目錄
一、堆的性質
二、堆的實現
1. 儲存方式
2. 向堆中插入元素
3. 刪除堆頂元素
4. 其他操作
5. 完整**
三、堆排序
1. 原地建堆
2. 排序
3. 完整**
4. 建堆的複雜度:o(n)
5. 堆排序的複雜度:o(nlogn)
6. 和快速排序的比較
堆是一種特殊的樹。
只要滿足以下兩點,它就是乙個堆:
第一點,堆必須是乙個完全二叉樹。完全二叉樹要求,除了最後一層,其他層的節點個數都是滿的,最後一層的節點都靠左排列,自然堆也具有完全二叉樹的所有性質。
第二點,堆中的每個節點的值必須大於等於(或者小於等於)其子樹中每個節點的值。實際上,我們還可以換一種說法,堆中每個節點的值都大於等於(或者小於等於)其左右子節點的值。這兩種表述是等價的。
對於每個節點的值都大於等於子樹中每個節點值的堆,我們叫做「大頂堆」。對於每個節點的值都小於等於子樹中每個節點值的堆,我們叫做「小頂堆」。
以下講解都用大根堆為例。
因為堆是完全二叉樹,而用陣列來儲存完全二叉樹是非常節省儲存空間的,所以堆也選擇陣列儲存。
class dui
...};
這裡我們定義乙個堆的類,n代表我們堆中的元素個數,所以建構函式預設n是0,另外我們定義乙個陣列a,建構函式中可以自定義堆的大小,預設是100;當然陣列也可以直接用vector。
用陣列的1號下標儲存根節點。那麼對於一般的節點 i,其左孩子為 i*2,右孩子為 i*2+1,其父親節點則為 i/2。(當然所有派生出來的節點x都要先判斷 0
為了保證其完全二叉樹的性質,所以插入元素的位置,一定是n的下乙個位置;
為了保證其堆的性質,插入乙個元素後我們需要進行調整,這個過程稱之為堆化。
例如我們在如下圖所示堆中插入下標為14的節點22。
因為22大於其父親節點9,這不滿足大根堆的性質,所以我們要執行交換,以此類推,直至不在發生交換。如下圖:
像這種順著節點所在的路徑,依次向上對比,然後交換的操作,我們稱之為自下而上的堆化。因為插入節點是在最底層,所以插入操作適合自下而上的堆化方法。
void dui::push(int x)
}
為了滿足完全二叉樹的性質,我們的刪除步驟是用最後乙個節點覆蓋堆頂,然後再從堆頂去維護堆的性質。
例如下圖我們刪除節點12。我們先用12替換(覆蓋)節點33,然後再從上往下依次比較,交換,這種方式稱之為自上而下的堆化。因為我們是先從根開始調整的,所以刪除操作適合自上而下的堆化方法。
這裡還要注意,自上而下的堆化過程比自下而上的方法要複雜點兒,因為左右孩子是需要去選擇的,而父親節點只有乙個。
void dui::pop() //堆的大小
int top() //返回堆頂
bool empty() //判斷堆是否為空
這些操作都很簡單,不再贅述。
#includeusing namespace std;
class dui
int size() //堆的大小
int top() //返回堆頂
bool empty() //判斷堆是否為空
void push(int x); //插入元素至堆
void pop(); //刪除堆頂元素
};void dui::push(int x)
}void dui::pop()
...}
建好堆後,堆頂也就是a[1],即是堆的最大值,我們把它跟最後乙個元素交換,那最大元素就放到了下標為 n 的位置。
這個過程類似於「刪除堆頂元素」的操作,刪除之後我們堆化剩餘的n-1個元素(最後乙個元素已經是有序的,不再考慮),然後可以得到剩下的 n−1 個元素重構的堆。
再交換,堆化,直至堆中只剩乙個元素,排序結束。
過程如下圖:
**: 需要注意的是,堆的大小是一直在減小的,heapful的堆大小引數是i-1。
//排序過程:交換 維護
for (int i=n; i>1; i--)
#includeusing namespace std;
void heapful(int a,int n,int i)
//排序過程:交換 維護
for (int i=n; i>1; i--)
}int main(){
int n;
int p[1000];
cin>>n;
for (int i=1; i<=n; i++) cin>>p[i];
sort(p,n);
for (int i=1; i<=n; i++) cout一直以為建堆的複雜度和向堆中插入元素的複雜度是一樣的,都是o(nlogn),沒想到是o(n)。
淺顯的理解:葉子節點不需要堆化。
證明:建堆複雜度o(n),排序複雜度n*logn,總複雜度o(nlogn)。並且堆排序是不穩定的。
為什麼快速排序要比堆排序效能好?
第一點,堆排序資料訪問的方式沒有快速排序友好。對於快速排序來說,資料是順序訪問的。而對於堆排序來說,資料是跳著訪問的。 比如,堆排序中,最重要的乙個操作就是資料的堆化。比如下面這個例子,對堆頂節點進行堆化,會依次訪問陣列下標是 1,2,4,8 的元素,而不是像快速排序那樣,區域性順序訪問,所以,這樣對 cpu 快取是不友好的。
第二點,對於同樣的資料,在排序過程中,堆排序演算法的資料交換次數要多於快速排序。我們在講排序的時候,提過兩個概念,有序度和逆序度。對於基於比較的排序演算法來說,整個排序過程就是由兩個基本的操作組成的,比較和交換(或移動)。快速排序資料交換的次數不會比逆序度多。
堆的實現及堆排序
前兩天刷筆試題,判斷乙個陣列的序列可以構成堆。仔細想了想,腦海裡幾乎已經遺忘了堆的知識,今天又重新去看書,把堆的知識總結一下。首先堆是一種陣列物件,它可以被看成乙個完全二叉樹。在我們常見的堆中有大堆和小堆。對大堆來說,每個父節點都大於孩子結點 小堆恰好相反。而且,大堆 小堆的每個子樹也是乙個大堆 小...
堆以及堆排序實現
1.今天實現了一下堆排序,這裡的堆是指二叉堆,至於二項堆和斐波那契堆以後再說。先看二叉堆的定義 二叉堆是完全二叉樹或近似完全二叉樹。滿足特性是 父親節點的值大於等於 小於等於 左右孩子的值,並且左右子樹也是二叉堆。對應的二叉堆是大頂堆 小頂堆 二叉堆是完全二叉樹,可以用線性表來儲存。2.實現堆排序時...
堆排序 堆的模擬
一般用陣列來表示堆,下標從0開始。則下標為 i 的節點的父節點下標為 i 1 2,其左右子節點分別為 2i 1 2i 2 下標從1開始 左右節點2i 2i 1i 2。利用大頂堆 小頂堆 堆頂記錄的是最大 小 關鍵字這一特性,每次從無序陣列中選出最大 最小 值。1 將待排序序列造成乙個最大堆,此時根節...