20172332 2017-2018-2 《程序設計與數據結構》第七週學習總結

20172332 2017-2018-2 《程序設計與數據結構》第七週學習總結

教材學習內容總結

第十一章 二叉查找樹

  • 1.二叉查找樹:一種帶有附加屬性的二叉樹。即每一個結點其左孩子都要小於其父結點,而父結點又小於或等於其右孩子。(左孩子<父結點<=右孩子)
  • 2.二叉查找樹不只具備二叉樹的操做,還具備如下的特殊操做:html

  • 3.用鏈表實現二叉查找樹:每一個BinaryTreeNode對象要維護一個指向結點所存儲元素的引用,另外還要維護指向結點的每一個孩子的引用。
  • 4.二叉查找樹的構造函數代碼(引用了父類的兩個構造函數):
public LinkedBinarySearchTree() 
    {
        super();
    }


    public LinkedBinarySearchTree(T element) 
    {
        super(element);
        
        if (!(element instanceof Comparable))
            throw new NonComparableElementException("LinkedBinarySearchTree");
    }
  • 5.addElement操做:
    ①若是這個元素不是Comparable,則該方法會拋出NoComparableElementException異常;
    ②若是樹爲空,則這個新元素成爲根結點;
    ③若是樹非空,這個新元素會與樹根元素進行比較,
    • (1)若是小於根結點中存儲的元素且根的左孩子爲null,則這個元素成爲根的左孩子。
    • (2)若是這個新元素小於根結點中存儲的那個元素且根的左孩子不是null,則會遍歷根的左孩子並再次進行比較操做。
    • (3)若是這個新元素大於或等於樹根存儲的那個元素且根的右孩子爲null,則這個新元素會成爲根的右孩子。
    • (4)若是這個新元素大於或等於樹根處存儲的那個元素且根的右孩子不是null,則會遍歷根的右孩子,並再次進行比較操做。java

  • 代碼實現:
public void addElement(T element) 
    {
        if (!(element instanceof Comparable))
            throw new NonComparableElementException("LinkedBinarySearchTree");

        Comparable<T> comparableElement = (Comparable<T>)element;

        if (isEmpty())
            root = new BinaryTreeNode<T>(element);
        else 
        {
            if (comparableElement.compareTo(root.getElement()) < 0)
            {
                if (root.getLeft() == null) 
                    this.getRootNode().setLeft(new BinaryTreeNode<T>(element));
                else
                    addElement(element, root.getLeft());
            }
            else
            {
                if (root.getRight() == null) 
                    this.getRootNode().setRight(new BinaryTreeNode<T>(element));
                else
                    addElement(element, root.getRight());
            }
        }
        modCount++;
    }
    
    private void addElement(T element, BinaryTreeNode<T> node) 
    {
        Comparable<T> comparableElement = (Comparable<T>)element;
        
        if (comparableElement.compareTo(node.getElement()) < 0)
        {
            if (node.getLeft() == null) 
                node.setLeft(new BinaryTreeNode<T>(element));
            else
                addElement(element, node.getLeft());
        }
        else
        {
            if (node.getRight() == null) 
                node.setRight(new BinaryTreeNode<T>(element));
            else
                addElement(element, node.getRight());
        }
    }
  • 6.removeElement操做:(必須推選出另外一個結點來代替要被刪除的那個結點)
    ①找不到給定目標元素時,拋出ElementNotFoundException異常。
    ②若是被刪除結點沒有孩子,則replacement返回null。
    ③若是被刪除結點只有一個孩子,則replacement返回這個孩子。
    ④若是被刪除結點有兩個孩子,則replacement會返回中序後繼者(由於相等元素會放到右邊)
  • 代碼實現:
