堆排序
前言
堆排序、快速排序、歸併排序的平均時間複雜度都為,由於堆排序對原始記錄的排序狀態並不敏感,因此無論是最好、最壞和平均時間複雜度均為。這在效能上顯然要遠遠好於氣泡排序、簡單選擇排序、直接插入排序的的時間複雜度了。
空間複雜度上,它只有乙個用來交換的暫存單元,也非常不錯。不過由於記錄的比較與交換是跳躍式進行,因此堆排序是一種不穩定的排序演算法
此外,由於初始構建堆所需的比較次數較多,因此,它並不適合待排序序列個數較少的情況。
二叉堆
二叉堆是具有下列性質的完全二叉樹
(1) 二叉堆的父節點的值總是大於或等於(小於或等於)其左右孩子的值;
(2) 每個節點的左右子樹都是這樣的一棵二叉堆。
每個節點的值都大於或等於其左右孩子節點的值,稱為大頂堆;或者每個節點的值都小於或等於其左右孩子節點的值,稱為小頂堆。
堆排序演算法
堆排序(heap sort)就是利用堆(假設利用大頂堆)進行排序的方法。它的基本思想是:將待排序的序列構造成乙個大頂堆。此時,整個序列的最大值就是堆頂的根節點。將它移走(其實就是講其與堆陣列的末尾元素交換,此時末尾元素就是最大值),然後將剩餘的n-1個序列重新構造成乙個堆,這樣就會得到n個元素中的次大值。如此反覆執行,便能得到乙個有序序列了。
由於輸入的是乙個無序數列,因此要實現堆排序,需要解決以下兩個問題:
(1) 如何將乙個無序序列建成乙個堆(二叉堆構建)
(2) 在去掉堆頂元素後,如何將剩下的元素調整為乙個二叉堆(二叉堆調整)
針對第乙個問題,可能很明顯會想到利用堆的插入操作,乙個乙個的插入元素,每次插入後調整元素的位置,使得新的序列依然為二叉堆。這種操作一般是自底向上的調整操作,即先將待插入元素放在二叉堆後面,而後逐漸向上將其與父節點比較,進而調整位置。但根據二叉堆的特點,完全用不著這麼做,我們只需先解決第二個問題,自然第乙個問題也就迎刃而解了。
二叉堆調整
假設我們排序的目標是從小到大,因此我們使用大頂堆;我們將二叉堆中的元素以程式遍歷後的順序儲存在一維陣列中,根節點在陣列中的位置序號為0。
這樣,如果某個節點的位置序號為i,那麼它的左右孩子的位置序號分別為2i+1和2i+2。
為了使調整過程更容易理解,我們採用如下的二叉堆來分析:
這裡陣列a中的元素個數為8,很明顯最大值為a0,為了實現排序後的元素按照從小到大的順序排列,我們可以將二叉堆中的最後乙個元素a7與a0交換,這樣a7中儲存的就是陣列中的最大值,而此時二叉堆變成如下情況:
為了將其調整為二叉堆,我們需要尋找4應該插入的位置。為此,我們讓4與它的孩子節點中的最大的那個,也就是其左孩子7,進行比較,由於4<7,將4與7交換,這樣二叉樹變成如下的形式:
接下來,繼續讓4與其左右孩子節點中的最大者,即6,進行比較,同樣由於4<6,交換4與6,這樣二叉樹變成如下形式:
這樣便又構成二叉堆,這時a0為7,是所有元素中的最大值。同樣,我們將二叉堆中的最後乙個元素a6與a0交換,這樣a6中儲存的就是陣列中第二大元素6,而a0就變成了3,如下圖所示:
為了將其調整成為二叉堆,將3與其左右孩子中的最大者即6進行比較,由於3<6,因此將3與6交換,繼續比較交換後的二叉樹中3和其左右孩子中的最大者4,得到以下二叉堆:
同樣將a0與此時堆中的最後乙個元素a5交換,同樣a5儲存的就是陣列中第三大元素,再次調整剩餘的節點,如此反覆,直到堆中僅剩最後乙個元素,這時整個陣列便已按照從小到大的順序排列好了。
**:
/*******************
* heap sort
* * date: 2017/0624
* * ****************/
#include
using namespace std;
/* *
* arr[start+1...end]滿足最大堆定義,
* 將arr[start]加入到最大堆arr[start+1...end]中,
* 調整arr[start]的位置,使arr[start...end]也成為最大堆。
* 注:由於陣列從0開始計算序號,也就是二叉堆的根節點序號為0,
* 因此序號為i的節點的左右孩子節點序號分別為2i+1, 2i+2
**/void heapadjustdown(int* arr, int start, int end)
arr[start] = temp;
}//堆排序後的順序為從小到大,因此建立大頂堆
void heapsort(int* arr, int len)
}int main();
cout << "original array: ";
for(int i=0; i<10; i++)
cout << a[i] << " ";
cout << endl;
heapsort(a, 10);
cout << "heapsorted array: ";
for(int i=0; i<10; i++)
cout << a[i] << " ";
cout << endl;
return 0;
}
執行結果:
總結
複雜度:我們在重新調整堆時,都要將父節點與孩子節點進行比較,這樣,每次重新調整堆的時間複雜度變為,而堆排序有n-1次重新調整堆的操作,建堆時有((len-1)/2+1)次重新調整堆的操作,因此堆排序的平均時間複雜度為。由於我們這裡沒有借助輔助儲存空間,因此空間複雜度為。
堆排序在排序元素較少時有點大材小用,待排序元素較多時,堆排序還是很有效的。另外,堆排序在最壞情況下,時間複雜度也為。相對於快速排序(平均複雜度),最壞情況下,這是堆排序最大的優點。
排序演算法之堆排序
前言 今天我來介紹下堆排序,在寫堆排序 之前,我們要知道堆的概念!堆的定義 n個關鍵字序列kl,k2,kn稱為 heap 當且僅當該序列滿足如下性質 簡稱為堆性質 1 ki k 2i 且ki k 2i 1 1 i n 當然,這是小根堆,大根堆則換成 號。k i 相當於二叉樹的非葉子結點,k 2i 則...
排序演算法之堆排序
堆排序演算法是選擇排序的一種,該演算法只是通過堆,最大堆 或者最小堆選擇出乙個待排序序列中的最大值,或者最小值。要想實現堆排序演算法,就需要構建什麼堆,這裡也最小堆為例。說明什麼是堆,怎麼構建乙個堆。假設待排序序列為a n 為乙個陣列。陣列的長度為n 陣列下標為 0,1,2,i,2i,2i 1 n ...
排序演算法之堆排序
宣告 本博文 為樓主親自編寫並測試,其它內容引用至我一直很崇拜的牛人morewindows。他對排序演算法的講解通俗易懂,給人一種耳目一新的感覺。堆排序與快速排序 歸併排序 一樣都是時間複雜度為o n logn 的幾種常見排序方法。最小堆的講解以及最小堆元素的插入和刪除參見最小堆操作。以下繼續引用以...