20172319 實驗二《樹》實驗報告

20172319 2018.11.04-11.12

實驗二《樹》 實驗報告

課程名稱:《程序設計與數據結構》  
學生班級:1723班  
學生姓名:唐才銘  
學生學號:20172319 
實驗教師:王志強老師
課程助教:張師瑜學姐、張之睿學長
實驗時間:2018年11月04日——2018年11月12日
必修/選修:必修

目錄


實驗內容

  1. 實驗二-1-實現二叉樹: 完成鏈樹LinkedBinaryTree的實現。
  2. 實驗二 樹-2-中序先序序列構造二叉樹: 基於LinkedBinaryTree,實現基於(中序,先序)序列構造惟一一棵二㕚樹的功能
  3. 實驗二 樹-3-決策樹: 本身設計並實現一顆決策樹
  4. 實驗二 樹-4-表達式樹: 輸入中綴表達式,使用樹將中綴表達式轉換爲後綴表達式,並輸出後綴表達式和計算結果
  5. 實驗二 樹-5-二叉查找樹: 完成PP11.3
  6. 實驗二 樹-6-紅黑樹分析: 參考http://www.cnblogs.com/rocedu/p/7483915.html對Java中的紅黑樹(TreeMap,HashMap)進行源碼分析,並在實驗報告中體現分析結果

返回目錄html


實驗要求

  1. 完成藍墨雲上與實驗二《樹》相關的活動,及時提交代碼運行截圖和碼雲Git連接,截圖要有學號水印,不然會扣分。
  2. 完成實驗、撰寫實驗報告,實驗報告以博客方式發表在博客園,注意實驗報告重點是運行結果,遇到的問題(工具查找,安裝,使用,程序的編輯,調試,運行等)、解決辦法(空洞的方法如「查網絡」、「問同窗」、「看書」等一概得0分)以及分析(從中能夠獲得什麼啓示,有什麼收穫,教訓等)。報告能夠參考範飛龍老師的指導
  3. 嚴禁抄襲,有該行爲者實驗成績歸零,並附加其餘懲罰措施。

返回目錄java


實驗步驟

  1. 實驗二-1-實現二叉樹:
    參考教材p212,完成鏈樹LinkedBinaryTree的實現(getRight,contains,toString,preorder,postorder)
    用JUnit或本身編寫驅動類對本身實現的LinkedBinaryTree進行測試,提交測試代碼運行截圖,要全屏,包含本身的學號信息
    課下把代碼推送到代碼託管平臺
  2. 實驗二 樹-2-中序先序序列構造二叉樹:
    基於LinkedBinaryTree,實現基於(中序,先序)序列構造惟一一棵二㕚樹的功能,好比給出中序HDIBEMJNAFCKGL和後序ABDHIEJMNCFGKL,構造出附圖中的樹
    用JUnit或本身編寫驅動類對本身實現的功能進行測試,提交測試代碼運行截圖,要全屏,包含本身的學號信息
    課下把代碼推送到代碼託管平臺
  3. 實驗二 樹-3-決策樹:
    本身設計並實現一顆決策樹
    提交測試代碼運行截圖,要全屏,包含本身的學號信息
    課下把代碼推送到代碼託管平臺
  4. 實驗二 樹-4-表達式樹:
    輸入中綴表達式,使用樹將中綴表達式轉換爲後綴表達式,並輸出後綴表達式和計算結果(若是沒有用樹,則爲0分)
    提交測試代碼運行截圖,要全屏,包含本身的學號信息
    課下把代碼推送到代碼託管平臺
  5. 實驗二 樹-5-二叉查找樹:
    完成PP11.3
    提交測試代碼運行截圖,要全屏,包含本身的學號信息
    課下把代碼推送到代碼託管平臺
  6. 實驗二 樹-5-二叉查找樹:
    參考http://www.cnblogs.com/rocedu/p/7483915.html對Java中的紅黑樹(TreeMap,HashMap)進行源碼分析,並在實驗報告中體現分析結果。
    (C:\Program Files\Java\jdk-11.0.1\lib\src\java.base\java\util)

