《數據結構與算法之美》筆記之 堆及其應用


堆排序時間複雜度:O(nlogn)O(n\log n)

一、定義

定義只有兩條:

  1. 堆是一個完全二叉樹;
    除了最後一層外,其它節點都是滿的,最後一層節點全部靠左排列,完全二叉樹非常適合使用數組存儲
    圖片來自百度百科
  2. 堆中每一個節點的值都必須大於等於(或小於等於)其子樹中每個節點的值。
    等價於“堆中每個節點的值都大於等於(或小於等於)其左子節點和右子節點的值”,大於等於的叫大頂堆,小於等於的叫小頂堆。

二、如何實現一個堆

首先確定如何存儲一個堆 和 堆支持哪些操作

存儲堆

使用數組存儲完全二叉樹,不需要存儲左右子節點的指針,只需要通過數組的下標就可以找到左右子節點和父節點。所以用數組存儲堆。

假設堆頂元素存儲在數組下標爲 1 的位置,那麼下標爲ii的節點的相關節點位置爲:

  • 左子節點爲2i2*i
  • 右子節點爲2i+12*i+1
  • 父節點爲i2\frac i 2

堆的基本操作

主要有插入元素,刪除堆頂元素

插入元素

假設我們將新插入的元素放在堆的最後,插入元素後需要繼續滿足堆的定義,就需要對堆進行調整,這個過程叫堆化
堆化分爲自上而下和自下而上
自下而上即讓新插入的節點與父節點比對大小,如果不滿足子節點小於等於父節點的大小關係就互換兩個節點,一直重複,直到滿足關係。

刪除堆頂元素

假設堆爲大頂堆,刪除之後就需要將左右子節點較大的元素放入父節點,迭代刪除,但是這樣會出現空洞,所以我們可以先將堆的最後一個元素放入堆頂,然後利用同樣的父子節點比對方法進行互換,這就是自上而下的的堆化方法。

那堆的這兩個基本操作的時間複雜度是多少?堆化是順着節點所在路徑比較交換,時間複雜度與樹的高度成正比,樹的高度不超過log2n\log_2 n,所以時間複雜度爲O(logn)O(\log n)

三、堆排序

堆排序的步驟爲建堆排序

1.建堆

將數組原地建成一個堆
兩種思路:

  1. 藉助堆的插入方法,假設起初堆中只包含一個元素,下標爲1,之後插入下標爲2的元素,再插入下標爲3的元素…直到將包含n個元素的數組組成了堆,這樣就是從前往後處理數組元素,自下而上堆化。
  2. 另外的思路是從後往前處理數組元素,自上而下堆化
    因爲葉子節點往下堆化只能與自己比較,所以我們從第一個非葉子節點(下標爲n2\frac n 2)開始依次堆化,下標大於n2\frac n 2的是葉子節點,不需要堆化。建堆過程的時間複雜度是O(n)

2. 排序

建堆之後,數據就是按照大頂堆的特性來組織的,我們將堆頂元素與堆最後一個元素交換,即將最大元素放到堆的最後,類似於刪除了堆頂元素,將剩下n-1個重新建堆,再取堆頂元素,放到n-1個元素的最後,直到堆中只剩下標爲1的一個元素。排序每次維護堆的時間複雜度爲O(logn),進行了n-1次,所以時間複雜度爲O(nlogn)。

堆排序是原地排序,並不是穩定的排序算法,排序過程中堆的每一個節點要與堆頂節點互換。

如果堆在數組中從0開始存儲,那麼相關節點的下標爲:

  1. 左節點下標爲2i+12*i+1
  2. 右節點下標爲2i+22*i+2
  3. 父節點下標爲i12\frac {i-1} 2

3. 堆排序的缺點

實際開發中快排要比堆排序好,有以下原因:

  1. 快排局部順序訪問,而堆排序依次訪問的下標是指數增長的,如1,2,4,8,。。。這樣對CPU緩存不友好
  2. 對於同樣的順序,堆排序的數據交換次數多於快速排序。快排的數據交換次數不會比逆序度多,而快排第一步的建堆過程會打亂數據的先後順序,導致原有數據有序度降低。

堆的應用

主要有:優先隊列,求Top K 和求中位數

優先隊列

堆和優先隊列非常相似,一個堆就可以看做一個優先隊列
優先隊列的具體應用有合併有序小文件和高性能定時器

求TOP K

針對靜態數據集合,可以維護一個大小爲K的小頂堆,順序遍歷數組,從數組中取出數據與堆頂元素比較,如果比堆頂大,則把堆頂元素刪除,並把這個元素插入堆,如果比堆頂元素小,則不作處理。時間複雜度是O(nlogK)。
針對動態數據集合,我們同樣維護一個大小爲K的小頂堆,當數據被添加到集合時,與堆頂元素比較,決定是非刪除堆頂元素併入堆,

利用堆求中位數

對於靜態數據,先排序再確定中位數,但是對於動態數據集合,每次排序的成本太高。

我們可以維護兩個堆,一個大頂堆,一個小頂堆,大頂堆中存儲前半部分數據,小頂堆中存儲後半部分數據,小頂堆中數據都大於大頂堆的數據,這樣堆頂元素就有一個是中位數,當n是奇數時,大頂堆存儲2n+12n+1個數據,這樣大頂堆堆頂就是中位數
數據是動態變化,當添加一個元素a時,當a小於大頂堆堆頂我們就將a插入大頂堆,否則插入小頂堆,這樣就有可能出現兩個堆中數據不符合前面約定,我們將一個堆的堆頂插入另一個堆直到滿足要求即可。此時插入數據的時間複雜度爲O(logn),取出中位數的時間複雜度爲O(1),求其它百分位數據是同樣道理

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章