1. 程式人生 > >20172332 2018-2019-2 《程式設計與資料結構》實驗二報告

20172332 2018-2019-2 《程式設計與資料結構》實驗二報告

20172332 2017-2018-2 《程式設計與資料結構》實驗二報告

課程:《程式設計與資料結構》
班級: 1723
姓名: 於欣月
學號:20172332
實驗教師:王志強
實驗日期:2018年11月8日
必修/選修: 必修

1.實驗內容

  • 實驗一:實現二叉樹。
  • 參考教材p212,完成鏈樹LinkedBinaryTree的實現(getRight,contains,toString,preorder,postorder)

  • 實驗二:中序先序序列構造二叉樹
  • 基於LinkedBinaryTree,實現基於(中序,先序)序列構造唯一一棵二㕚樹的功能,比如給出中序HDIBEMJNAFCKGL和後序ABDHIEJMNCFGKL,構造出附圖中的樹

  • 實驗三:決策樹
  • 自己設計並實現一顆決策樹

  • 實驗四:表示式樹
  • 輸入中綴表示式,使用樹將中綴表示式轉換為字尾表示式,並輸出字尾表示式和計算結果(如果沒有用樹,則為0分)

  • 實驗五:二叉查詢樹
  • 完成PP11.3

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

2. 實驗過程及結果

前期準備:

  • 1.瞭解二叉樹的構造與實現原理。

過程:

  • 1.實驗一
  • 關鍵程式碼:
public BinaryTreeNode<T> getRight() {
        return root.right;
    }
public boolean contains(T targetElement) {
        BinaryTreeNode<T> current = findAgain(targetElement, root);
        if (current == null)
            return false;
        else
            return true;
    }
    //先判斷根root是否為空,如果為空則null,如果元素相等則返回root結點,
    // 否則開始進行遞迴,先判斷左孩子是否為空,為空則null,如果元素相等則返回左孩子結點,不相等再進行遞迴
    // 直到為null時,temp也為null,開始判斷右孩子,與上同理,進行遞迴,最後得到結果。
     private BinaryTreeNode<T> findAgain(T targetElement, BinaryTreeNode<T> next) {
        if (next == null)
            return null;
        if (next.element.equals(targetElement))
            return next;
        BinaryTreeNode<T> temp = findAgain(targetElement, next.left);

        if (temp == null)
            temp = findAgain(targetElement, next.right);

        return temp;
    }
//toString程式碼的理解在第六週部落格中有詳細的過程
public String toString() {
        UnorderedListADT<BinaryTreeNode<T>> nodes =
                new ArrayUnorderedList<BinaryTreeNode<T>>();
        UnorderedListADT<Integer> levelList =
                new ArrayUnorderedList<Integer>();
        BinaryTreeNode<T> 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) {
                previousLevel = currentLevel;
                result = result + "\n" + "\n";

                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;
    }
public void preOrder(BinaryTreeNode<T> root) {
        if (root != null) {
            System.out.print(root.element + " ");
            preOrder(root.left);
            preOrder(root.right);
        }
    }
public void postOrder(BinaryTreeNode<T> node, ArrayUnorderedList<T> tempList) {
        if (node != null) {
            postOrder(node.getLeft(), tempList);
            postOrder(node.getRight(), tempList);
            tempList.addToRear(node.getElement());
  • 測試結果:

  • 2.實驗二
  • 關鍵程式碼:

public void buildTree(T[] preorder, T[] inorder) {
        BinaryTreeNode temp = makeTree(preorder, 0, preorder.length, inorder, 0, inorder.length);
        root = temp;

    }
public BinaryTreeNode<T> makeTree(T[] preorder, int start, int len, T[] inorder, int start1, int len1) {
        if (len < 1) {
            return null;
        }
        BinaryTreeNode root;
        //先序的第一個元素為根
        root = new BinaryTreeNode(preorder[start]);
        int n = 0;
        for (int temp = 0; temp < len1; temp++) {
            //找到在中序中的根的位置並記錄
            if (inorder[start1 + temp] == root.element) {
                n = temp;
                break;
            }
        }
        //左子樹的建立,先序的左子樹開始是下一位,結束是中序的開始到記錄的位置,中序的左子樹開始不變,結束的記錄的位置
        root.setLeft(makeTree(preorder, start + 1, n, inorder, start1, n));
        //右子樹的建立,先序的右子樹開始是左子樹的根位置加上中序的記錄的位置,結束是最後的索引值減去記錄的位置。
        root.setRight(makeTree(preorder, start + n + 1, len - n - 1, inorder, start1 + n + 1, len1 - n - 1));
        return root;
    }
  • 測試結果:

  • 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 java.util.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);
    }
  • 測試結果:

  • 4.實驗四
  • 關鍵程式碼:

//大體思路是把數字放在一個數組中,把+ - 放在另一個數組中,如果遇見* /,把* / 作為根,從數字組中取出最後的數作為左孩子,把* / 的後一個數作為右孩子,把整個樹再放在數字組中。遇見括號就把括號裡的全部讀出作為新的表示式遞迴進入該方法,再得出該子表示式的結果放入數字組中。

public BinaryTreeNode buildTree(String str) {
//建立一個結點類的ArrayList存放數字,建立一個String的ArrayList存放符號
        ArrayList<BinaryTreeNode> num = new ArrayList<BinaryTreeNode>();
        ArrayList<String> fuhao = new ArrayList<String>();
        StringTokenizer st = new StringTokenizer(str);
        String token;
        while (st.hasMoreTokens()) {
            token = st.nextToken();
            //如果是括號,就讀出括號裡的所有內容形成子表示式。
            if (token.equals("(")) {
                String str1 = "";
                token = st.nextToken();
                while (!token.equals(")")) {
                    str1 += token + " ";
                    token = st.nextToken();
                }
                //得出子表示式的結果之後,如果父表示式結束,也就是“)”後再沒內容,則跳出該迴圈,否則接著讀下一個元素。
                num.add(buildTree(str1));
                if (st.hasMoreTokens()) {
                    token = st.nextToken();
                }
                else
                    break;
            }
            //如果是數,則直接存入數字組中。
            if (!token.equals("+") && !token.equals("-") && !token.equals("*") && !token.equals("/")) {
                num.add(new BinaryTreeNode(token));
            }
            //如果是+或者-,先把該元素作為結點,再讀取下一個元素,如果下一個元素不為“(”,則把該元素放入符號組,把下一個元素(肯定是數字)放入數字組。如果下一個元素是“(”,則把該元素放入符號組之後,再讀取下一個的下一個元素(也就是括號後的第一個元素),再把括號裡的元素讀出形成子表示式呼叫該方法得出結果,放入數字組中。
            if (token.equals("+") || token.equals("-")) {

                BinaryTreeNode<String> tempNode = new BinaryTreeNode<>(token);
                token = st.nextToken();
                if (!token.equals("(")) {
                    fuhao.add(tempNode.element);
                    num.add(new BinaryTreeNode(token));
                }
                else {
                    fuhao.add(tempNode.element);
                    String temp = st.nextToken();
                    String s = "";
                    while (!temp.equals(")")) {
                        s += temp + " ";
                        temp = st.nextToken();
                    }
                    num.add(buildTree(s));
                }
            }
            //如果是*或者/,先把該元素形成新的結點,如果下一個元素不為“(”,則把數字組的最後一個元素作為左孩子,下一個元素作為右孩子,形成新的樹存入數字組中。如果下一個元素為“(”,則把數字組的最後一個元素作為左孩子,把括號得出的子表示式的結果作為右孩子,得出的樹放入數字組中。
            if (token.equals("*") || token.equals("/")) {
                BinaryTreeNode<String> tempNode = new BinaryTreeNode<>(token);
                token = st.nextToken();
                if (!token.equals("(")) {
                    tempNode.setLeft(num.remove(num.size() - 1));
                    tempNode.setRight(new BinaryTreeNode<String>(token));
                    num.add(tempNode);
                }
                else {
                    String temp = st.nextToken();
                    tempNode.setLeft(num.remove(num.size() - 1));
                    String s = "";
                    while (!temp.equals(")")) {
                        s += temp + " ";
                        temp = st.nextToken();
                    }
                    tempNode.setRight(buildTree(s));
                    num.add(tempNode);
                }
            }
        }
        //表示式處理完之後,當符號組有符號,則把符號組從後彈出,作為根,數字組的最後一個元素作為右孩子,倒數第二個元素作為左孩子,依次迴圈直至符號組的符號全部彈出,最後得出的結果放入數字組中。
        int i = fuhao.size();
        while (i > 0) {
            BinaryTreeNode<T> root = new BinaryTreeNode(fuhao.remove(fuhao.size() - 1));
            root.setRight(num.remove(num.size() - 1));
            root.setLeft(num.remove(num.size() - 1));
            num.add(root);
            i--;
        }
        //返回數字組索引值為0的位置就是樹存在的位置。
        return num.get(0);
    }
  • 測試結果:

  • 5.實驗五
  • 關鍵程式碼:

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

        if (isEmpty())
            throw new EmptyCollectionException("LinkedBinarySearchTree");
        else {
            if (root.right == null) {
                result = root.element;
                root = root.right;
            } 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;
    }
