1. 程式人生 > >排序演算法(四):堆排序(Heap Sort)

排序演算法(四):堆排序(Heap Sort)

堆排序是一種樹形選擇排序,是對直接選擇排序的有效改進。

基本思想:

堆的定義如下:具有n個元素的序列(k1,k2,...,kn),當且僅當滿足

時稱之為堆。由堆的定義可以看出,堆頂元素(即第一個元素)必為最小項(小頂堆)。 若以一維陣列儲存一個堆,則堆對應一棵完全二叉樹,且所有非葉結點的值均不大於(或不小於)其子女的值,根結點(堆頂元素)的值是最小(或最大)的。如:

(a)大頂堆序列:(96, 83,27,38,11,09)

  (b)  小頂堆序列:(12,36,24,85,47,30,53,91)

 

初始時把要排序的n個數的序列看作是一棵順序儲存的二叉樹(一維陣列儲存二叉樹)

,調整它們的儲存序,使之成為一個堆,將堆頂元素輸出,得到n 個元素中最小(或最大)的元素,這時堆的根節點的數最小(或者最大)。然後對前面(n-1)個元素重新調整使之成為堆,輸出堆頂元素,得到n 個元素中次小(或次大)的元素。依此類推,直到只有兩個節點的堆,並對它們作交換,最後得到有n個節點的有序序列。稱這個過程為堆排序

因此,實現堆排序需解決兩個問題: 1. 如何將n 個待排序的數建成堆; 2. 輸出堆頂元素後,怎樣調整剩餘n-1 個元素,使其成為一個新堆。

 

首先討論第二個問題:輸出堆頂元素後,對剩餘n-1元素重新建成堆的調整過程。 調整小頂堆的方法:

1)設有m 個元素的堆,輸出堆頂元素後,剩下m-1 個元素。將堆底元素送入堆頂((最後一個元素與堆頂進行交換),堆被破壞,其原因僅是根結點不滿足堆的性質。

2)將根結點與左、右子樹中較小元素的進行交換。

3)若與左子樹交換:如果左子樹堆被破壞,即左子樹的根結點不滿足堆的性質,則重複方法 (2).

4)若與右子樹交換,如果右子樹堆被破壞,即右子樹的根結點不滿足堆的性質。則重複方法 (2).

5)繼續對不滿足堆性質的子樹進行上述交換操作,直到葉子結點,堆被建成。

稱這個自根結點到葉子結點的調整過程為篩選。如圖:

 

再討論對n 個元素初始建堆的過程。 建堆方法:對初始序列建堆的過程,就是一個反覆進行篩選的過程。

1)n 個結點的完全二叉樹,則最後一個結點是第個結點的子樹。

2)篩選從第個結點為根的子樹開始,該子樹成為堆。

3)之後向前依次對各結點為根的子樹進行篩選,使之成為堆,直到根結點。

如圖建堆初始過程:無序序列:(49,38,65,97,76,13,27,49)                              

                             

 

 演算法的實現:

從演算法描述來看,堆排序需要兩個過程,一是建立堆,二是堆頂與堆的最後一個元素交換位置。所以堆排序有兩個函式組成。一是建堆的滲透函式,二是反覆呼叫滲透函式實現排序的函式。

#include <stdio.h>
#include <iostream>
using namespace std;

void print(int a[],int i)
{  
    cout<<i <<" : ";  
    for(int j= 0; j<10; j++)
    {  
        cout<<a[j] <<" ";  
    }  
    cout<<endl;  
}  
/** 
 * 已知H[s…m]除了H[s] 外均滿足堆的定義 
 * 調整H[s],使其成為大頂堆.即將對第s個結點為根的子樹篩選,  
 * 
 * @param H是待調整的堆陣列 
 * @param s是待調整的陣列元素的位置 
 * @param length是陣列的長度 
 * 
 */  
void HeapAdjust(int H[],int s, int length)  
{  
    int tmp  = H[s];  
    int child = 2*s+1; //左孩子結點的位置。(i+1 為當前調整結點的右孩子結點的位置)  
    while (child < length) {  
        if(child+1 <length && H[child]<H[child+1]) { // 如果右孩子大於左孩子(找到比當前待調整結點大的孩子結點)  
            ++child ;  
        }  
        if(H[s]<H[child]) {  // 如果較大的子結點大於父結點  
            H[s] = H[child]; // 那麼把較大的子結點往上移動,替換它的父結點  
            s = child;       // 重新設定s ,即待調整的下一個結點的位置  
            child = 2*s+1;  
        }  else {            // 如果當前待調整結點大於它的左右孩子,則不需要調整,直接退出  
             break;  
        }  
        H[s] = tmp;         // 當前待調整的結點放到比其大的孩子結點位置上  
    }  
    print(H,length);  
}  
  
  
/** 
 * 初始堆進行調整 
 * 將H[0..length-1]建成堆 
 * 調整完之後第一個元素是序列的最小的元素 
 */  
void BuildingHeap(int H[], int length)  
{   
    //最後一個有孩子的節點的位置 i=  (length -1) / 2  
    for (int i = (length -1) / 2 ; i >= 0; --i)  
        HeapAdjust(H,i,length);  
}  
/** 
 * 堆排序演算法 
 */  
void HeapSort(int H[],int length)  
{  
    //初始堆  
    BuildingHeap(H, length);  
    //從最後一個元素開始對序列進行調整  
    for (int i = length - 1; i > 0; --i)  
    {  
        //交換堆頂元素H[0]和堆中最後一個元素  
        int temp = H[i]; H[i] = H[0]; H[0] = temp;  
        //每次交換堆頂元素和堆中最後一個元素之後,都要對堆進行調整  
        HeapAdjust(H,0,i);  
  }  
} 
int main()
{  
    int H[10] = {3,1,5,7,2,4,9,6,10,8};  
    HeapSort(H,10);  
    return 0;  
  
}  

 

執行結果:

10 : 3 1 5 7 8 4 9 6 10 2 
10 : 3 1 5 10 8 4 9 6 7 2 
10 : 3 1 9 10 8 4 5 6 7 2 
10 : 3 10 9 7 8 4 5 6 1 2 
10 : 10 8 9 7 3 4 5 6 1 2 
9 : 9 8 5 7 3 4 2 6 1 10 
8 : 8 7 5 6 3 4 2 1 9 10 
7 : 7 6 5 1 3 4 2 8 9 10 
6 : 6 3 5 1 2 4 7 8 9 10 
5 : 5 3 4 1 2 6 7 8 9 10 
4 : 4 3 2 1 5 6 7 8 9 10 
3 : 3 1 2 4 5 6 7 8 9 10 
2 : 2 1 3 4 5 6 7 8 9 10 
1 : 1 2 3 4 5 6 7 8 9 10