前期準備:

  1. 預先下載安裝好IDEA 。

需求分析:

  1. 須要掌握二叉查找樹的相關知識;
  2. 須要掌握當任意給出兩個序能構建出惟一一棵二叉樹;
  3. 須要理解表達式樹的實現;
  4. 須要理解決策樹的實現。

返回目錄node


代碼實現及解釋

本次實驗一共分爲六個提交點:

  • 實驗二-1-實現二叉樹:
  • 要實現的方法有:getRight;contains;toString;preorder;postorder;
  • getRight是獲取右子樹,但這裏並無準確說明針對哪一種結點的操做,爲了程序的完整性,便實現了可調用如何結點的右子樹。
  • getRight具體代碼以下:
// 獲取某一結點的右子樹
    public  String getNodeRightTree(T Elemnet){
        String result;
        BinaryTreeNode node = new BinaryTreeNode(Elemnet);
        LinkedBinaryTree linkedBinaryTree = new LinkedBinaryTree();
        linkedBinaryTree.root = root;
        if (root==null){
            return "";
        }
        else {
            if (root != null && root.left == null && root.right == null) {
                linkedBinaryTree.root = root;
                result = linkedBinaryTree.printTree();
                return result;
            }
            node = findNode(Elemnet,root);
            if (node.right!=null){
                linkedBinaryTree.root = node.right;
                result = linkedBinaryTree.printTree();
            }
            else {
                result = "該結點無右子樹";
            }
            return result;
        }
    }
  • 實現結果截圖:
    git

  • contains判斷樹中是否包含某一元素,這裏使用了原有的find方法,使得實現更加便捷。
  • contains具體代碼以下:算法

@Override
    public boolean contains(T targetElement)
    {
        // To be completed as a Programming seatwork
        T temp;
        boolean found = false;

        try {
            temp = find(targetElement);
            found = true;
        }
        catch (Exception ElementNotFoundExecption){
            found = false;
        }
        return found;
    }
  • find的具體代碼以下:
@Override
    public T find(T targetElement) throws ElementNotFoundException
    {
        BinaryTreeNode<T> current = findNode(targetElement, root);

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

        return (current.getElement());
    }
  • 實現結果截圖:
    express

  • toString是輸出樹中元素,本來是直接經過一個遍歷算法來輸出,但爲了實驗的直觀和便於操做,借用了表達式中的printTree
  • toString具體代碼:此處用了前序遍從來輸出數組

@Override
    public String toString()
    {
        // To be completed as a Programming seatwork
        ArrayUnorderedList<T> tempList = new ArrayUnorderedList<T>();
        preOrder(root,tempList);

        return tempList.toString();
    }
  • printTree具體代碼:
public String printTree()
    {
        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)
            {
                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;
    }
  • 實現結果截圖:
    網絡

  • preorder,postorder,書上只給了inorder的實現,只需更改遍歷結點的順序便可實現:
  • preorderpostorder具體代碼以下:數據結構

private void preOrder(BinaryTreeNode<T> node,
                            ArrayUnorderedList<T> tempList)
    {
        // To be completed as a Programming seatwork
        if (node!=null){
            tempList.addToRear(node.element);
            preOrder(node.left,tempList);
            preOrder(node.right,tempList);
        }

    }
 private void postOrder(BinaryTreeNode<T> node,
                             ArrayUnorderedList<T> tempList)
    {
        // To be completed as a Programming seatwork
        if (node != null)
        {
            postOrder(node.getLeft(), tempList);
            postOrder(node.getRight(), tempList);
            tempList.addToRear(node.getElement());
        }
    }
  • 實現結果截圖:
    app

  • 實驗二 樹-2-中序先序序列構造二叉樹:
  • 先整明白如何經過給定的兩個不一樣遍從來構建一棵惟一的二叉樹,在一輪遞歸中用兩個指針分別指向前序和中序中的元素,遍歷前序和中序,當兩個指針指向的元素同樣時,結束該輪次,記錄下一次遍歷前序的起始位置,開始下輪遍歷。
  • 具體代碼以下:

