在學習計算機編程的時候,堆棧
是我們最經常使用到的概念,也是2種最基本的內存管理方式。可是在數據結構算法也有一個堆
,還有叫二叉堆
的,那麼堆棧的堆、堆、和二叉堆,到底是什麼,它們之間有關係嗎,本文就是來解密。
先說結論,數據結構中的堆
和二叉堆
是一個概念,它們和內存管理中的堆
沒有半毛錢的關係。爲了區別說法,本文我們稱前者是二叉堆
,後者叫內存堆
。
內存堆
內存堆通常和棧出現在一起叫內存堆棧:
- 棧:是指函數調用是參數和局部變量的存儲方式。在函數調用的時候由系統分配一塊連續內存,局部變量都存在在棧中,在函數結束後統一釋放。函數間的調用關係也是由函數棧通過先進先出的規則管理。
- 堆:是動態分配的,需要程序主動控制。
二叉堆
設想我們有一個垃圾桶問題。假設:
- 總共有7個固定的放垃圾桶的位置
- 垃圾車隔10分鐘來一次,每次把一個最重的垃圾桶拉走
- 居民每隔10分鐘會放一個新的垃圾桶,重量不定
要求設計一個方案,使得工作最輕鬆。
方案一,沒有任何設計的情況
- 垃圾車取垃圾時,要每個秤一遍,選取最重的後拖走
- 居民看到空的位置放上去。
這個方案,垃圾車每次都需要:秤7次。
方案二,每次都排序好
既然,垃圾車每次都要秤7次,不如我們先排好序,方案如下:
- 垃圾車每次都在1號位置取
- 居民沒次把垃圾桶排好序。
這個方案優化了垃圾車都是一次搬走,但是對於居民每次要排序,怎麼排呢?最節省的方式是用二分查找,如下圖:
先秤4號位置的垃圾桶,如果更輕,則再比較6號位置的,如果更輕,則最後比較7號位置的,如果還是更輕,則所有的桶需要向前移動一位,然後放到7號位置,如下圖:
該方案的最壞情況,每次需要:秤3次,移6次
二叉堆出場
先二叉堆
排序,滿足以下要求:
-
全二叉樹
(即:一定要先填滿上面的) - 父節點比子節點重:如上所示,1比2和3重,2比4和5重,3比6和7重。
注意:這不是二叉搜索樹,二叉搜索樹是父節點比左子節點重,但比右子節點輕。
-
二叉堆
看着是二叉樹,實際上在存儲中還是使用數組,由於是全二叉樹
,子節點可以通過2*k
和2*k + 1
來定位。比如對於3號位置,其子節點就是3*2=6
和3*2+1=7
號位置。
先看垃圾車的操作,最重在1號位置,所以1次搬走。
再看居民的操作,假設新來一個垃圾桶是6kg:
第一步:首先填上空着的1號位置,如下圖。之後,要把1號位置下沉到合適位置。
-
第二步:先和兩個子節點比較,即2和3號位置,如果都比它們重則完成,否則和最重的交換位置,本例中,和3號交換位置如下。這一步:秤2次,移動1次
-
第三步:先和兩個子節點比較,即6和7號位置,如果都比它們重則完成,否則和最重的交換位置,本例中,和7號位置交換。這一步:秤2次,移動1次。
最壞的情況:秤4次,移2次
比較3種算法
方案 | 操作次數 | 複雜度 |
---|---|---|
方案一 | 秤7次 | O(N) |
方案二 | 秤3次,移6次 | O(N) |
方案三 | 秤4次,移2次 | O(Log(N)) |
顯然方案三最優。因爲數量較少,所以方案三的優勢不明顯,當數量逐漸增加時,Log(N)和N的差距就會越來越大。當有10000個垃圾桶時,以下是數據,這個優勢就是數量級的:
方案 | 操作次數 | 複雜度 |
---|---|---|
方案一 | 秤10000次 | O(N) |
方案二 | 秤13次,移9999次 | O(N) |
方案三 | 秤14次,移14次 | O(Log(N)) |
總結
- 內存堆棧的堆和數據結構中的堆,沒有聯繫,它們只是都有上面小下面大,有點像米堆,呵呵;
-
二叉堆
和二叉搜索樹
的區別是,前者父節點比子節點都大,後者是父節點比左節點大,比右節點小; -
二叉堆
算法被普遍用於調度算法中,其核心操作是獲取最大的
和插入
。二叉堆
保證所有操作的複雜度在log(N)。