2018.2.14 algo part2 heaps and trees
1.
先講的堆,其實上周的dijkstra就有涉及到堆的內容,但是這周才詳細的講堆本身。堆邏輯上是一個二叉樹,父節點的值比兩個子節點都要大(或者比兩個小)。堆是一個完全二叉樹,所以很適合直接就用數組表示,因為父節點和字節點的位置關系是確定的(2n和2n+1)。
堆的特點是它一直把最大/最小值放在根節點,因此如果你的需求是需要不停的對一組動態的數據求max/min,那麽你就可以用堆來優化。當然,除了extract min/max以外一般還需要同時做插入數據之類的操作,否則就是靜態數據了,如果是靜態數據直接數組就行了。
(1)對一個排好的堆做insert操作,就是把插入的數據放在堆的最末尾,然後看它是不是比父節點小(假設是min堆),如果是就沒啥問題,不是的話就和父節點交換(把父節點擠下來)然後再和新的父節點比,直到比父節點小為止。這個動作很顯然是O(lg n)的。
(2)另外一個常用操作就是extract min了,畢竟這就是堆的本職工作。步驟是把根節點pop掉後,用尾部的結點代替根節點。當然這時候的堆顯然不太對,所以新的根節點需要不停的和自己比較小的子節點交換,直到沒有子節點,或者比所有子節點都小為止。這個動作也是O(lg n)的。
一般來說,heap做的操作時間復雜度看起來和二叉樹差不多,而且heap還不能同時保存min和max,而二叉樹卻可以。但是heap的優勢是,大部分操作heap的時間復雜度常數以及空間復雜度都要更好些。
2.
下一個話題就是二叉樹了,或者具體說是平衡二叉搜索樹。對於數據結構來說,一個排好的數組看起來已經很強大了,但是它很難處理動態的數據,因為它的插入或刪除操作很可能是O(n),這就比較僵硬。所以當你需要頻繁插入或刪除的時候,就需要使用平衡二叉樹,來得到O(lg n)的性能。
先說普通非平衡的二叉搜索樹,二叉搜索樹的特征就是對於父節點來說,左邊子樹的所有值都比它小,而右邊子樹都更大。對這樣的樹搜索,自然非常方便,從根節點搜起,左拐右拐就拐到了。insert的話其實和search一樣,就是先search你要插入的那個數據,然後把數據插進search到的應該出現的位置就對了。
(1)求max或者min,就是從根節點找起,一直向左拐或者一直向右拐,直到到頭為止,很簡單很直觀。
(2)找某個結點的數值上的前一個結點:如果該結點有左子樹,那麽找到左子樹的max結點;如果左子樹是空的,那麽就順著父節點向上爬,爬到第一個比它小的就是他的前一個結點了。找數值上的後一個結點也一樣,把左右顛倒下就行。
(3)把二叉搜索樹輸出為有序數組:一個大量遞歸的算法。對於給定的根節點,先對他的左子樹遞歸這個算法,然後打印根節點,然後對右子樹遞歸這個算法。這裏應該需要一個判定條件,如果只有一個子樹就遞歸這個子樹就好。沒有子樹就是base case,直接打印葉子結點。
(4)對二叉搜索樹的結點做刪除操作:如果是葉子,直接刪掉就好。如果該結點只有一個子樹,那麽把他刪掉,然後把他的父節點和子樹連上就行。如果是兩個子樹都有,就比較麻煩,需要找出他左子樹的max結點,然後把max結點和待煽節點交換,交換後待刪結點是只有左子樹或者沒子樹的,那麽用前面兩個方法之一處理就好。
(5)對二查搜索樹做select操作:就是找出該樹第i大的數據。對這種操作,我們需要一個輔助數據,就是我們要知道每個節點的size。size就是這個節點的左右子樹(如果有的話)連同他自己的所有結點的數量。求一個節點的size顯然用遞歸就好,比較簡單。不過insert和delete操作之後需要對size更新,比較麻煩。
有了size之後就很簡單了,從root找起,如果左子樹的size剛好是i-1,那就是root了;如果左子樹的size比i-1大,那麽對左子樹遞歸;如果比i-1小,那麽對右子樹遞歸,不過要找的序數i要調整,有點像之前的R-selection算法。
後面還有紅黑樹的內容,明天再說吧,作業也明天做,這周內容好多。。。
2018.2.14 algo part2 heaps and trees