//  前序中序構建二叉樹
    public BinaryTreeNode BuildTree(char[] preorder, char[] inorder) {
        return BuildLinkedBinaryTree(preorder, inorder, 0, inorder.length - 1, inorder.length);
    }

    /**
     * @param preorder 前序
     * @param inorder  中序
     * @param Start 起始位置
     * @param End 終止位置
     * @param length 結點個數
     */
    public BinaryTreeNode BuildLinkedBinaryTree(char[] preorder,char[] inorder,int Start, int End,int length) {
        if (preorder==null||preorder.length == 0 || inorder == null
                || inorder.length == 0 || length <= 0){
            return null;
        }
        BinaryTreeNode binaryTreeNode;
        binaryTreeNode = new BinaryTreeNode(preorder[Start]);
        if (length==1){
            return binaryTreeNode;
        }
        int flag=0;
        while (flag < length){
            if (preorder[Start] == inorder[End - flag]){
                break;
            }
            flag++;
        }
        binaryTreeNode.left = BuildLinkedBinaryTree(preorder, inorder,Start + 1,  End - flag - 1, length - 1 - flag);
        binaryTreeNode.right = BuildLinkedBinaryTree(preorder, inorder,Start + length - flag,  End, flag );
        return binaryTreeNode;
    }
  • 實現結果截圖:

  • 實驗二 樹-3-決策樹:

  • 本身想好所決策須要的問題,修改先前文件的內容便可:
  • 實現結果截圖:

  • 實驗二 樹-4-表達式樹

  • 實現思想:
  • 數字是葉子節點,操做符爲根節點。
  • 先用中綴表達式構建成樹,以後後序遍歷可得其後綴表達式;
  • 構樹過程:
  • 從表達式的最後一位元素往前掃描,當遇到最後計算的運算符(+或-)時,做爲當前根節點,運算符左側表達式做爲左節點,右側表達式做爲右節點,而後遞歸處理。
  • 具體代碼以下:

private boolean priority(String[] operator,int size){
        // 先對有+ - 的式子進行拆分
        boolean found1 = true,found2=true ,found = true;
        for (int i = 0 ; i< size;i++) {
            if (operator[i].equals("+")) {
                found1 = false;
            }
        }
        for (int i = 0 ; i< size;i++) {
            if (operator[i].equals("-")) {
                found2 = false;
                }
            }
        if (found1 == false||found2==false){
            found = false;
        }
        return found;
    }
    public BinaryTreeNode Build_Expression_Tree(String[] expression, int size){
        // 帶括號的式子暫未實現(遞歸出現的問題太多了(╬ ̄皿 ̄))
        BinaryTreeNode binaryTreeNode = new BinaryTreeNode(null);
        int length = size; //  元素個數
        String[] expression_Left_Tree = null; //  左子樹
        String[] expression_Right_Tree = null; //  右子樹
        for (int i = length - 1; i > 0; i--){  //  遍歷數組元素
            String temp = expression[i];
                if (temp.equals("+") || temp.equals("-")) {  // 若遇到+ - ,則對數組進行此元素左右分割
                    binaryTreeNode = new BinaryTreeNode(temp);
                    expression_Left_Tree = new String[i];
                    expression_Right_Tree = new String[length - i - 1];
                    for (int j = 0; j < expression_Left_Tree.length; j++) {  //  拆分結點左邊數組(左子樹)
                        expression_Left_Tree[j] = expression[j];
                    }
                    for (int k = 0; k < expression_Right_Tree.length; k++) {//  拆分結點右邊數組(右子樹)
                        expression_Right_Tree[k] = expression[i + k + 1];
                    }
                    if (expression_Left_Tree.length == 1) {  // 若結點左子樹數組長度爲1
                        binaryTreeNode.setLeft(new BinaryTreeNode(expression_Left_Tree[0]));// 輸出數組元素並創建左孩子
                        if (expression_Right_Tree.length!=1){ // 對該結點右端進行建樹,後面狀況大體同樣不作多餘複述
                            binaryTreeNode.setRight(Build_Expression_Tree(expression_Right_Tree,expression_Right_Tree.length));
                        }
                        if (expression_Right_Tree.length==1){
                            binaryTreeNode.setRight(new BinaryTreeNode(expression_Right_Tree[0]));
                        }
                        return binaryTreeNode;

                    }


                    if (expression_Right_Tree.length == 1) {
                        binaryTreeNode.setRight(new BinaryTreeNode(expression_Right_Tree[0]));
                        if (expression_Left_Tree.length!=1){
                            binaryTreeNode.setLeft(Build_Expression_Tree(expression_Left_Tree,expression_Left_Tree.length));
                        }
                        if (expression_Left_Tree.length==1){
                            binaryTreeNode.setLeft(new BinaryTreeNode(expression_Left_Tree[0]));
                        }
                        return binaryTreeNode;
                    }
                    break;
                }


            else if (priority(expression,expression.length)!=false){  // 優先級判斷,此刻數組裏已無加減號
                if (temp.equals("*") || temp.equals("/")) {   // 若遇到+ - ,則對數組進行此元素左右分割
                    binaryTreeNode = new BinaryTreeNode(temp);
                    expression_Left_Tree = new String[i];
                    expression_Right_Tree = new String[length - i - 1];
                    for (int j = 0; j < expression_Left_Tree.length; j++) {
                        expression_Left_Tree[j] = expression[j];
                    }
                    for (int k = 0; k < expression_Right_Tree.length; k++) {
                        expression_Right_Tree[k] = expression[i + k + 1];
                    }
                    if (expression_Left_Tree.length == 1) {
                        binaryTreeNode.setLeft(new BinaryTreeNode(expression_Left_Tree[0]));
                        if (expression_Right_Tree.length!=1){
                            binaryTreeNode.setRight(Build_Expression_Tree(expression_Right_Tree,expression_Right_Tree.length));
                        }
                        if (expression_Right_Tree.length==1){
                            binaryTreeNode.setRight(new BinaryTreeNode(expression_Right_Tree[0]));
                        }
                        return binaryTreeNode;
                    }
                    if (expression_Right_Tree.length == 1) {
                        binaryTreeNode.setRight(new BinaryTreeNode(expression_Right_Tree[0]));
                        if (expression_Left_Tree.length!=1){
                            binaryTreeNode.setLeft(Build_Expression_Tree(expression_Left_Tree,expression_Left_Tree.length));
                        }
                        if (expression_Left_Tree.length==1){
                            binaryTreeNode.setLeft(new BinaryTreeNode(expression_Left_Tree[0]));
                        }
                        return binaryTreeNode;
                    }
                    break;
                }
            }
        }
        binaryTreeNode.setLeft(Build_Expression_Tree(expression_Left_Tree,expression_Left_Tree.length));
        binaryTreeNode.setRight(Build_Expression_Tree(expression_Right_Tree,expression_Right_Tree.length));
        return binaryTreeNode;
    }
  • 運行結果截圖:

  • 實驗二 樹-5-二叉查找樹
  • 完成PP11.3:實現removeMin;findMin;findMax操做:
  • 具體代碼以下:
@Override
    public T removeMin() 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;
                parent.left = current.right;
            }

            modCount--;
        }
 
        return result;
    }


@Override
    public T findMin() throws EmptyCollectionException
    {
        // To be completed as a Programming Project
        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;
                parent.left = current;
            }
        }
        return result;
    }

