該文章為清華大學數據結構與算法設計MOOC課程讀書筆記.
1. 數據結構的靜態操作與動態操作
靜態操作(static operation) : search
動態操作(dynamic operation) : insert, remove
效率 | Vector | List |
---|---|---|
static | O(lgn) | 線性 |
dynamic | 線性 | O(1) |
因為vector和list在靜態或者動態操作中,有一種操作為線性的效率,因此稱他們為線性數據結構。
Tree綜合了Vector和List的優點,高效地兼顧了靜態操作與動態操作。雖然具有線性特征,但是不完全是線性結構,因此可以稱之為半線性結構。
2. 有根樹與有序樹
有根:樹本身是一個圖,指定圖中某個點為root,該圖則變成有根樹。
有序:在數學上樹的孩子可以無序,但是在計算機對樹的表示中,需要指定出孩子間所存在的一種有序性,因此稱為有序樹。
關于樹的節點還有邊的關系,重要的是:它們的數量級同階
3. 樹結構的特性(與圖的區別)
簡單來講,樹是一種連通無環圖.
- 連通性(connected):任意兩點之間均有路徑 -> 邊數不會太少
- 無環性(acyclic):不存在環路 -> 邊數不能太多
推論:
任何節點v與root之間存在唯一的path
- 對于半線性的另一種理解:前驅(prev)只有一個,唯一確定,后繼(succ)可以有多個,不確定。
4. 樹的表示
“長子兄弟(1st-child & sibling)”法:每個節點維持兩個指針,一個指針指向第一個child node,另一個指針指向后一個sibling node。
5. 二叉樹
5.1 性質
節點數最少與高度h同階,最多可以是高度h的冪指數級。
5.2 表示與實現
6. 二叉樹的遍歷
通過遍歷,將半線性結構的樹轉換為完全線性結構的遍歷序列,使某些問題得到簡化。
按照節點被訪問的順序分類:先序(pre-order),中序(in-order),后序(post-order),層次(level-order)
特點:每個節點只被訪問一次,復雜度O(n).
6.1 先序遍歷
(1)遞歸實現
按照定義,遞歸實現很簡單。但是在運行棧中,由于每個遞歸實例需要占據固定的空間,遞歸實現的效率其實并不高。
(2)迭代實現1
因為stack的LIFO性質,注意節點入棧的順序:先右后左
(3)迭代實現2
雖然迭代實現1非常簡單易懂,但是不利于推廣到中序以及后序遍歷中,因此需要一個更加能夠抓住遍歷本質的算法。
通過對遍歷過程的觀察可知,遍歷具有這樣一個特點:先沿著左側鏈(left-branch)不斷向下訪問,再自下而上地去訪問右子樹。
因此,實現的思路是:
沿著left-branch自上而下地訪問節點,同時將其對應的右孩子壓入棧中。
6.2 中序遍歷
(1) 遞歸實現
同理
(2) 迭代實現
觀察可知:最先訪問的節點是最左的節點,總體而言自下而上地進行訪問。因此需要一種LIFO的數據結構-Stack。
因此,實現的思路是:
沿著left-branch找到最左節點并訪問,在這個過程中將沿途的節點壓入棧中。訪問完當前節點后控制權轉移到當前節點的右子樹。
(3) 效率分析 - 分攤分析(Amortized Analysis)法
乍一看,兩個循環,每個循環最壞可以達到O(n),因此O(n2)。這好比一個正方形,每個循環分別代表一條邊,因為每條邊的長度O(n),因此面積O(n2)。
但是仔細分析之后,真正的運行時間好比正方形框中的一條條直線,每條直線的長度對應s.push()
的操作次數,因為每個節點最多入棧一次,節點數目為n,因此各條直線的累積和為O(n)。相當與平均下來,每條直線的長度只是O(1),因此實際面積(即實際時間復雜度)為O(n).
6.3 后序遍歷
觀察可知:最先訪問的節點是最左的葉節點,總體而言自下而上地進行訪問。因此需要一種LIFO的數據結構-Stack。
因此,實現的思路是:
沿著left-branch找到最左葉節點并訪問,在這個過程中將沿途的節點以及其右子樹節點壓入棧中。訪問完當前節點后控制權轉移到當前節點的右子樹。
后序遍歷應用 - 表達式樹
對表達式樹的后序遍歷可以生成RPN,再根據RPN的算法來計算表達式的值。
6.4 層次遍歷
思路:在之前的三種遍歷中,都有著一種逆序訪問的過程,即后代咸魚祖先被訪問。因此,這三種遍歷的實現都借助與Stack這種具有LIFO特性的數據結構。但是在層次遍歷中,嚴格遵循著父輩先與子輩被訪問,因此需要一種FIFO的數據結構-Queue。
6.5 重構
思想:通過遍歷序列確定root + 左子樹節點 + 右子樹節點,然后遞歸地重構出二叉樹。
(1) 普適方法
通過先序/后序遍歷的序列來確定root,再根據中序遍歷序列來確定左、右子樹。
注意,只通過先序和后續遍歷的序列,是不能夠保證重構的,因為:
若只存在左子樹或者右子樹,是無法確定是左子樹還是右子樹的。
(2) 特殊情況:真二叉樹proper binary tree
由于在真二叉樹中,某個節點要么左右子樹都沒有,要么左右子樹都有,因此可以確定出root,左子樹以及右子樹。