20172329 2018-2019-2 《Java軟件結構與數據結構》實驗二報告

20172329 2018-2019-2 《Java軟件結構與數據結構》實驗二報告

課程:《Java軟件結構與數據結構》php

班級: 1723html

姓名: 王文彬java

學號:20172329node

實驗教師:王志強git

實驗日期:2018年11月7日web

必修/選修: 必修算法

一.實驗內容

1.1 第一個實驗內容

  • 要求
    • (1)參考課本P212使用鏈表實現二叉樹進行對於課本代碼的完善以及補全。
    • (2)實現方法getRight方法,contains方法,toString方法,preorder方法,postorder方法。
    • (3)用JUnit或本身編寫驅動類對本身實現的LinkedBinaryTree進行測試
    • (4)提交測試代碼運行截圖,要全屏,包含本身的學號信息,上傳代碼至碼雲,並提交碼雲連接。

1.2 第二個實驗內容

  • 要求
    • (1)基於LinkedBinaryTree,實現基於(中序,先序)序列構造惟一一棵二㕚樹的功能。
    • (2)好比給出中序HDIBEMJNAFCKGL和前序ABDHIEJMNCFGKL,構造出附圖中的樹。
    • (3)用JUnit或本身編寫驅動類對本身實現的類進行測試
    • (4)提交測試代碼運行截圖,要全屏,包含本身的學號信息,上傳代碼至碼雲,並提交碼雲連接。

1.3 第三個實驗內容

  • 要求
    • (1)本身設計並實現一顆決策樹
    • (2)用JUnit或本身編寫驅動類對本身實現的類進行測試
    • (3)提交測試代碼運行截圖,要全屏,包含本身的學號信息,上傳代碼至碼雲,並提交碼雲連接

1.4 第四個實驗內容

  • 要求
    • (1)輸入中綴表達式,使用樹將中綴表達式轉換爲後綴表達式,並輸出後綴表達式和計算結果
    • (2)用JUnit或本身編寫驅動類對本身實現的類進行測試
    • (3)提交測試代碼運行截圖,要全屏,包含本身的學號信息,上傳代碼至碼雲,並提交碼雲連接

1.5 第五個實驗內容

  • 要求
    • (1)完成PP11.3:對於二叉樹查找樹的鏈表實現,請實現removeMax方法,findMin方法和findMax方法以及後緒方法。
    • (2)用JUnit或本身編寫驅動類對本身實現的類進行測試
    • (3)提交測試代碼運行截圖,要全屏,包含本身的學號信息,上傳代碼至碼雲,並提交碼雲連接

1.6 第六個實驗內容

  • 要求
    • (1)參考http://www.cnblogs.com/rocedu/p/7483915.html對Java中的紅黑樹(TreeMap,HashMap)進行源碼分析,並在實驗報告中體現分析結果。

2、實驗過程及結果

2.1 第一個實驗過程

  • 步驟:
    • (1)第一個實驗是有關對於代碼補全的實驗實現,其中包括實現方法getRight方法,contains方法,toString方法,preorder方法,postorder方法。咱們將一個一個進行實驗而且進行代碼分析。
  • 代碼分析:

(1)getRight方法數組

public LinkedBinaryTree<T> getRight()
    {
        LinkedBinaryTree node = new LinkedBinaryTree();
        node.root=root.getRight();
       // return new LinkedBinaryTree(root.getRight());
       return node;
    }

分析:該方法經過先進行一個新樹的初始化,使得咱們能夠作一個臨時樹的角色,看到上述代碼中註釋的那一行,那是我第一次寫的,是一個錯誤的代碼,是由於他老是會獲得根也就是root的右側的樹或結點,可是咱們須要獲得的右子樹或者有結點是變化的,假如按照以前寫的,就會致使沒法進行遞歸,程序就死在根上了。咱們經過初始化下面一行的代碼進行根的更新,使咱們獲得合適的右子樹。安全

(2)contains方法數據結構