public T findMin() throws EmptyCollectionException {
        T result = null;

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

        }
        return result;
    }
public T findMax() throws EmptyCollectionException {
        T result = null;

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

            modCount--;
        }
        return result;
    }
}
  • 測試結果:

  • 實驗六
  • 紅黑樹的特性:
  • (1)每個節點或者是黑色,或者是紅色。
  • (2)根節點是黑色。
  • (3)每個葉子節點(NIL)是黑色。
  • [注意:這裡葉子節點,是指為空(NIL或NULL)的葉子節點!]
  • (4)如果一個節點是紅色的,則它的子節點必須是黑色的。
  • (5)從一個節點到該節點的子孫節點的所有路徑上包含相同數目的黑節點。
  • 注意:
  • (01) 特性(3)中的葉子節點,是隻為空(NIL或null)的節點。
  • (02) 特性(5),確保沒有一條路徑會比其他路徑長出倆倍。因而,紅黑樹是相對是接近平衡的二叉樹。
  • TreeMap類:TreeMap還有一個性質,就是他的左子樹比他小,右子樹比他大,這裡的比較是按照key排序的。存放的時候如果key一樣就把他替換了。
  • 程式碼分析:

//結點類
static final class Entry<K,V> implements Map.Entry<K,V> {
        K key;
        V value;
        Entry<K,V> left;
        Entry<K,V> right;
        Entry<K,V> parent;
        boolean color = BLACK;

        /**
         * Make a new cell with given key, value, and parent, and with
         * {@code null} child links, and BLACK color.
         */
        Entry(K key, V value, Entry<K,V> parent) {
            this.key = key;
            this.value = value;
            this.parent = parent;
        }

        /**
         * Returns the key.
         *
         * @return the key
         */
        public K getKey() {
            return key;
        }

        /**
         * Returns the value associated with the key.
         *
         * @return the value associated with the key
         */
        public V getValue() {
            return value;
        }

        /**
         * Replaces the value currently associated with the key with the given
         * value.
         *
         * @return the value associated with the key before this method was
         *         called
         */
        public V setValue(V value) {
            V oldValue = this.value;
            this.value = value;
            return oldValue;
        }
        
        //比較兩個結點的key值是否相等
        public boolean equals(Object o) {
            if (!(o instanceof Map.Entry))
                return false;
            Map.Entry<?,?> e = (Map.Entry<?,?>)o;

            return valEquals(key,e.getKey()) && valEquals(value,e.getValue());
        }

        //hashCode要不是keyHash要不是valueHash,只能二者選一。
        public int hashCode() {
            int keyHash = (key==null ? 0 : key.hashCode());
            int valueHash = (value==null ? 0 : value.hashCode());
            return keyHash ^ valueHash;
        }

        public String toString() {
            return key + "=" + value;
        }
    }
//得到最小值也就是最左下角的數
final Entry<K,V> getFirstEntry() {
        Entry<K,V> p = root;
        if (p != null)
            while (p.left != null)
                p = p.left;
        return p;
    }
//得到最大值也就是最右下角的數
    final Entry<K,V> getLastEntry() {
        Entry<K,V> p = root;
        if (p != null)
            while (p.right != null)
                p = p.right;
        return p;
    }
