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

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

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

班級: 1723

姓名: 王文彬

學號:20172329

實驗教師:王志強

實驗日期:2018年11月7日

必修/選修: 必修

一.實驗內容

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.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。

      • 1、首先我們看前序的第一個元素,是A則其肯定是根,所以在中序中找到A,現在在中序遍歷中A左邊的就是A的左子樹,A右邊的就是A的右子樹;

      • 2、同樣我們繼續找前序的第二個元素,是B,所以再看中序中B的位置,同樣和理解A的左右同理,我們可以找到B結點的左子樹和右子樹。

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

      • 4、現在開始看A的左子樹的右子樹的完善工作,因爲D是左邊最後一個根,所以從中序中可以得知,H是D的左孩 子,I是D的右孩子。

      • 5、我們繼續向上找,發現根是B,B的左邊我們已經構造完成了,所以我們現在需要構造出B的右邊,EMJN是我們現在需要構造出的,我們看中序,我們看到E是處於最左邊的,說明它是一個左孩子或者是一個根,再緊接着看,前序,發現E是第一個,所以說明E是一個根,所以我們確定了B的右根爲E;

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

      • 7、確定了A的左子樹了以後我們繼續看A的右子樹,中序是:FCKGL,因爲在前序中C在F之前,所以否定F是根,F是C的左子樹,所以現在可以知道A的右子樹的根是C,左子樹是F,再根據同樣的原理,可以得知,G爲C的右根,KL分別是G的左孩子和右孩子。

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

    • (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+(1+2✖3,我首先將1+2放到最前其次是 *3,最後是+1,最後結果就是1+2✖3+1 ,然後將數字與操作符存入兩個棧或者鏈表,分別進行彈出,自左向右,形成一棵樹;但是這個方法因爲假如涉及兩個括號就變得較爲複雜,因爲這個時候需要構建右子樹,所以就將這個方法淘汰了。

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

  • 代碼分析
//因爲代碼的龐大,所以就關鍵代碼進行分析,循環和輸出的過程簡單,在
//這裏不做分析,在這裏分析如何對於每一個元素進行分析
//這個代碼參考了之前學長的代碼,並且請教了學長本人,通過學長的講授受益匪淺。
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、處理括號內部分的過程和處理括號外是一樣的,只是括號需要優先處理,現在我們開始分析當我們進行括號或者一個類似與‘1+2✖6’之類的式子進行處理;3、當我們遇到數字的時候,我們將其保存進數字的鏈表(樹類型,也就是保存進一個以這個數字爲根的小樹)中,然後循環;4、當我們遇到‘+或者-’的時候,將其保存進一個存符號的鏈表,然後循環;5、當我們遇到‘✖或者➗的時候,我們首先需要將我們之前存入數字的鏈表的元素進行拿出並且進行將其做一個新樹,因爲在畫過這種樹圖的同學來講,很清楚一個問題,就是加法肯定會跟靠近樹根,所以我們要將我們之前放入數字鏈表的元素拿出來,爲做一個左樹做好準備,然後判斷一下‘✖️或者➗’ 後有沒有括號,假如有括號我們仍舊需要優先處理,按照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方法:
    • 我們首先先了解一下這個類:
      • 1、TreeMap 是一個有序的key-value集合,它是通過紅黑樹實現的。
      • 2、TreeMap 繼承於AbstractMap,所以它是一個Map,即一個key-value集合。
      • 3、TreeMap 實現了NavigableMap接口,意味着它支持一系列的導航方法。比如返回有序的key集合。
      • 4、TreeMap 實現了Cloneable接口,意味着它能被克隆。
      • 5、TreeMap 實現了java.io.Serializable接口,意味着它支持序列化。
      • 6、TreeMap基於紅黑樹(Red-Black tree)實現。該映射根據其鍵的自然順序進行排序,或者根據創建映射時提供的 Comparator 進行排序,具體取決於使用的構造方法。
      • 7、TreeMap的基本操作 containsKey、get、put 和 remove 的時間複雜度是 log(n) 。
      • 8、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.

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

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

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

  • 問題1解決方案:

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

    • (2)方法一:用棧把遞歸轉換成非遞歸
      通常,一個函數在調用另一個函數之前,要作如下的事情:

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

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

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

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

四、感想

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

五、參考文獻

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章