public boolean contains(T targetElement)
    {
        if (find(targetElement)==targetElement){
        return true;
    }
    else {
            return false;
        }

    }
    public T find(T targetElement) throws ElementNotFoundException
    {
        BinaryTreeNode<T> current = findNode(targetElement, root);

        if (current == null)
            throw new ElementNotFoundException("LinkedBinaryTree");

        return (current.getElement());
    }
    private BinaryTreeNode<T> findNode(T targetElement,
                                        BinaryTreeNode<T> next)
    {
        if (next == null)
            return null;

        if (next.getElement().equals(targetElement))
            return next;

        BinaryTreeNode<T> temp = findNode(targetElement, next.getLeft());

        if (temp == null)
            temp = findNode(targetElement, next.getRight());

        return temp;
    }

分析:該方法嵌套了另外的兩個方法,寫這個方法的邏輯就是,首先咱們須要找到這個結點,假如找到了這個結點就返回true,假如沒有找到就返回false。如今咱們須要清楚查找這個結點的過程是如何進行的。首先,咱們須要清楚咱們須要從哪一個結點開始找,在這裏就有人有疑問了,爲何不老是設定成從根開始找,其實,假如像數據量小這樣找還ok,可是假如在數據量龐大的時候,有這樣一個設定能夠很大的節約咱們查找元素的速度。

(3)toString方法

public String toString()
    {
        UnorderedListADT<BinaryTreeNode> nodes =
                new ArrayUnorderedList<BinaryTreeNode>();
        UnorderedListADT<Integer> levelList =
                new ArrayUnorderedList<Integer>();
        BinaryTreeNode current;
        String result = "";
        int printDepth = this.getHeight();
        int possibleNodes = (int)Math.pow(2, printDepth + 1);
        int countNodes = 0;

        nodes.addToRear(root);
        Integer currentLevel = 0;
        Integer previousLevel = -1;
        levelList.addToRear(currentLevel);

        while (countNodes < possibleNodes)
        {
            countNodes = countNodes + 1;
            current = nodes.removeFirst();
            currentLevel = levelList.removeFirst();
            if (currentLevel > previousLevel)
            {
                result = result + "\n\n";
                previousLevel = currentLevel;
                for (int j = 0; j < ((Math.pow(2, (printDepth - currentLevel))) - 1); j++)
                    result = result + " ";
            }
            else
            {
                for (int i = 0; i < ((Math.pow(2, (printDepth - currentLevel + 1)) - 1)) ; i++)
                {
                    result = result + " ";
                }
            }
            if (current != null)
            {
                result = result + (current.getElement()).toString();
                nodes.addToRear(current.getLeft());
                levelList.addToRear(currentLevel + 1);
                nodes.addToRear(current.getRight());
                levelList.addToRear(currentLevel + 1);
            }
            else {
                nodes.addToRear(null);
                levelList.addToRear(currentLevel + 1);
                nodes.addToRear(null);
                levelList.addToRear(currentLevel + 1);
                result = result + " ";
            }

        }

        return result;
    }

分析:這個代碼其實就是教材中表達式樹輸出成一棵樹的方法,具體詳見個人第六週博客,在其中的課本問題三中有詳細的分析。

(4)preorder方法

public Iterator<T> iteratorPreOrder()
    {
        ArrayUnorderedList<T> tempList = new ArrayUnorderedList<T>();
       preOrder(root, tempList);

        return new TreeIterator(tempList.iterator());
    }
    
protected void preOrder(BinaryTreeNode<T> node,
                            ArrayUnorderedList<T> tempList)
    {
        if(node!=null)
            {
               System.out.print(node.getElement()+" ");
                tempList.addToRear(node.getElement());
                preOrder(node.getLeft(),tempList);
                preOrder(node.getRight(),tempList);
            }

    }

分析:這一部分實則運用的是迭代器的方法,前序遍歷實現講分析與後序大體相同,在這裏只分析前序,首先咱們要清楚前序遍歷的順序,在這裏假若有不清楚的同窗能夠參考個人第六週博客,教材內容總結部分。當咱們清楚了前序遍歷是如何進行的之後,咱們就能夠大體瞭解這個遞歸的運行原理了。其中迭代器的做用爲的是咱們能夠將其進行一個一個的輸出。

  • 實驗實現結果展現:

2.2 第二個實驗過程

  • 步驟:

    • 第二個實驗是須要咱們去利用中序和前序去構造出一顆二叉樹,咱們須要清楚的有如下步驟:

    • (1)首先咱們在課上練習過如何去利用中序和前序去構造出一顆樹,在這裏我再進行說明;

    • (2)有這樣一句話方便理解前(或後)序定根,中序定左右,如何理解這句話呢,舉個例子,就拿咱們題目中的進行分析。前序是:ABDHIEJMNCFGKL,中序是:HDIBEMJNAFCKGL。

      • 一、首先咱們看前序的第一個元素,是A則其確定是根,因此在中序中找到A,如今在中序遍歷中A左邊的就是A的左子樹,A右邊的就是A的右子樹;

      • 二、一樣咱們繼續找前序的第二個元素,是B,因此再看中序中B的位置,一樣和理解A的左右同理,咱們能夠找到B結點的左子樹和右子樹。

      • 三、緊接着重複上述直到找到中序的第一個元素爲至,咱們就中止,如今咱們構造好的是整個樹的左子樹(左樹的左樹);

      • 四、如今開始看A的左子樹的右子樹的完善工做,由於D是左邊最後一個根,因此從中序中能夠得知,H是D的左孩 子,I是D的右孩子。

      • 五、咱們繼續向上找,發現根是B,B的左邊咱們已經構造完成了,因此咱們如今須要構造出B的右邊,EMJN是咱們如今須要構造出的,咱們看中序,咱們看到E是處於最左邊的,說明它是一個左孩子或者是一個根,再緊接着看,前序,發現E是第一個,因此說明E是一個根,因此咱們肯定了B的右根爲E;

      • 六、由於在看到中序E後面的是M,因此,和第5步的理解相同,發現一樣M是一個根,如今再看前序,發現J和N在M的兩側,即分別是M的左子樹和右子樹。

      • 七、肯定了A的左子樹了之後咱們繼續看A的右子樹,中序是:FCKGL,由於在前序中C在F以前,因此否認F是根,F是C的左子樹,因此如今能夠知道A的右子樹的根是C,左子樹是F,再根據一樣的原理,能夠得知,G爲C的右根,KL分別是G的左孩子和右孩子。

      • 八、經過這樣一個過程就構建出一棵樹了。

    • (3)根據這樣一個思想咱們開始進行對於代碼的編寫。

  • 代碼分析

public int find(T[] a,T element,int begin,int end){
        for(int i = begin;i<=end; i++){
            if(a[i]==element){
                return i;
            }
        }
        return -1;
    }

    public BinaryTreeNode<T> construct(T[] pre,int prestart,int preend,T[] inO,int inOSart,int inOend){
        if(prestart>preend||inOSart>inOend){
            return null;
        }
        BinaryTreeNode Root;
        T rootData = pre[prestart];
         Root = new BinaryTreeNode(rootData);
        //找到根節點所在位置
        int rootIndex = find(inO,rootData,inOSart,inOend);
        //構建左子樹
        Root.left = construct(pre,prestart+1,prestart+rootIndex-inOSart,inO,inOSart,rootIndex-1);
       // 構建右子樹

        Root.right = construct(pre,prestart+rootIndex-inOSart+1,preend,inO,rootIndex+1,inOend);
         return Root;

        }

        public void ConTree(T[] pre,T[] inO){
        BinaryTreeNode<T> node = construct(pre,0,pre.length-1,inO,0,inO.length-1);
        root=node;
        }

分析:咱們在這個主方法定義了六個變量,在construct方法的做用分別爲:傳入一個先序遍歷結果(數組),肯定先序遍歷中開始的位置,肯定先序遍歷中結束的位置,傳入一箇中序遍歷結果(數組),肯定中序遍歷中開始的位置,肯定中序遍歷中結束的位置。如今咱們開始看方法體的內容,咱們首先肯定一個東西,就是根確定是傳入先序遍歷的第一個元素,因此先肯定根的位置,找到根之後,咱們開始安排找到它的子樹們,咱們經過一個find方法進行對於咱們須要元素在中序遍歷中的查找,由於咱們能夠經過一個規律,就是對於傳入的元素查找後獲得的值(1或者-1)進行對於左孩子仍是右孩子的查找,從而能夠完成一棵樹的構造。

  • 實驗實現結果展現

2.3 第三個實驗過程

  • 步驟

    • 第三個實驗是讓咱們本身寫一個決策樹,對於這個實驗我基於課本代碼進行了仿寫進行實驗。
  • 代碼分析:
public DecisionTree(String filename) throws FileNotFoundException
    {
        File inputFile = new File(filename);
        Scanner scan = new Scanner(inputFile);
        int numberNodes = scan.nextInt();
        scan.nextLine();
        int root = 0, left, right;
        
        List<LinkedBinaryTree<String>> nodes = new ArrayList<LinkedBinaryTree<String>>();
        for (int i = 0; i < numberNodes; i++)
            nodes.add(i,new LinkedBinaryTree<String>(scan.nextLine()));
        
        while (scan.hasNext())
        {
            root = scan.nextInt();
            left = scan.nextInt();
            right = scan.nextInt();
            scan.nextLine();
            
            nodes.set(root, new LinkedBinaryTree<String>((nodes.get(root)).getRootElement(), 
                                                     nodes.get(left), nodes.get(right)));
        }
        tree = nodes.get(root);
    }
    public void evaluate()
    {
        LinkedBinaryTree<String> current = tree;
        Scanner scan = new Scanner(System.in);

        while (current.size() > 1)
        {
            System.out.println (current.getRootElement());
            if (scan.nextLine().equalsIgnoreCase("N"))
                current = current.getLeft();
            else
                current = current.getRight();
        }

        System.out.println (current.getRootElement());
    }

分析:這一部分的代碼由於都是書中的代碼,本身就照搬了,(本身太懶了)在這代碼中首先咱們利用一個txt文檔對於書中的文檔進行了層序的保存,經過讀取文件的方式咱們進行決策樹的構造,其中第一個方法(DecisionTree)咱們將文件讀取後將其內容一個一個放入一個鏈表,而後經過層序進行把元素放入樹中;第二個方法(evaluate)用於判斷當咱們輸入的是N或者其餘時,進行對於元素的輸出。

  • 實驗實現結果展現