public V put(K key, V value)   
{   
    // 先以 t 儲存連結串列的 root 節點  
    Entry<K,V> t = root;   
    // 如果 t==null,表明是一個空連結串列,即該 TreeMap 裡沒有任何 Entry   
    if (t == null)   
    {   
        // 將新的 key-value 建立一個 Entry,並將該 Entry 作為 root   
        root = new Entry<K,V>(key, value, null);   
        // 設定該 Map 集合的 size 為 1,代表包含一個 Entry   
        size = 1;   
        // 記錄修改次數為 1   
        modCount++;   
        return null;   
    }   
    int cmp;   
    Entry<K,V> parent;   
    Comparator<? super K> cpr = comparator;   
    // 如果比較器 cpr 不為 null,即表明採用定製排序  
    if (cpr != null)   
    {   
        do {   
            // 使用 parent 上次迴圈後的 t 所引用的 Entry   
            parent = t;   
            // 拿新插入 key 和 t 的 key 進行比較  
            cmp = cpr.compare(key, t.key);   
            // 如果新插入的 key 小於 t 的 key,t 等於 t 的左邊節點  
            if (cmp < 0)   
                t = t.left;   
            // 如果新插入的 key 大於 t 的 key,t 等於 t 的右邊節點  
            else if (cmp > 0)   
                t = t.right;   
            // 如果兩個 key 相等,新的 value 覆蓋原有的 value,  
            // 並返回原有的 value   
            else   
                return t.setValue(value);   
        } while (t != null);   
    }   
    else   
    {   
        if (key == null)   
            throw new NullPointerException();   
        Comparable<? super K> k = (Comparable<? super K>) key;   
        do {   
            // 使用 parent 上次迴圈後的 t 所引用的 Entry   
            parent = t;   
            // 拿新插入 key 和 t 的 key 進行比較  
            cmp = k.compareTo(t.key);   
            // 如果新插入的 key 小於 t 的 key,t 等於 t 的左邊節點  
            if (cmp < 0)   
                t = t.left;   
            // 如果新插入的 key 大於 t 的 key,t 等於 t 的右邊節點  
            else if (cmp > 0)   
                t = t.right;   
            // 如果兩個 key 相等,新的 value 覆蓋原有的 value,  
            // 並返回原有的 value   
            else   
                return t.setValue(value);   
        } while (t != null);   
    }   
    // 將新插入的節點作為 parent 節點的子節點  
    Entry<K,V> e = new Entry<K,V>(key, value, parent);   
    // 如果新插入 key 小於 parent 的 key,則 e 作為 parent 的左子節點  
    if (cmp < 0)   
        parent.left = e;   
    // 如果新插入 key 小於 parent 的 key,則 e 作為 parent 的右子節點  
    else   
        parent.right = e;   
    // 修復紅黑樹  
    fixAfterInsertion(e);                               // ①  
    size++;   
    modCount++;   
    return null;   
}
private void deleteEntry(Entry<K,V> p) 
 { 
    modCount++; 
    size--; 
    // 如果被刪除節點的左子樹、右子樹都不為空
    if (p.left != null && p.right != null) 
    { 
        // 用 p 節點的中序後繼節點代替 p 節點
        Entry<K,V> s = successor (p); 
        p.key = s.key; 
        p.value = s.value; 
        p = s; 
    } 
    // 如果 p 節點的左節點存在,replacement 代表左節點;否則代表右節點。
    Entry<K,V> replacement = (p.left != null ? p.left : p.right); 
    if (replacement != null) 
    { 
        replacement.parent = p.parent; 
        // 如果 p 沒有父節點,則 replacemment 變成父節點
        if (p.parent == null) 
            root = replacement; 
        // 如果 p 節點是其父節點的左子節點
        else if (p == p.parent.left) 
            p.parent.left  = replacement; 
        // 如果 p 節點是其父節點的右子節點
        else 
            p.parent.right = replacement; 
        p.left = p.right = p.parent = null; 
        // 修復紅黑樹
        if (p.color == BLACK) 
            fixAfterDeletion(replacement);       // ①
    } 
    // 如果 p 節點沒有父節點
    else if (p.parent == null) 
    { 
        root = null; 
    } 
    else 
    { 
        if (p.color == BLACK) 
            // 修復紅黑樹
            fixAfterDeletion(p);                 // ②
        if (p.parent != null) 
        { 
            // 如果 p 是其父節點的左子節點
            if (p == p.parent.left) 
                p.parent.left = null; 
            // 如果 p 是其父節點的右子節點
            else if (p == p.parent.right) 
                p.parent.right = null; 
            p.parent = null; 
        } 
    } 
 }
