20172301 《程序設計與數據結構》第七週學習總結

20172301 《程序設計與數據結構》第七週學習總結

教材學習內容總結

  • 二叉查找樹是一種含有附加屬性的二叉樹,其左孩子小於父結點,父結點小於或者等於右孩子。

用鏈表實現二叉查找樹

  • addElement操做:根據給定元素的值,在樹中的恰當位置添加該元素。
    • 判斷元素是否是Comparable,不是則拋出異常。
    • 樹爲空:新元素成爲根結點。
    • 樹非空:新元素與根元素進行比較
      • 小於:若是根的左孩子爲空,成爲根的左孩子;左孩子不空,遍歷添加。
      • 大於:若是根的右孩子爲空,成爲根的右孩子;右孩子不空,遍歷添加。
  • removeElement操做:從二叉查找樹中刪除給定的Comparable元素;找不到則拋出異常。
    • 選擇替換結點的三種狀況:
      (1)被刪除結點沒有孩子,replacement返回null;
      (2)被刪除結點有一個孩子,replacement返回這個孩子 ;
      (3)被刪除結點有兩個孩子,replacement返回中序後繼者;(處於根結點右子樹上)
  • removeAllOccurrences操做:從二叉查找樹中刪除指定元素的全部存在。
    • 方法使用了LinkedBinaryTree類的contains方法。
  • removeMin操做:
    • 最小元素在二叉查找樹的可能狀況:
      (1)樹根沒有左孩子,樹根即爲最小元素,樹根右孩子變成新的根結點;
      (2)樹的最左側結點爲一片葉子,該葉子即爲最小元素,設置其父結點的左孩子應用爲null;
      (3)樹的最左側結點爲內部結點,設置其父結點的左孩子引用指向最小元素的右孩子。

用有序鏈表實現二叉查找樹

  • 樹的主要使用之一就是爲其餘集合提供高效的實現。
操做 說明 LinkedList BinarySearchTreeList
removeFirst 刪除列表的第一個元素   O(1) O(logn)
removeLast 刪除列表的最後一個元素  O(n) (logn)
remove 刪除列表中的查找到的第一個元素 O(n) O(logn)
first 返回列表第一個元素   O(1) O(logn)
last 返回列表最後一個元素   O(n) O(logn)
contains 判斷列表是否含有一個特定元素   O(n) O(logn)
is Empty 斷定列表是否爲空  O(1) O(1)
size 列表中的元素數目  O(1) O(1)
add(有序列表特有) 向列表添加一個元素  O(n) O(logn)
  • add操做和remove操做都會致使樹不平衡。

平衡二叉查找樹

爲何樹須要平衡?html

若是二叉樹是一棵蛻化樹,他的效率可能比線性結構還要低。也就不能知足樹的高效實現。node

  • 右旋:指左孩子繞着其父結點向右旋轉。
    • 應用狀況:根結點左孩子的左子樹較長路徑致使不平衡
    • 方法步驟:
      • 樹根左孩子元素成爲新的根元素
      • 原樹根元素稱爲新樹根的右孩子元素
      • 使原樹根左孩子的右孩子,成爲原樹根的新的左孩子
  • 左旋:指右孩子繞着其父結點向左旋轉。
    • 應用狀況:根結點右孩子的右子樹較長路徑致使不平衡
    • 方法步驟:
      • 樹根右孩子元素成爲新的根元素
      • 原樹根元素稱爲新樹根的左孩子元素
      • 使原樹根右孩子的左孩子,成爲原樹根的新的右孩子
  • 左右旋:先讓樹根左孩子的右孩子,繞着樹根的左孩子進行一次左旋,而後再讓所得樹根左孩子繞着樹根進行一次右旋
    • 應用狀況:根結點左孩子的右子樹較長路徑致使不平衡
  • 右左旋:先讓樹根右孩子的左孩子,繞着樹根的右孩子進行一次右旋,而後再讓所得樹根右孩子繞着樹根進行一次左旋
    • 應用狀況:根結點右孩子的左子樹較長路徑致使不平衡