2.4 第四個實驗過程

  • 步驟

    • 第四個實驗要求咱們輸入一箇中綴表達式,經過一個二叉樹轉換成後綴表達式,再將其輸出後進行計算,首先咱們須要清楚這個實驗應該如何去進行。

    • (1)第四個實驗一開始我畫了不少中樹圖進行表示,最後本身認爲假如實現的話有兩種方式:

      • 一、由於輸入的中綴表達式可能涉及咱們須要面臨括號的問題,或者也就是咱們須要去如何去解決優先級的問題,在這時候個人第一種想法是:先將一箇中綴表達式按照優先級進行排序,好比,1+(1+2✖3,我首先將1+2放到最前其次是 *3,最後是+1,最後結果就是1+2✖3+1 ,而後將數字與操做符存入兩個棧或者鏈表,分別進行彈出,自左向右,造成一棵樹;可是這個方法由於假如涉及兩個括號就變得較爲複雜,由於這個時候須要構建右子樹,因此就將這個方法淘汰了。

      • 二、第二站方法也就是最終進行實現的方法,首先咱們輸入一箇中綴表達式,將表達式的每個元素進行拿出,按照「數字」,「括號」,「加減」,「乘除」分紅四種狀況,用兩個棧或者鏈表進行保存「加減」和「構成樹的結點」。這種方法會使得方法實現變得簡單易行。

  • 代碼分析
//由於代碼的龐大,因此就關鍵代碼進行分析,循環和輸出的過程簡單,在
//這裏不作分析,在這裏分析如何對於每個元素進行分析
//這個代碼參考了以前學長的代碼,而且請教了學長本人,經過學長的講授受益不淺。
if (a[i].equals("(")){
    String temp1 = "";
    while (true) {
    if (!a[i+1].equals(")")){
     temp1 += a[i+1] + " ";
            }
            else {
            break;
        }
            i++;
            }
        chapter10.jsjf.LinkedBinaryTree temp = new chapter10.jsjf.LinkedBinaryTree();
        temp.root = maketree(temp1);
         num.add(temp);
         i++;
            }
         if (a[i].equals("+")||a[i].equals("-")){
     fuhao.add(String.valueOf(a[i]));
             }
      else if (a[i].equals("*")||a[i].equals("/")){
    LinkedBinaryTree left=num.remove(num.size()-1);
     String temp2=String.valueOf(a[i]);
     if (!a[i+1].equals("(")){
     LinkedBinaryTree right = new LinkedBinaryTree(String.valueOf(a[i+1]));
     LinkedBinaryTree node = new LinkedBinaryTree(temp2, left, right);
    num.add(node);
        }
        else {
        String temp3 = "";
        while (true) {
        if (!a[i+1].equals(")")){
        temp3+=String.valueOf(a[i+1]);
        }
        else {
         break;
        }
        i++;
        }
        LinkedBinaryTree temp4 = new LinkedBinaryTree();
        temp4.root = maketree(temp3);
        LinkedBinaryTree node1 = new LinkedBinaryTree(temp2, left, temp4);
        num.add(node1);
       }
       }else {
        num.add(new LinkedBinaryTree(a[i]));}

分析:從上述的代碼就能夠看到須要的邏輯之複雜,當時在學習過程就實驗四真的是絞盡腦汁。首先,咱們須要作的事情是先了解咱們這個樹是如何「長」成一棵樹的,由於優先級最高的是括號,因此括號的這一部分就是咱們的葉子,咱們這個樹是倒着長,從葉子長到根,所以咱們就須要針對括號進行優先處理,因此咱們先寫括號。一、當咱們遇到‘(’的時候,咱們要作的一件事就是須要將直至‘)’內的元素都進行一個保存,由於說明這一部分咱們須要優先處理它,當咱們將這一部分進行保存了之後咱們利用一個遞歸,開始處理這一部分;二、處理括號內部分的過程和處理括號外是同樣的,只是括號須要優先處理,如今咱們開始分析當咱們進行括號或者一個相似與‘1+2✖6’之類的式子進行處理;三、當咱們遇到數字的時候,咱們將其保存進數字的鏈表(樹類型,也就是保存進一個以這個數字爲根的小樹)中,而後循環;四、當咱們遇到‘+或者-’的時候,將其保存進一個存符號的鏈表,而後循環;五、當咱們遇到‘✖或者➗的時候,咱們首先須要將咱們以前存入數字的鏈表的元素進行拿出而且進行將其作一個新樹,由於在畫過這種樹圖的同窗來說,很清楚一個問題,就是加法確定會跟靠近樹根,因此咱們要將咱們以前放入數字鏈表的元素拿出來,爲作一個左樹作好準備,而後判斷一下‘✖️或者➗’ 後有沒有括號,假若有括號咱們仍舊須要優先處理,按照1,2操做,,構造好一個右樹,而後當咱們處理好這一系列問題之後,就能夠以這個+或者-爲根,將剛剛分析的作好準備的左樹和剛剛構造好的右樹進行放在這個根的下面,分別作它的左子樹和右子樹,而後重複這個過程,直至沒有元素可找。這就是整個實驗四在遇到各個符號的狀況。

  • 實驗實現結果展現

2.5 第五個實驗過程

  • 步驟:

    • (1)第五個實驗同時咱們和第一個實驗四類似,都是進行代碼補全,進行分析。

    • (2)實現removeMax方法,findMin方法和findMax方法以及後緒方法。

  • 代碼分析:

(1)removeMax方法

public T removeMax() throws EmptyCollectionException
    {
        T result = null;

        if (isEmpty())
            throw new EmptyCollectionException("LinkedBinarySearchTree");
        else
        {
            if (root.right == null)
            {
                result = root.element;
                root = root.left;
            }
            else
            {
                BinaryTreeNode<T> parent = root;
                BinaryTreeNode<T> current = root.right;
                while (current.right != null)
                {
                    parent = current;
                    current = current.right;
                }
                result =  current.element;
                parent.right = current.left;
            }

            modCount--;
        }

        return result;
    }

由於咱們知道對於一個樹而言,最左邊是最小值,最右邊是最大值,因此咱們假如要刪除最小值的話,就須要先找到這個最小值,而後讓它爲空,而且返回它,刪除最大值也是一樣的道理。而找到最小值最大值一樣也只是這一部分代碼的一部分,同理。

  • 實驗實現結果展現

2.6 第六個實驗過程

  • 步驟:

    • 最後一個實驗是讓咱們對Java中的紅黑樹(TreeMap,HashMap)進行源碼分析,首先既然是紅黑樹的兩個方法,因此在開始的時候咱們要去了解紅黑樹是什麼,具體能夠詳見個人第七週博客

    • 首先既然兩個都是map結尾的,說明map也是一個類,咱們先來看看map是什麼?

    • map:Map接口中鍵和值一一映射. 能夠經過鍵來獲取值。

      • (1)給定一個鍵和一個值,你能夠將該值存儲在一個Map對象. 以後,你能夠經過鍵來訪問對應的值。
      • (2)當訪問的值不存在的時候,方法就會拋出一個NoSuchElementException異常.
      • (3)當對象的類型和Map裏元素類型不兼容的時候,就會拋出一個 ClassCastException異常。
      • (4)當在不容許使用Null對象的Map中使用Null對象,會拋出一個NullPointerException 異常。
      • (5)當嘗試修改一個只讀的Map時,會拋出一個UnsupportedOperationException異常。
      • 用代碼舉個例子:
import java.util.*;

public class CollectionsDemo {

   public static void main(String[] args) {
      Map m1 = new HashMap(); 
      m1.put("Zara", "8");
      m1.put("Mahnaz", "31");
      m1.put("Ayan", "12");
      m1.put("Daisy", "14");
      System.out.println();
      System.out.println(" Map Elements");
      System.out.print("\t" + m1);
   }
}
結果:
Map Elements
        {Mahnaz=31, Ayan=12, Daisy=14, Zara=8}
  • 在以上的程序中一樣的也展現出了咱們一下子要分析的方法之一hashmap方法,以上的方法是用了其put方法,也就是讓兩個參數之間創建了一種映射。所以在這裏咱們詳細的列一下map這個類所擁有的方法。
序號 方法 描述
1 void clear( ) 今後映射中移除全部映射關係(可選操做)。
2 boolean containsKey(Object k) 若是此映射包含指定鍵的映射關係,則返回 true。
3 boolean containsValue(Object v) 若是此映射將一個或多個鍵映射到指定值,則返回 true。
4 Set entrySet( ) 返回此映射中包含的映射關係的 Set 視圖。
5 boolean equals(Object obj) 比較指定的對象與此映射是否相等。
6 Object get(Object k) 返回指定鍵所映射的值;若是此映射不包含該鍵的映射關係,則返回 null。
7 int hashCode( ) 返回此映射的哈希碼值。
8 boolean isEmpty( ) 若是此映射未包含鍵-值映射關係,則返回 true。
9 Set keySet( ) 返回此映射中包含的鍵的 Set 視圖。
10 Object put(Object k, Object v) 將指定的值與此映射中的指定鍵關聯(可選操做)。
11 void putAll(Map m) 從指定映射中將全部映射關係複製到此映射中(可選操做)。
12 Object remove(Object k) 若是存在一個鍵的映射關係,則將其今後映射中移除(可選操做)。
13 int size( ) 返回此映射中的鍵-值映射關係數。
14 Collection values( ) 返回此映射中包含的值的 Collection 視圖。
  • 在瞭解了這個map類之後咱們開始步入咱們的正題。

  • treemap方法:
    • 咱們首先先了解一下這個類:
      • 一、TreeMap 是一個有序的key-value集合,它是經過紅黑樹實現的。
      • 二、TreeMap 繼承於AbstractMap,因此它是一個Map,即一個key-value集合。
      • 三、TreeMap 實現了NavigableMap接口,意味着它支持一系列的導航方法。好比返回有序的key集合。
      • 四、TreeMap 實現了Cloneable接口,意味着它能被克隆。
      • 五、TreeMap 實現了java.io.Serializable接口,意味着它支持序列化。
      • 六、TreeMap基於紅黑樹(Red-Black tree)實現。該映射根據其鍵的天然順序進行排序,或者根據建立映射時提供的 Comparator 進行排序,具體取決於使用的構造方法。
      • 七、TreeMap的基本操做 containsKey、get、put 和 remove 的時間複雜度是 log(n) 。
      • 八、TreeMap是非同步的。 它的iterator 方法返回的迭代器是fail-fastl的。
    • 源碼分析
      我在這裏經過網上其餘博主的參考和本身的理解對此方法進行了分析,由於代碼的龐大,因此在這裏放置代碼連接。重要的一些方法在這裏我進行分析。

(1)瞭解構造函數

// 帶比較器的構造函數
    public TreeMap(Comparator<? super K> comparator) {
        this.comparator = comparator;
    }

    // 帶Map的構造函數,Map會成爲TreeMap的子集
    public TreeMap(Map<? extends K, ? extends V> m) {
        comparator = null;
        putAll(m);
    }

    // 帶SortedMap的構造函數,SortedMap會成爲TreeMap的子集
    public TreeMap(SortedMap<K, ? extends V> m) {
        comparator = m.comparator();
        try {
            buildFromSorted(m.size(), m.entrySet().iterator(), null, null);
        } catch (java.io.IOException cannotHappen) {
        } catch (ClassNotFoundException cannotHappen) {
        }
    }

(2)瞭解咱們在其餘數據結構中常見的幾個方法

// 返回TreeMap中是否保護「鍵(key)」
    public boolean containsKey(Object key) {
        return getEntry(key) != null;
    }

    // 返回TreeMap中是否保護"值(value)"
    public boolean containsValue(Object value) {
        // getFirstEntry() 是返回紅黑樹的第一個節點
        // successor(e) 是獲取節點e的後繼節點
        for (Entry<K,V> e = getFirstEntry(); e != null; e = successor(e))
            if (valEquals(value, e.value))
                return true;
        return false;
    }

    // 獲取「鍵(key)」對應的「值(value)」
    public V get(Object key) {
        // 獲取「鍵」爲key的節點(p)
        Entry<K,V> p = getEntry(key);
        // 若節點(p)爲null,返回null;不然,返回節點對應的值
        return (p==null ? null : p.value);
    }

(3)其餘詳見碼雲

  • hashmap方法
    • 一開始看到這個方法有一絲疑問,爲何在紅黑樹裏面還會牽扯到哈希表,也就是散列表,而後就心存一位是否是應該是treeset方法,因此一下子仍是想繼續分析treeset方法,可是首先我須要瞭解,爲何散列表會和紅黑樹掛勾呢?
    • 咱們知道HashMap中的值都是key,value對吧,其實這裏的存儲與上面的很像,key會被映射成數據所在的地址,而value就在以這個地址爲頭的鏈表中,這種數據結構在獲取的時候就很快。但這裏存在的問題就是若是hash桶較小,數據量較大,就會致使鏈表很是的長。好比說上面的長爲11的空間我要放1000個數,不管Hash函數如何精妙,後面跟的鏈表都會很是的長,這樣Hash表的優點就不復存在了,反而傾向於線性檢索。

    • 源碼分析

//實際存儲的key-value鍵值對的個數
transient int size;
//閾值,當table == {}時,該值爲初始容量(初始容量默認爲16);當table被填充了,也就是爲table分配內存空間後,threshold通常爲 capacity*loadFactory。HashMap在進行擴容時須要參考threshold,後面會詳細談到
int threshold;
//負載因子,表明了table的填充度有多少,默認是0.75
final float loadFactor;
//用於快速失敗,因爲HashMap非線程安全,在對HashMap進行迭代時,若是期間其餘線程的參與致使HashMap的結構發生變化了(好比put,remove等操做),須要拋出異常ConcurrentModificationException
transient int modCount;
  • 構造函數:
public HashMap(int initialCapacity, float loadFactor) {
     //此處對傳入的初始容量進行校驗,最大不能超過MAXIMUM_CAPACITY = 1<<30(230)
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);

        this.loadFactor = loadFactor;
        threshold = initialCapacity;
     
        init();//init方法在HashMap中沒有實際實現,不過在其子類如 linkedHashMap中就會有對應實現
    }
  • get方法:
public V get(Object key) {
     //若是key爲null,則直接去table[0]處去檢索便可。
        if (key == null)
            return getForNullKey();
        Entry<K,V> entry = getEntry(key);
        return null == entry ? null : entry.getValue();
 }
  • treeset方法:

  • 構造函數:

序號 方法 描述
1 TreeSet () 此構造函數構造空樹集,將在根據其元素的天然順序按升序排序。
2 TreeSet (Collection c) 此構造函數生成樹的集合,它包含的元素的集合 c。
3 TreeSet (Comparable comp) 此構造函數構造一個空樹集,將根據給定的比較器進行排序。
4 TreeSet (SortedSet ss) 此構造函數生成包含給定 SortedSet 的元素 TreeSet
  • 方法:
序號 方法 描述
1 add(E e) 將指定的元素添加到這套,若是它已不存在。
2 addAll(Collection<? extends E> c) 在加入這一組指定的集合中添加的全部元素。
3 ceiling(E e) 返回最小的元素在這一組大於或等於給定的元素
4 clear() 從這一組中移除全部元素。
5 clone() 返回此TreeSet實例淺表副本。
6 comparator() 返回用於排序在這集,或空元素
7 contains(Object o) 若是此集合包含指定的元素,則返回true 。
8 descendingIterator() 返回迭代器中這套降序排序的元素。
9 descendingSet() 返回逆序視圖中包含的元素這一套。
10 first() 返回第一個 (最低) 元素當前在這一套。
11 floor(E e) 返回的最大元素在這一組小於或等於null若是沒有這樣的元素。
12 headSet(E toElement) 返回其元素是嚴格小於toElement這套的部分視圖.
13 headSet(E toElement, boolean inclusive) 返回一個視圖的這部分設置的元素都小於
14 higher(E e) 返回最小的元素在這套嚴格大於給定的元素
15 isEmpty() 若是此集不包含任何元素,則返回true 。
16 iterator() 返回迭代器中這套以升序排序的元素。
17 last() 在這套目前返回的最後一個 (最高) 的元素。
18 lower(E e) 在這一套嚴格的小於給定的元素,則null返回的最大元素
19 pollFirst() 檢索和刪除第一個 (最低) 元素,或若是此集合爲空
20 pollLast() 檢索和刪除的最後一個 (最高) 的元素
21 remove(Object o) 從這一組中移除指定的元素,若是它存在。
22 size() 在這套 (其基數) 中返回的元素的數目。
23 subSet(E fromElement, boolean fromInclusive, E toElement) 返回此集的部分視圖的元素範圍從fromElement到toElement.