@Override
    public T findMax() throws EmptyCollectionException
    {
        // To be completed as a Programming Project
        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;
            }
        }
        return result;
    }
  • 實現結果截圖:

  • 實驗二 樹-6-紅黑樹分析
  • 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 class TreeMap<K,V>
    extends AbstractMap<K,V>
    implements NavigableMap<K,V>, Cloneable, java.io.Serializable
{
    // 比較器對象
    private final Comparator<? super K> comparator;

    // 根節點
    private transient Entry<K,V> root;

    // 集合大小
    private transient int size = 0;

    // 樹結構被修改的次數
    private transient int modCount = 0;

    // 靜態內部類用來表示節點類型
    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; // 
    }
}
  • 2.類構造方法:
public TreeMap() {   // 1,無參構造方法
        comparator = null; // 默認比較機制
    }

    public TreeMap(Comparator<? super K> comparator) { // 2,自定義比較器的構造方法
        this.comparator = comparator;
    }

    public TreeMap(Map<? extends K, ? extends V> m) {  // 3,構造已知Map對象爲TreeMap
        comparator = null; // 默認比較機制
        putAll(m);
    }

    public TreeMap(SortedMap<K, ? extends V> m) { // 4,構造已知的SortedMap對象爲TreeMap
        comparator = m.comparator(); // 使用已知對象的構造器
        try {
            buildFromSorted(m.size(), m.entrySet().iterator(), null, null);
        } catch (java.io.IOException cannotHappen) {
        } catch (ClassNotFoundException cannotHappen) {
        }
    }
  • 3.紅黑樹:
  • (1) 結點顏色及其對應類:
// 紅黑樹的節點顏色--紅色
    private static final boolean RED   = false;
    // 紅黑樹的節點顏色--黑色
    private static final boolean BLACK = true;

    // 「紅黑樹的節點」對應的類。
    // 包含了 key(鍵)、value(值)、left(左孩子)、right(右孩子)、parent(父節點)、color(顏色)
    static final class Entry<K,V> implements Map.Entry<K,V> {
        // 鍵
        K key;
        // 值
        V value;
        // 左孩子
        Entry<K,V> left = null;
        // 右孩子
        Entry<K,V> right = null;
        // 父節點
        Entry<K,V> parent;
        // 當前節點顏色
        boolean color = BLACK;

        // 構造函數
        Entry(K key, V value, Entry<K,V> parent) {
            this.key = key;
            this.value = value;
            this.parent = parent;
        }

        // 返回「鍵」
        public K getKey() {
            return key;
        }

        // 返回「值」
        public V getValue() {
            return value;
        }

        // 更新「值」,返回舊的值
        public V setValue(V value) {
            V oldValue = this.value;
            this.value = value;
            return oldValue;
        }

        // 判斷兩個節點是否相等的函數,覆蓋equals()函數。
        // 若兩個節點的「key相等」而且「value相等」,則兩個節點相等
        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函數。
        public int hashCode() {
            int keyHash = (key==null ? 0 : key.hashCode());
            int valueHash = (value==null ? 0 : value.hashCode());
            return keyHash ^ valueHash;
        }

        // 覆蓋toString()函數。
        public String toString() {
            return key + "=" + value;
        }
    }
  • (2) 在樹中結點的共同操做:
// 返回「紅黑樹的第一個節點」
    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;
    }

    // 返回「節點t的後繼節點」
    static <K,V> TreeMap.Entry<K,V> successor(Entry<K,V> t) {
        if (t == null)
            return null;
        else if (t.right != null) {
            Entry<K,V> p = t.right;
            while (p.left != null)
                p = p.left;
            return p;
        } else {
            Entry<K,V> p = t.parent;
            Entry<K,V> ch = t;
            while (p != null && ch == p.right) {
                ch = p;
                p = p.parent;
            }
            return p;
        }
    }

    // 返回「節點t的前繼節點」
    static <K,V> Entry<K,V> predecessor(Entry<K,V> t) {
        if (t == null)
            return null;
        else if (t.left != null) {
            Entry<K,V> p = t.left;
            while (p.right != null)
                p = p.right;
            return p;
        } else {
            Entry<K,V> p = t.parent;
            Entry<K,V> ch = t;
            while (p != null && ch == p.left) {
                ch = p;
                p = p.parent;
            }
            return p;
        }
    }

    // 返回「節點p的顏色」
    // 根據「紅黑樹的特性」可知:空節點顏色是黑色。
    private static <K,V> boolean colorOf(Entry<K,V> p) {
        return (p == null ? BLACK : p.color);
    }

    // 返回「節點p的父節點」
    private static <K,V> Entry<K,V> parentOf(Entry<K,V> p) {
        return (p == null ? null: p.parent);
    }

    // 設置「節點p的顏色爲c」
    private static <K,V> void setColor(Entry<K,V> p, boolean c) {
        if (p != null)
        p.color = c;
    }

    // 設置「節點p的左孩子」
    private static <K,V> Entry<K,V> leftOf(Entry<K,V> p) {
        return (p == null) ? null: p.left;
    }

    // 設置「節點p的右孩子」
    private static <K,V> Entry<K,V> rightOf(Entry<K,V> p) {
        return (p == null) ? null: p.right;
    }
  • (3)結點的旋轉:
// 對節點p執行「左旋」操做
    private void rotateLeft(Entry<K,V> p) {
        if (p != null) {
            Entry<K,V> r = p.right;
            p.right = r.left;
            if (r.left != null)
                r.left.parent = p;
            r.parent = p.parent;
            if (p.parent == null)
                root = r;
            else if (p.parent.left == p)
                p.parent.left = r;
            else
                p.parent.right = r;
            r.left = p;
            p.parent = r;
        }
    }

    // 對節點p執行「右旋」操做
    private void rotateRight(Entry<K,V> p) {
        if (p != null) {
            Entry<K,V> l = p.left;
            p.left = l.right;
            if (l.right != null) l.right.parent = p;
            l.parent = p.parent;
            if (p.parent == null)
                root = l;
            else if (p.parent.right == p)
                p.parent.right = l;
            else p.parent.left = l;
            l.right = p;
            p.parent = l;
        }
    }
  • (4)結點的插入和刪除