AVL樹

  • 平衡因子:右子樹的高度減去左子樹的高度。

紅黑樹

  • 紅黑樹的性質:
    • 樹中的每個結點都儲存着紅色或黑色,一般使用一個布爾值來實現,值false等價於紅色。
    • 根結點和葉子結點(null)爲黑色。爲空(null)的葉子結點才爲黑色。
    • 紅色結點的全部孩子一定是黑色。
    • 從樹根到樹葉的每條路徑都包含有一樣數目的黑色結點。
  • 紅黑樹的平衡性質並無AVL樹那麼嚴格,可是,他們的序仍然是logn。

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

  • 問題1:關於書P228頁的中序後繼者的理解。
  • 問題1解決方案:
    • 所謂的中序後繼者意思是:中序遍歷二叉樹結點的後繼結點
    • 如何查找中序後繼者?
      • 若右子樹不爲空,則找到右子樹最左的葉子節點;
      • 若右子樹爲空,且擁有右父親節點,則找到右父親節點;
      • 若右子樹爲空,且擁有左父親節點,則找到最近的右祖先節點;
    • 而對於刪除結點有兩個孩子的狀況時,不必定replacement返回中序後繼者。也能夠返回中繼前驅者。 具體的須要看代碼實現,而不須要侷限於書本。
    • 如何查找中序前驅者?
      • 若左子樹不爲空,則找到左子樹的最右的葉子節點;
      • 若左子樹爲空,且擁有左父親節點,則找到左父親節點;
      • 若左子樹爲空,且擁有右父親節點,則找到最近的左父祖先節點;
  • 問題2:書P228的變量modCount爲何是減減。
  • 問題2解決方案:
    • 變量modcount應該是計算迭代次數的。
    • API裏寫的也是這樣的,可是爲何刪除操做要遞減一。
    • 以前實現列表的刪除操做也是modcount--,這個未解決。

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

  • 問題1:是否須要定義新的指針類AVLTreeNode,換句話說,AVL樹和二叉查找樹以及鏈表實現的二叉樹之間的關係。
  • 問題1解決方案:
    • 首先,根據書上P240所述git

      因爲須要上溯樹,所以AVL樹一般最好實現爲每一個結點都包含一個指向其父結點的引用。markdown

    • 這裏的上溯樹是由於,樹由於插入結點或者刪除結點而變得不平衡,因此每次在進行這兩個操做的時候,須要更新平衡因子,從插入或者刪除的那個結點開始,檢查到根結點。因此,咱們的指針類極可能除了指向左右孩子的指針,還須要一個指向父結點的。
    • 其次,根據書上P239所述數據結構

      對於樹中的每一個結點,咱們都會跟蹤其左、右子樹的高度。架構

    • 由此,指針類會須要一個int型變量height,來得出結點的高度。
    • 在我實現了指針類AVLTreeNodeLinkedAVLTree的平衡方法後,我須要實現添加和刪除方法。可是,AVL樹和二叉查找樹惟一不一樣的是添加和刪除中若是不平衡要進行旋轉。 因此,AVL樹是能夠繼承二叉查找樹的。
    • 這時,其實我陷入了一個思惟誤區。我寫的指針類AVLTreeNode所以確定也要繼承二叉樹指針類BinaryTreeNode。可是,其實根本不用這麼麻煩呀!
      直接在BinaryTreeNode構建新的構造方法不就能夠了!
    public BinaryTreeNode(T obj, LinkedBinaryTree<T> left, LinkedBinaryTree<T> right,int height)`
    • 存在的問題:
      雖然準確理解了AVL樹中旋轉平衡的操做,可是並無總體理解代碼與代碼之間的關係。花費大量的時間作了無用功,同時讓本身陷入了錯誤的循環。
      若是,我直接發現AVL樹是二叉查找樹的子類,那我也不會構建新的指針類。
      因此,解決代碼問題,首先須要宏觀的觀察,肯定好總體的架構,這即是UML類圖的重要性。否則,儘管你細節處理的再完美,方向錯了,即是越走越遠。學習

      先設計,考慮全部的狀況,再去實現。spa

  • 問題2:鏈表旋轉方法的順序問題。
  • 問題2解決方案:這裏以右旋爲例。
    • 根據書P238 給出右旋的操做.net

      • 使樹根的左孩子元素成爲新的根元素。
      • 使原根元素成爲這個新樹根的右孩子元素。
      • 使原樹根的左孩子的右孩子,成爲原樹根的新的左孩子。
    • 因此咱們實現右旋方法就可使用一下操做,其中node是原樹根,node1是新樹根。
    node1 = node.left;
    node1.right = node;
    node.left = node1.right;
    • 而後,添加上更新高度的操做。就能夠返回新的根元素。
    node.height = Math.max(height(node.left),height(node.right));
    node1.height = Math.max(height(node1.left),height(node1.right));
    return node1;
    • 運行,首先給我拋出的是StackOverflowError錯誤。

    • 當應用程序遞歸太深而發生堆棧溢出時,拋出該錯誤。也就是說,方法裏出現了死遞歸。這個問題,我在上週侯澤洋同窗的博客中也看見過。
    • 調試發現,node的左子樹是無限的,這說明右旋存在問題。
      咱們根據書上的操做寫出來的代碼,改變了node1的右子樹,因此node的左子樹插入的全是node,也就是無限循環的。
    • 因此,咱們須要改變一下操做順序。
    node1 = node.left;
    node.left = node1.right;
    node1.right = node;

    這樣的操做就會更加合理,一樣也不會出現死遞歸的錯誤。實現代碼以前,要考慮樹的子樹鏈接順序問題,和鏈表相似,不要出現丟失或者贅餘的狀況。設計

  • 問題3:AVL樹添加/刪除方法旋轉狀況的不全面。
  • 問題3解決方案:
    • AVL樹旋轉的緣由應該是樹不平衡
    • 我初次實現代碼時,旋轉的判斷條件是當根的平衡因子絕對值大於1的時候,因此存在一種狀況,即爲對於根來講是平衡的,而對於根的某一結點來講是不平衡的。
    • 這裏我新建一個AVL數,在插入10,6,12,8,14以後,樹是平衡的,並不須要平衡操做。
      如圖

    而後,我插入數字7。
    如圖

    這時,AVL樹對於根來講是平衡的,可是對於根的左子樹來講是不平衡的,由於根的左孩子沒有左孩子。對於較爲嚴格的AVL樹來講,這是不符合規則的。

    因此,書上給出的避免操做是創建一個指向父結點的引用,經過上溯樹來判斷每一個結點是不是平衡的。

    • 可是,我若是經過添加操做的遞歸在插入的時候來判斷結點是不是平衡的,是否能夠呢。有待實現

代碼託管

上週考試錯題總結

上週無錯題,優秀!

結對及互評

點評過的同窗博客和代碼

  • 上週博客互評狀況
    • 20172304
    • 段志軒同窗的博客教材內容總結的愈來愈詳細,要注意markdown的表格格式。教材問題應該能更深刻一些。
    • 20172328
    • 博客一樣內容很豐富,感想也是很深入。

其餘

紅黑樹雖然難理解,可是應用的實例不少,也須要掌握。同時,AVL樹相對嚴格,實現代碼的時候要更加嚴謹,考慮所有的可能和狀況。從而儘量減小錯誤的出現。

學習進度條

代碼行數(新增/累積) 博客量(新增/累積) 學習時間(新增/累積) 重要成長
目標 5000行 30篇 400小時
第一週 0/0 1/1 10/10
第二週 610/610 1/2 20/30
第三週 593/1230 1/3 18/48
第四周 2011/3241 2/5 30/78
第五週 956/4197 1/6 22/100
第六週 2294/6491 2/8 20/120
第七週 914/7405 1/9 20/140

參考資料

相關文章
相關標籤/搜索