final Entry<K,V> getEntry(Object key) 
 { 
    // 如果 comparator 不為 null,表明程式採用定製排序
    if (comparator != null) 
        // 呼叫 getEntryUsingComparator 方法來取出對應的 key 
        return getEntryUsingComparator(key); 
    // 如果 key 形參的值為 null,丟擲 NullPointerException 異常
    if (key == null) 
        throw new NullPointerException(); 
    // 將 key 強制型別轉換為 Comparable 例項
    Comparable<? super K> k = (Comparable<? super K>) key; 
    // 從樹的根節點開始
    Entry<K,V> p = root; 
    while (p != null) 
    { 
        // 拿 key 與當前節點的 key 進行比較
        int cmp = k.compareTo(p.key); 
        // 如果 key 小於當前節點的 key,向“左子樹”搜尋
        if (cmp < 0) 
            p = p.left; 
        // 如果 key 大於當前節點的 key,向“右子樹”搜尋
        else if (cmp > 0) 
            p = p.right; 
        // 不大於、不小於,就是找到了目標 Entry 
        else 
            return p; 
    } 
    return null; 
 }
final Entry<K,V> getEntryUsingComparator(Object key) 
 { 
    K k = (K) key; 
    // 獲取該 TreeMap 的 comparator 
    Comparator<? super K> cpr = comparator; 
    if (cpr != null) 
    { 
        // 從根節點開始
        Entry<K,V> p = root; 
        while (p != null) 
        { 
            // 拿 key 與當前節點的 key 進行比較
            int cmp = cpr.compare(k, p.key); 
            // 如果 key 小於當前節點的 key,向“左子樹”搜尋
            if (cmp < 0) 
                p = p.left; 
            // 如果 key 大於當前節點的 key,向“右子樹”搜尋
            else if (cmp > 0) 
                p = p.right; 
            // 不大於、不小於,就是找到了目標 Entry 
            else 
                return p; 
        } 
    } 
    return null; 
 }
  • HashMap類:
//通過hash計算插入的項的槽位,如果有是一樣的key則根據設定的引數是否執行覆蓋,如果相應槽位空的話直接插入,如果對應的槽位有項則判斷是紅黑樹結構還是連結串列結構的槽位,連結串列的話則順著連結串列尋找如果找到一樣的key則根據引數選擇覆蓋,沒有找到則連結在連結串列最後面,連結串列項的數目大於8則對其進行樹化,如果是紅黑樹結構則按照樹的新增方式新增項。
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict)
    {
        Node<K, V>[] tab;
        Node<K, V> p;
        int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else
        {
            Node<K, V> e;
            K k;
            if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                // 如果當前的bucket裡面已經是紅黑樹的話,執行紅黑樹的新增操作
                e = ((TreeNode<K, V>) p).putTreeVal(this, tab, hash, key, value);
            else
            {
                for (int binCount = 0;; ++binCount)
                {
                    if ((e = p.next) == null)
                    {
                        p.next = newNode(hash, key, value, null);
                        // TREEIFY_THRESHOLD = 8,判斷如果當前bucket的位置連結串列長度大於8的話就將此連結串列變成紅黑樹。
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null)
            { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }
//第一次迴圈會將連結串列中的首節點作為紅黑樹的根,而後的迴圈會將連結串列中的的項通過比較hash值然後連線到相應樹節點的左邊或者右邊,插入可能會破壞樹的結構所以接著執行balanceInsertion。
final void treeifyBin(Node<K, V>[] tab, int hash)
    {
        int n, index;
        Node<K, V> e;
        if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
            // resize()方法這裡不過多介紹,感興趣的可以去看上面的連結。
            resize();
        // 通過hash求出bucket的位置。
        else if ((e = tab[index = (n - 1) & hash]) != null)
        {
            TreeNode<K, V> hd = null, tl = null;
            do
            {
                // 將每個節點包裝成TreeNode。
                TreeNode<K, V> p = replacementTreeNode(e, null);
                if (tl == null)
                    hd = p;
                else
                {
                    // 將所有TreeNode連線在一起此時只是連結串列結構。
                    p.prev = tl;
                    tl.next = p;
                }
                tl = p;
            } while ((e = e.next) != null);
            if ((tab[index] = hd) != null)
                // 對TreeNode連結串列進行樹化。
                hd.treeify(tab);
        }
    }
final void treeify(Node<K, V>[] tab)
    {
        TreeNode<K, V> root = null;
        // 以for迴圈的方式遍歷剛才我們建立的連結串列。
        for (TreeNode<K, V> x = this, next; x != null; x = next)
        {
            // next向前推進。
            next = (TreeNode<K, V>) x.next;
            x.left = x.right = null;
            // 為樹根節點賦值。
            if (root == null)
            {
                x.parent = null;
                x.red = false;
                root = x;
            } else
            {
                // x即為當前訪問連結串列中的項。
                K k = x.key;
                int h = x.hash;
                Class<?> kc = null;
                // 此時紅黑樹已經有了根節點,上面獲取了當前加入紅黑樹的項的key和hash值進入核心迴圈。
                // 這裡從root開始,是以一個自頂向下的方式遍歷新增。
                // for迴圈沒有控制條件,由程式碼內break跳出迴圈。
                for (TreeNode<K, V> p = root;;)
                {
                    // dir:directory,比較新增項與當前樹中訪問節點的hash值判斷加入項的路徑,-1為左子樹,+1為右子樹。
                    // ph:parent hash。
                    int dir, ph;
                    K pk = p.key;
                    if ((ph = p.hash) > h)
                        dir = -1;
                    else if (ph < h)
                        dir = 1;
                    else if ((kc == null && (kc = comparableClassFor(k)) == null)
                            || (dir = compareComparables(kc, k, pk)) == 0)
                        dir = tieBreakOrder(k, pk);

                    // xp:x parent。
                    TreeNode<K, V> xp = p;
                    // 找到符合x新增條件的節點。
                    if ((p = (dir <= 0) ? p.left : p.right) == null)
                    {
                        x.parent = xp;
                        // 如果xp的hash值大於x的hash值,將x新增在xp的左邊。
                        if (dir <= 0)
                            xp.left = x;
                        // 反之新增在xp的右邊。
                        else
                            xp.right = x;
                        // 維護新增後紅黑樹的紅黑結構。
                        root = balanceInsertion(root, x);
                        
                        // 跳出迴圈當前連結串列中的項成功的新增到了紅黑樹中。
                        break;
                    }
                }
            }
        }
        // Ensures that the given root is the first node of its bin,自己翻譯一下。
        moveRootToFront(tab, root);
    }
static <K, V> TreeNode<K, V> balanceInsertion(TreeNode<K, V> root, TreeNode<K, V> x)
    {
        // 正如開頭所說,新加入樹節點預設都是紅色的,不會破壞樹的結構。
        x.red = true;
        // 這些變數名不是作者隨便定義的都是有意義的。
        // xp:x parent,代表x的父節點。
        // xpp:x parent parent,代表x的祖父節點
        // xppl:x parent parent left,代表x的祖父的左節點。
        // xppr:x parent parent right,代表x的祖父的右節點。
        for (TreeNode<K, V> xp, xpp, xppl, xppr;;)
        {
            // 如果x的父節點為null說明只有一個節點,該節點為根節點,根節點為黑色,red = false。
            if ((xp = x.parent) == null)
            {
                x.red = false;
                return x;
            } 
            // 進入else說明不是根節點。
            // 如果父節點是黑色,那麼大吉大利(今晚吃雞),紅色的x節點可以直接新增到黑色節點後面,返回根就行了不需要任何多餘的操作。
            // 如果父節點是紅色的,但祖父節點為空的話也可以直接返回根此時父節點就是根節點,因為根必須是黑色的,新增在後面沒有任何問題。
            else if (!xp.red || (xpp = xp.parent) == null)
                return root;
            
            // 一旦我們進入到這裡就說明了兩件是情
            // 1.x的父節點xp是紅色的,這樣就遇到兩個紅色節點相連的問題,所以必須經過旋轉變換。
            // 2.x的祖父節點xpp不為空。
            
            // 判斷如果父節點是否是祖父節點的左節點
            if (xp == (xppl = xpp.left))
            {
                // 父節點xp是祖父的左節點xppr
                // 判斷祖父節點的右節點不為空並且是否是紅色的
                // 此時xpp的左右節點都是紅的,所以直接進行上面所說的第三種變換,將兩個子節點變成黑色,將xpp變成紅色,然後將紅色節點x順利的新增到了xp的後面。
                // 這裡大家有疑問為什麼將x = xpp?
                // 這是由於將xpp變成紅色以後可能與xpp的父節點發生兩個相連紅色節點的衝突,這就又構成了第二種旋轉變換,所以必須從底向上的進行變換,直到根。
                // 所以令x = xpp,然後進行下下一層迴圈,接著往上走。
                if ((xppr = xpp.right) != null && xppr.red)
                {
                    xppr.red = false;
                    xp.red = false;
                    xpp.red = true;
                    x = xpp;
                }
                // 進入到這個else裡面說明。
                // 父節點xp是祖父的左節點xppr。
                // 祖父節點xpp的右節點xppr是黑色節點或者為空,預設規定空節點也是黑色的。
                // 下面要判斷x是xp的左節點還是右節點。
                else
                {
                    // x是xp的右節點,此時的結構是:xpp左->xp右->x。這明顯是第二中變換需要進行兩次旋轉,這裡先進行一次旋轉。
                    // 下面是第一次旋轉。
                    if (x == xp.right)
                    {
                        root = rotateLeft(root, x = xp);
                        xpp = (xp = x.parent) == null ? null : xp.parent;
                    }
                    // 針對本身就是xpp左->xp左->x的結構或者由於上面的旋轉造成的這種結構進行一次旋轉。
                    if (xp != null)
                    {
                        xp.red = false;
                        if (xpp != null)
                        {
                            xpp.red = true;
                            root = rotateRight(root, xpp);
                        }
                    }
                }
            } 
            // 這裡的分析方式和前面的相對稱只不過全部在右測不再重複分析。
            else
            {
                if (xppl != null && xppl.red)
                {
                    xppl.red = false;
                    xp.red = false;
                    xpp.red = true;
                    x = xpp;
                } else
                {
                    if (x == xp.left)
                    {
                        root = rotateRight(root, x = xp);
                        xpp = (xp = x.parent) == null ? null : xp.parent;
                    }
                    if (xp != null)
                    {
                        xp.red = false;
                        if (xpp != null)
                        {
                            xpp.red = true;
                            root = rotateLeft(root, xpp);
                        }
                    }
                }
            }
        }
    }

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

  • 問題1:實驗三中

  • 問題1解決方案:在輸入的文字中多一行空行。因為nextLine會多讀一個換行符,如果沒有那一行空行,沒有東西讀就會報錯。

  • 問題2:實驗四中索引值應該是個數-1,但是如果我變size()-1會出現越界的錯誤情況

  • 問題2解決方案:這裡的size()並不是num的size(),而是該類中自己的size(),應改為下圖就可以了。

  • 問題3:實驗六中TreeMap類是什麼?
  • 問題3解決方案:
    TreeMap是一個有序的key-value集合,它是通過紅黑樹實現的。
    TreeMap 繼承於AbstractMap,所以它是一個Map,即一個key-value集合。
    TreeMap 實現了NavigableMap介面,意味著它支援一系列的導航方法。比如返回有序的key集合。
    TreeMap 實現了Cloneable介面,意味著它能被克隆。
    TreeMap 實現了java.io.Serializable介面,意味著它支援序列化。

  • 問題4:實驗六中下列程式碼中的^是什麼意思。
public int hashCode() {
            int keyHash = (key==null ? 0 : key.hashCode());
            int valueHash = (value==null ? 0 : value.hashCode());
            return keyHash ^ valueHash;
        }
  • 問題4解決方案:是進行異或運算的意思。使異或表示式為true的條件是:有且僅有一個操作為true;其他情況都為false;

  • 問題5:下列程式碼中的>>是什麼意思。

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
  • 問題5解決方案:表示帶符號右移,按二進位制形式把所有的數字向右移動對應的位數,低位移出(捨棄),高位的空位補符號位,即正數補零,負數補1

其他(感悟、思考等)

  • 這次的實驗是關於樹的實驗,二叉查詢樹,決策樹這些都是在做pp就實現過的,最難的就是用表示式樹中綴轉字尾的過程,原理是需要把中綴先變成樹,然後進行後序遍歷就可以得到字尾表示式,先序遍歷得到字首表示式(注意有括號的情況)。實驗六的分析,因為原始碼太多了不知道關鍵,所以查了相關資料,看了相關部落格,簡單的寫了一下。程式碼的解釋我都作為註釋寫在程式碼塊內了。
    (劃重點!!!我覺得我實驗四的建立中綴表示式樹寫的非常完美,一點bug都沒有,加幾個括號都是對的!)

參考資料