public T removeElement(T targetElement) throws ElementNotFoundException 
    {
        T result = null;

        if (isEmpty())
            throw new ElementNotFoundException("LinkedBinarySearchTree");
        else
        {
            BinaryTreeNode<T> parent = null;
            if (((Comparable<T>)targetElement).equals(root.element)) 
            {
                result =  root.element;
                BinaryTreeNode<T> temp = replacement(root);
                if (temp == null)
                    root = null;
                else 
                {
                    root.element = temp.element;
                    root.setRight(temp.right);
                    root.setLeft(temp.left);
                }

                modCount--;
            }
            else 
            {                
                parent = root;
                if (((Comparable)targetElement).compareTo(root.element) < 0)
                    result = removeElement(targetElement, root.getLeft(), parent);
                else
                    result = removeElement(targetElement, root.getRight(), parent);
            }
        }
        
        return result;
    }

    private T removeElement(T targetElement, BinaryTreeNode<T> node, BinaryTreeNode<T> parent) throws ElementNotFoundException 
    {
        T result = null;
        
        if (node == null)
            throw new ElementNotFoundException("LinkedBinarySearchTree");
        else
        {
            if (((Comparable<T>)targetElement).equals(node.element)) 
            {
                result =  node.element;
                BinaryTreeNode<T> temp = replacement(node);
                if (parent.right == node)
                    parent.right = temp;
                else 
                    parent.left = temp;

                modCount--;
            }
            else 
            {                
                parent = node;
                if (((Comparable)targetElement).compareTo(node.element) < 0)
                    result = removeElement(targetElement, node.getLeft(), parent);
                else
                    result = removeElement(targetElement, node.getRight(), parent);
            }
        }
        
        return result;
    }

    private BinaryTreeNode<T> replacement(BinaryTreeNode<T> node) 
    {
        BinaryTreeNode<T> result = null;
        
        if ((node.left == null) && (node.right == null))
            result = null;
        
        else if ((node.left != null) && (node.right == null))
            result = node.left;
        
        else if ((node.left == null) && (node.right != null))
            result = node.right;
        
        else
        {
            BinaryTreeNode<T> current = node.right;
            BinaryTreeNode<T> parent = node;
            
            while (current.left != null)
            {
                parent = current;
                current = current.left;
            }
            
            current.left = node.left;
            if (node.right != current)
            {
                parent.left = current.right;
                current.right = node.right;
            }
            
            result = current;
        }
        
        return result;
    }
  • 7.removeAllOccurrences操做:(使用了contains方法,find方法被重載以便利用二叉查找樹的有序屬性。)
    ①當在樹中找不到指定元素時,則拋出ElementNotFoundException異常
    ②若是指定的元素不是Comparable,則該方法也會拋出ClassCastException異常。
    ③只要樹中還含有目標元素,就會再次調用removeElement方法。
  • 代碼實現:
public void removeAllOccurrences(T targetElement)
                   throws ElementNotFoundException 
    {
        removeElement(targetElement);
        
        try
        {
            while (contains((T)targetElement))
                removeElement(targetElement);
        }
        
        catch (Exception ElementNotFoundException)
        {
        }
    }
  • 8.removeMin操做:
    ①若是樹根沒有左孩子,則樹根就是最小元素,而樹根的右孩子會變成新的根結點。
    ②若是樹的最左側結點是一片葉子,則這片葉子就是最小元素,這時只需設置其父結點的左孩子引用爲null便可。
    ③若是樹的最左側結點是一個內部結點,則須要設置其父結點的左孩子引用指向這個將刪除結點的右孩子。
  • 代碼實現:
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;
    }
  • 9.用有序列表實現二叉查找樹:實現ListADT接口和OrderedListADT接口。
  • 10.BinarySearchTreeList實現的分析:add操做與remove操做都要求從新平衡化樹。
  • 11.樹實現中的有些操做更爲有效,有些操做更爲低效。
  • 12.蛻化樹:無分支的樹(效率比鏈表還低)
  • 13.平衡樹的方法:
    • (1)右旋(左子樹長):①使樹根的左孩子元素成爲新的根元素。②使原根元素成爲這個新樹根的右孩子元素。③使原樹根的左孩子的右孩子,成爲原樹根的新的左孩子。
    • (2)左旋(右子樹長):①使樹根的右孩子元素成爲新的根元素。②使原根元素成爲這個新樹根的左孩子元素。③使原樹根右孩子結點的左孩子,成爲原樹根的新的右孩子。
    • (3)右左旋(樹根右孩子的左子樹長):①讓樹根右孩子的左孩子,繞着樹根的右孩子進行一次右旋。②讓所得的樹根右孩子繞着樹根進行一次左旋。
    • (4)左右旋(樹根左孩子的右子樹長):①讓樹根左孩子的右孩子繞着樹根的左孩子進行一次左旋。②讓所得的樹根左孩子繞着樹根進行一次右旋。
  • 14.實現二叉查找樹:①AVL樹。②紅黑樹。(自樹根而下的路徑最大長度必須不超過log2 n,並且自樹根而下的路徑最小長度必須不小於log2 n -1)
  • 15.平衡因子:右子樹的高度減去左子樹的高度
  • 16.使樹變得不平衡有兩種方法:①插入結點。②刪除結點。
  • 17.AVL樹的右旋:由下圖可知咱們是在結點T的左結點的左子樹上作了插入元素的操做,咱們稱這種狀況爲左左狀況,咱們應該進行右旋轉(只需旋轉一次,故是單旋轉)【步驟與右旋步驟同樣】node

    • 過程:git

  • 18.AVL樹的左旋:由下圖可知咱們是在結點T的右結點的右子樹上作了插入元素的操做,咱們稱這種狀況爲右右狀況,咱們應該進行左旋轉(只需旋轉一次,故是單旋轉)【步驟與左旋步驟同樣】數據結構

    • 過程:函數

  • 19.AVL樹的左右(先左後右)旋:以下圖,只是單純的進行一次旋轉,獲得的樹仍然是不平衡的。因此應該進行二次旋轉。學習

  • 20.AVL樹的右左(先右後左)旋:以下圖,只是單純的進行一次旋轉,獲得的樹仍然是不平衡的。因此應該進行二次旋轉。this

  • 21.紅黑樹:每一個結點存出一種顏色(紅色或黑色,一般用一個布爾值來實現,false等價於紅色)
    • 紅黑樹的特性:
    • (1) 每一個節點或者是黑色,或者是紅色。
    • (2) 根節點是黑色。
    • (3) 每一個葉子節點是黑色。 [注意:這裏葉子節點,是指爲空的葉子節點!]
    • (4) 若是一個節點是紅色的,則它的子節點必須是黑色的。
    • (5) 從一個節點到該節點的子孫節點的全部路徑上包含相同數目的黑節點。
  • 22.紅黑樹的元素添加及刪除。.net