3、 實驗過程當中遇到的問題和解決過程

  • 問題1:在以前寫第二個實驗的時候發生了棧溢出的狀況,以下

java.lang.StackOverflowError異常(異常的圖丟了)

  • 問題1解決方案:

    • (1)我發現當咱們空寫一個初始化的時候,用它遞歸的時候就會發生棧溢出。

    • (2)方法一:用棧把遞歸轉換成非遞歸
      一般,一個函數在調用另外一個函數以前,要做以下的事情:

      • a)將實在參數,返回地址等信息傳遞給被調用函數保存;

      • b)爲被調用函數的局部變量分配存儲區;

      • c)將控制轉移到被調函數的入口。從被調用函數返回調用函數以前,也要作三件事情:
        • a)保存被調函數的計算結果;
        • b)釋放被調函數的數據區;
        • c)依照被調函數保存的返回地址將控制轉移到調用函數.全部的這些,不管是變量仍是地址,本質上來講都 是"數據",都是保存在系統所分配的棧中的. 那麼本身就能夠寫一個棧來存儲必要的數據,以減小系統負。
    • (3)方法二:使用static對象替代nonstatic局部對象
      在遞歸函數設計中,可使用static對象替代nonstatic局部對象(即棧對象),這不只能夠減小每次遞歸調用和返回時產生和釋放nonstatic對象的開銷,並且static對象還能夠保存遞歸調用的中間狀態,而且可爲各個調用層所訪問。

  • 問題2:其他的問題在我搞懂實驗的同時也都解決了,不少都寫在上述的步驟過程和代碼分析中進行了分析。
  • 問題2:詳見實驗過程

4、感想

在此次實驗中,本身寫了不少,同時也斃掉了不少版代碼,算法真是個神奇的東西,我本身也參考了不少文章,不少博客才能完成此次實驗,發現本身還差不少,本身還須要很努力才行。

5、參考文獻

【數據結構】中綴表達式構造二叉樹轉換成後綴表達式
表達式樹—中綴表達式轉換成後綴表達式(一)
java實現二叉樹已知先序遍歷和中序遍歷求後序遍歷
根據中序和前序序列生成二叉樹java遞歸實現
已知二叉樹的中序和前序序列(或後序)求解樹
Java實現二叉樹的前序、中序、後序、層序遍歷(非遞歸方法)
藍墨雲班課
Java程序設計
Java 集合系列12之 TreeMap詳細介紹(源碼解析)和使用示例
Java Map 接口
HashMap實現原理及源碼分析

相關文章
相關標籤/搜索