演算法基礎之五(堆排序),演算法基礎之五堆排序

來源:互聯網
上載者:User

演算法基礎之五(堆排序),演算法基礎之五堆排序

堆排序,首先要瞭解一下這裡的堆是什麼,這裡的堆其實就是二叉樹,很形象是不是,完整的二叉樹從頭看起,就是一個三角形,也可以看成一個“堆”。


一、數群組轉換成堆

        那麼首先要解決的問題就是給數組排序,如何轉換成二叉樹的?轉換方法

數組 int a[],包含元素a[0],a[1],a[2],a[3].....等等。

轉換成二叉樹:



        圖還是比較形象的,其實就是依次從堆頂向下排,但是有一個原則,就是上一層沒有排滿的時候,下一層不會有元素,有限排滿上一層,才考慮下一層。


二、如何進行堆排序


那麼第二個問題來了,我們怎麼才能對其進行排序呢。

在考慮這個問題之前,我們需要先進行一些前序工作:


1、已知一個堆中的元素a[i],它的父節點是誰?左右子節點呢(如果有)?

        通過觀察不難得出,如果像我們這樣,數組序號從0開始排起的話,元素a[i]的父節點(當然i不等於0時)是a[(i+1)/2 - 1],而左右子節點分別是a[2*i+1]和a[2*i+2];


2、引入大根堆的概念。

        大根堆:除根節點以外的所有及誒單a[i]都要滿足:a[PARENT(i)]>=a[i],這裡parent(i)表示i的父節點的下標值;顯然如果我們將一個樹變成了一個大根堆,我們就可以得到一個當前樹的最大值,也就是這個堆的頂部節點的值,由大根堆的性質可知。


3、對於一個左右子樹已經是大根堆的根節點,怎樣將以該節點為根的二叉樹轉換成大根堆?

        顯然,如果根節點比左右子節點要大,那麼已經是大根堆


        如果根節點比左右子節點要小,或者比其中一個要小?我們就需要將最大的那個子節點與根節點交換位置;但是這還沒有完,因為原根節點交換到它的直接子節點位置之後,還不一定會滿足大根堆的條件限制,如果不滿足,我們就需要繼續剛才的工作;

看看下面的代碼:


/** * 在左右子樹都是大根堆的情況下,形成新的大根堆 */void maxHeapIndex(int a[], int root, int maxSize) {//得到左子節點的下標int l = leftChildIndex(root);//得到右子節點的下標int r = rightChildIndex(root);//先預設最大節點的下標值為當前根節點int largest = root;//如果左節點存在且左節點的值大於根節點,則標記最大值為左節點if (l <= maxSize && a[l] > a[root]) {largest = l;}//如果左節點存在且右節點的值大於根節點,則標記最大值為右節點if (r <= maxSize && a[r] > a[largest]) {largest = r;}//經過標記判斷,如果發現最大值確實不是根節點,則進行交換值,且進行遞迴調用看看交換之後的根節點是否滿足大根堆性質if (root != largest) {swap(a + root, a + largest);maxHeapIndex(a, largest, maxSize);}}/* * 交換 */void swap(int* a, int* b) {int tmp = *a;*a = *b;*b = tmp;}/* * 得到左子節點的下標 */int leftChildIndex(int i) {return (i + 1) * 2 - 1;}/* * 得到右子節點的下標 */int rightChildIndex(int i) {return (i + 1) * 2;}

4、堆中節點的下標關係。

        經過第3點,已經搞定了在左右子樹都是大根堆的情況下,形成一個新的大根堆;那麼我們如何讓一顆沒有處理過的堆來形成一個大根堆呢?如何找到左右子樹都是大根堆的情況呢?找不到,那麼不如先不要左右子樹,那就是葉子節點,葉子節點沒有左右子樹,換言之它們就是天然的單節點大根堆;所以葉子節點的父節點都是滿足3所假設的條件的,那麼對於一個堆而言,葉子節點的父節點的下標的範圍是多少呢?


        對於一個滿節點二叉樹而言,如果有h層,比如中到a[15]處,有三層;對於h層的滿節點二叉樹,它的節點個數是2*2^h-1,2^h即2的h次冪,葉子節點的個數為2^h,而除葉子節點之外的其他節點的個數為2^h-1個。我們不難得出,如果數組下標從0開始算起的話,除葉子節點以外的其他節點的下標範圍應該是[0,2^h-2]。


        所以我們只需要從2^h-2位置的節點開始,也就是倒數第二層的最右邊的節點開始。而對於高為h層,節點數為n的堆而言,h和n有如下關係:h=不小於lgn的最接近的整數;知道了上面這些,我們就可以從2^h-2位置開始一直到0位置一直迴圈執行第3點來實現形成一個大根堆:

/** * 產生一個大根堆,思路: * 從倒數第二層的最右邊的節點開始,依次調用maxHeapIndex * */void maxHeapBuild(int a[], int len) {//得到高度int h = log2(len);//得到可用的最大的下標值int max = (int) (pow(2, h)) - 2;//迴圈調用for (int i = max; i >= 0; i--) {maxHeapIndex(a, i, len - 1);}}/** * 得到log2為底的值 */int log2(int i) {//ceil函數返回一個不小於某值的整數return (int) ceil(log(i) / log(2));}



三、堆排序過程

        經過上面四步,我們已經可以得到一個大根堆了,接下來的工作就是如何利用大根堆來進行排序。


        對於大根堆而言,我們唯一可以確定的是,它的頂部的那個元素一定是當前堆中最大的元素,於是我們可以每次將頂部的元素和最右邊的葉子節點也就是最後一個葉子節點進行交換,然後拿出這個最大值,並且將堆的節點數減一,然後重新對頂點也就是剛才那個交換到頂點位置的葉子節點進行上面3、中的操作,來重新讓新堆變成一個大根堆,然後我們依次迴圈執行上面的操作,直到取出堆中的所有元素,這樣,我們從開始到最後取出的元素是按照從大到小的順序,也就完成了排序。


這一步的代碼很簡單:

/* * 堆排序,思路: * 先進行一次maxHeapBuild,使得root節點為最大的值 * 然後將root節點和最後一個節點的值進行交換 * 然後將最後一個節點斷開(maxsize-=1) * 然後進行maxheapify,因為這時候根節點的左右子樹依舊是大根堆,所以只需要maxheapify而不需要maxHeapBuild * 這樣從n-1開始迴圈到2的時候即可 */void maxHeapSort(int a[], int maxSize, int tmp[]) {maxHeapBuild(a, maxSize); //nfor (int i = maxSize; i > 0;) { //迴圈n-1次swap(a, a + i);tmp[i] = a[i];i--;if (i == 0) {tmp[0] = a[0];} else {maxHeapIndex(a, 0, i); //logn 執行了n-2次}}for (int i = 0; i <= maxSize; i++) {a[i] = tmp[i];}}


四、堆排序時間複雜度

        對於堆排序演算法,我們簡單的看一下它的時間複雜度,一次maxHeapBuild的時間複雜度是n,然後迴圈n-1的maxHeapIndex時間複雜度為nlogn,迴圈賦值操作複雜度為n,所以算起來,時間複雜度為nlogn。



聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.