// 插入以後的修正操做。
    // 目的是保證:紅黑樹插入節點以後,仍然是一顆紅黑樹
    private void fixAfterInsertion(Entry<K,V> x) {
        x.color = RED;

        while (x != null && x != root && x.parent.color == RED) {
            if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
                Entry<K,V> y = rightOf(parentOf(parentOf(x)));
                if (colorOf(y) == RED) {
                    setColor(parentOf(x), BLACK);
                    setColor(y, BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    x = parentOf(parentOf(x));
                } else {
                    if (x == rightOf(parentOf(x))) {
                        x = parentOf(x);
                        rotateLeft(x);
                    }
                    setColor(parentOf(x), BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    rotateRight(parentOf(parentOf(x)));
                }
            } else {
                Entry<K,V> y = leftOf(parentOf(parentOf(x)));
                if (colorOf(y) == RED) {
                    setColor(parentOf(x), BLACK);
                    setColor(y, BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    x = parentOf(parentOf(x));
                } else {
                    if (x == leftOf(parentOf(x))) {
                        x = parentOf(x);
                        rotateRight(x);
                    }
                    setColor(parentOf(x), BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    rotateLeft(parentOf(parentOf(x)));
                }
            }
        }
        root.color = BLACK;
    }

    // 刪除「紅黑樹的節點p」
    private void deleteEntry(Entry<K,V> p) {
        modCount++;
        size--;

        // If strictly internal, copy successor's element to p and then make p
        // point to successor.
        if (p.left != null && p.right != null) {
            Entry<K,V> s = successor (p);
            p.key = s.key;
            p.value = s.value;
            p = s;
        } // p has 2 children

        // Start fixup at replacement node, if it exists.
        Entry<K,V> replacement = (p.left != null ? p.left : p.right);

        if (replacement != null) {
            // Link replacement to parent
            replacement.parent = p.parent;
            if (p.parent == null)
                root = replacement;
            else if (p == p.parent.left)
                p.parent.left  = replacement;
            else
                p.parent.right = replacement;

            // Null out links so they are OK to use by fixAfterDeletion.
            p.left = p.right = p.parent = null;

            // Fix replacement
            if (p.color == BLACK)
                fixAfterDeletion(replacement);
        } else if (p.parent == null) { // return if we are the only node.
            root = null;
        } else { //  No children. Use self as phantom replacement and unlink.
            if (p.color == BLACK)
                fixAfterDeletion(p);

            if (p.parent != null) {
                if (p == p.parent.left)
                    p.parent.left = null;
                else if (p == p.parent.right)
                    p.parent.right = null;
                p.parent = null;
            }
        }
    }

    // 刪除以後的修正操做。
    // 目的是保證:紅黑樹刪除節點以後,仍然是一顆紅黑樹
    private void fixAfterDeletion(Entry<K,V> x) {
        while (x != root && colorOf(x) == BLACK) {
            if (x == leftOf(parentOf(x))) {
                Entry<K,V> sib = rightOf(parentOf(x));

                if (colorOf(sib) == RED) {
                    setColor(sib, BLACK);
                    setColor(parentOf(x), RED);
                    rotateLeft(parentOf(x));
                    sib = rightOf(parentOf(x));
                }

                if (colorOf(leftOf(sib))  == BLACK &&
                    colorOf(rightOf(sib)) == BLACK) {
                    setColor(sib, RED);
                    x = parentOf(x);
                } else {
                    if (colorOf(rightOf(sib)) == BLACK) {
                        setColor(leftOf(sib), BLACK);
                        setColor(sib, RED);
                        rotateRight(sib);
                        sib = rightOf(parentOf(x));
                    }
                    setColor(sib, colorOf(parentOf(x)));
                    setColor(parentOf(x), BLACK);
                    setColor(rightOf(sib), BLACK);
                    rotateLeft(parentOf(x));
                    x = root;
                }
            } else { // symmetric
                Entry<K,V> sib = leftOf(parentOf(x));

                if (colorOf(sib) == RED) {
                    setColor(sib, BLACK);
                    setColor(parentOf(x), RED);
                    rotateRight(parentOf(x));
                    sib = leftOf(parentOf(x));
                }

                if (colorOf(rightOf(sib)) == BLACK &&
                    colorOf(leftOf(sib)) == BLACK) {
                    setColor(sib, RED);
                    x = parentOf(x);
                } else {
                    if (colorOf(leftOf(sib)) == BLACK) {
                        setColor(rightOf(sib), BLACK);
                        setColor(sib, RED);
                        rotateLeft(sib);
                        sib = leftOf(parentOf(x));
                    }
                    setColor(sib, colorOf(parentOf(x)));
                    setColor(parentOf(x), BLACK);
                    setColor(leftOf(sib), BLACK);
                    rotateRight(parentOf(x));
                    x = root;
                }
            }
        }

        setColor(x, BLACK);
    }

  • 紅黑樹的性質:
  • 一、節點是紅色或黑色
    二、根節點是黑色
    三、全部的葉子(NIL空節點)是黑色的
    四、每一個紅色節點的兩個兒子均爲黑色,即不可能有連續的兩個紅色節點
    五、從任一節點到其葉子(NIL空節點)的路徑都包含相同數目的黑節點

  • put方法

// 將「key, value」添加到TreeMap中
    // 理解TreeMap的前提是掌握「紅黑樹」。
    // 若理解「紅黑樹中添加節點」的算法,則很容易理解put。
    public V put(K key, V value) {
        Entry<K,V> t = root;
        // 若紅黑樹爲空,則插入根節點
        if (t == null) {
        // TBD:
        // 5045147: (coll) Adding null to an empty TreeSet should
        // throw NullPointerException
        //
        // compare(key, key); // type check
            root = new Entry<K,V>(key, value, null);
            size = 1;
            modCount++;
            return null;
        }
        int cmp;
        Entry<K,V> parent;
        // split comparator and comparable paths
        Comparator<? super K> cpr = comparator;
        // 在二叉樹(紅黑樹是特殊的二叉樹)中,找到(key, value)的插入位置。
        // 紅黑樹是以key來進行排序的,因此這裏以key來進行查找。
        if (cpr != null) {
            do {
                parent = t;
                cmp = cpr.compare(key, t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                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;
                cmp = k.compareTo(t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);
            } while (t != null);
        }
        // 新建紅黑樹的節點(e)
        Entry<K,V> e = new Entry<K,V>(key, value, parent);
        if (cmp < 0)
            parent.left = e;
        else
            parent.right = e;
        // 紅黑樹插入節點後,再也不是一顆紅黑樹;
        // 這裏經過fixAfterInsertion的處理,來恢復紅黑樹的特性。
        fixAfterInsertion(e);
        size++;
        modCount++;
        return null;
    }
  • 代碼分析
  • 1.校驗根節點:校驗根節點是否爲空,若爲空則根據傳入的key-value的值建立一個新的節點,若根節點不爲空則繼續第二步
    2.尋找插入位置:因爲TreeMap內部是紅黑樹實現的,在插入元素時,遍歷左子樹,或者右子樹
    3.新建並恢復:在第二步中其實是須要肯定當前插入節點的位置,而這一步是實際的插入操做,而插入以後爲啥還須要調用fixAfterInsertion方法,紅黑樹插入一個節點後可能會破壞紅黑樹的性質,所以須要使紅黑樹重新達到平衡,

  • HashMap:
  • TreeNode: HashMap的靜態內部類,繼承與LinkedHashMap.Entry<K,V>類,真正維護紅黑樹結構的方法都在其內部。
static final class TreeNode<K, V> extends LinkedHashMap.Entry<K, V>
    {
        TreeNode<K, V> parent; // red-black tree links
        TreeNode<K, V> left;
        TreeNode<K, V> right;
        TreeNode<K, V> prev; // needed to unlink next upon deletion
        boolean red;

        TreeNode(int hash, K key, V val, Node<K, V> next)
        {
            super(hash, key, val, next);
        }
        
        final void treeify(Node<K,V>[] tab)
        {
            // ......
        }
        
        static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root, TreeNode<K,V> x)
        {
            // ......
        }
        
        static <K,V> TreeNode<K,V> rotateLeft(TreeNode<K,V> root, TreeNode<K,V> p)
        {
            // ......
        }
        
        static <K,V> TreeNode<K,V> rotateRight(TreeNode<K,V> root, TreeNode<K,V> p)
        {
            // ......
        }
        
        // ......其他方法省略
    }
  • treeifyBin :在HashMap中put方法時候,但數組中某個位置的鏈表長度大於某一值時,會調用treeifyBin方法將鏈表轉化爲紅黑樹。
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);
        }
    }
  • treeify:將Treenode鏈轉化成紅黑樹,第一次循環會將鏈表中的首節點做爲紅黑樹的根,然後的循環會將鏈表中的的項經過比較hash值而後鏈接到相應樹節點的左邊或者右邊,插入可能會破壞樹的結構。
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);
    }
  • balanceInsertion: 從新平衡二叉樹
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);
                        }
                    }
                }
            }
        }
    }

返回目錄


測試過程及遇到的問題

  • 問題1: 無任何記錄。
  • 解決:

返回目錄


分析總結

返回目錄


代碼託管


返回目錄


參考資料

Intellj IDEA 簡易教程

返回目錄

相關文章
相關標籤/搜索