教材學習中的問題和解決過程

  • 問題1:replacement會返回中序後繼者的中序後繼者是什麼東西?
  • 問題1解決方案:從被刪除的結點出發通過它的右結點,而後右結點最左邊的葉子結點就是中序後繼結點。
public Node getSuccessor(Node delNode){ //參數爲被刪除的節點 
        //定義一個當前節點的引用,直接讓往下走一步,走到被刪除節點的右節點 
        Node curr=delNode.right; 
        Node successor=curr; //用來指向中級後續節點 
        Node sucParent=null; //用來指向中級後續節點的父節點 
        while(curr!=null){ 
        sucParent=successor; 
        successor=curr; 
        curr=curr.left; 
        } 
        //循環中止,中級後續節點被找出 
        if(successor!=delNode.right){ 
        //將後繼節點的子節點(只可能有右節點)替補到後繼節點的位置上 
        sucParent.left=successor.right; 
        //將被刪除的右孩子鏈接到後繼節點的右節點上 
        successor.right=delNode.right; 
        //將被刪除的左孩子鏈接到後繼節點的右節點上(就是用後繼節點替換掉被刪除的節點) 
        } 
        return successor; 
    }
  • 問題引伸:爲何要找中序後繼者做爲代替刪除結點的位置。
  • 問題引伸解決方案:若是直接刪結點,整個樹的大小順序就亂了,因此須要考慮,在樹中找到一個合適的節點來把這個結點給替換掉,用這種方法來保持整個樹的穩定。須要在樹中找出全部比被刪除節點的值大的全部數,並在這些數中找出一個最小的數來。設計

  • 問題2:AVL樹的做用。
  • 問題2解決方案:刪除時樹的平衡性受到破壞,提升它的操做的時間複雜度。而AVL樹就不會出現這種狀況,樹的高度始終是O(lgN).高度越小,對樹的一些基本操做的時間複雜度就會越小。

代碼調試中的問題和解決過程

  • 問題1:紅黑樹的元素添加。
  • 問題1解決方案:
    • 步驟1:將紅黑樹看成一顆二叉查找樹,將節點插入。
    • 步驟2:將插入的節點着色爲"紅色"。
    • 步驟3:經過一系列的旋轉或着色等操做,使之從新成爲一顆紅黑樹。
    private void insert(RBTNode<T> node) {
          int cmp;
          RBTNode<T> y = null;
          RBTNode<T> x = this.mRoot;
    
          // 1. 將紅黑樹看成一顆二叉查找樹,將節點添加到二叉查找樹中。
          while (x != null) {
              y = x;
              cmp = node.key.compareTo(x.key);
              if (cmp < 0)
                  x = x.left;
              else
                  x = x.right;
          }
    
          node.parent = y;
          if (y!=null) {
              cmp = node.key.compareTo(y.key);
              if (cmp < 0)
                  y.left = node;
              else
                  y.right = node;
          } else {
              this.mRoot = node;
          }
    
          // 2. 設置節點的顏色爲紅色
          node.color = RED;
    
          // 3. 將它從新修正爲一顆二叉查找樹
          insertFixUp(node);
      }
      /* 
       * 新建結點(key),並將其插入到紅黑樹中
       *
       * 參數說明:
       *     key 插入結點的鍵值
       */
      public void insert(T key) {
          RBTNode<T> node=new RBTNode<T>(key,BLACK,null,null,null);
          // 若是新建結點失敗,則返回。
          if (node != null)
              insert(node);
      }
  • 問題2:紅黑樹的元素刪除。
  • 問題2解決方案:
    • 步驟1:將紅黑樹看成一顆二叉查找樹,將節點刪除。
    • 步驟2:經過"旋轉和從新着色"等一系列來修正該樹,使之從新成爲一棵紅黑樹。
    /* 
       * 刪除結點(node),並返回被刪除的結點
       *
       * 參數說明:
       *     node 刪除的結點
       */
      private void remove(RBTNode<T> node) {
          RBTNode<T> child, parent;
          boolean color;
          // 被刪除節點的"左右孩子都不爲空"的狀況。
          if ( (node.left!=null) && (node.right!=null) ) {
              // 被刪節點的後繼節點。(稱爲"取代節點")
              // 用它來取代"被刪節點"的位置,而後再將"被刪節點"去掉。
              RBTNode<T> replace = node;
              // 獲取後繼節點
              replace = replace.right;
              while (replace.left != null)
                  replace = replace.left;
              // "node節點"不是根節點(只有根節點不存在父節點)
              if (parentOf(node)!=null) {
                  if (parentOf(node).left == node)
                      parentOf(node).left = replace;
                  else
                      parentOf(node).right = replace;
              } else {
                  // "node節點"是根節點,更新根節點。
                  this.mRoot = replace;
              }
              // child是"取代節點"的右孩子,也是須要"調整的節點"。
              // "取代節點"確定不存在左孩子!由於它是一個後繼節點。
              child = replace.right;
              parent = parentOf(replace);
              // 保存"取代節點"的顏色
              color = colorOf(replace);
              // "被刪除節點"是"它的後繼節點的父節點"
              if (parent == node) {
                  parent = replace;
              } else {
                  // child不爲空
                  if (child!=null)
                      setParent(child, parent);
                  parent.left = child;
                  replace.right = node.right;
                  setParent(node.right, replace);
              }
              replace.parent = node.parent;
              replace.color = node.color;
              replace.left = node.left;
              node.left.parent = replace;
              if (color == BLACK)
                  removeFixUp(child, parent);
                  node = null;
              return ;
          }
          if (node.left !=null) {
              child = node.left;
          } else {
              child = node.right;
          }
          parent = node.parent;
          // 保存"取代節點"的顏色
          color = node.color;
          if (child!=null)
              child.parent = parent;
          // "node節點"不是根節點
          if (parent!=null) {
              if (parent.left == node)
                  parent.left = child;
              else
                  parent.right = child;
          } else {
              this.mRoot = child;
          }
          if (color == BLACK)
              removeFixUp(child, parent);
          node = null;
      }
      /* 
       * 刪除結點(z),並返回被刪除的結點
       *
       * 參數說明:
       *     tree 紅黑樹的根結點
       *     z 刪除的結點
       */
      public void remove(T key) {
          RBTNode<T> node; 
    
          if ((node = search(mRoot, key)) != null)
              remove(node);    
      }
  • 問題3:按着書上講的左旋右旋步驟來作,會出現錯誤


  • 問題3解決方案:
  • 問題代碼的過程爲下圖,很顯然出現了覆蓋結點,致使丟失結點的問題。

  • 改正:


  • 過程:

代碼託管

上週考試錯題總結

  • 無。

點評過的同窗博客和代碼

其餘(感悟、思考等,可選)

  • 查找二叉樹是二叉樹的引伸學習,難度真的很大!

學習進度條

代碼行數(新增/累積) 博客量(新增/累積) 學習時間(新增/累積) 重要成長
目標 5000行 30篇 400小時
第一週 0/0 1/1 2/2
第二週 1010/1010 1/2 10/12
第三週 651/1661 1/3 13/25
第四周 2205/3866 1/4 15/40
第五週 967/4833 2/6 22/62
第六週 1680/6513 1/7 34/96
第七週 2196/8709 1/8 35/131
  • 計劃學習時間:30小時

  • 實際學習時間:35小時

  • 改進狀況:AVL樹和紅黑樹真的耗費了大量的時間!

參考資料

相關文章
相關標籤/搜索