八大排序算法-堆排序

基本思想

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

堆的定義如下:具有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>

/** 
 * 已知H[s…m]除了H[s] 外均滿足堆的定義 
 * 調整H[s],使其成爲大頂堆.即將對第s個結點爲根的子樹篩選,  
 * 
 * @param H是待調整的堆數組 
 * @param s是待調整的數組元素的位置 
 * @param length是數組的長度 
 * 
 */  
 void heapAdjust(int H[],int s,int length){
    int temp = 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] = temp; // 當前待調整的結點放到比其大的孩子結點位置上  
    }   
 } 
 /** 
 * 初始堆進行調整 
 * 將H[0..length-1]建成堆 
 * 調整完之後第一個元素是序列的最小的元素 
 */  
void buildHeap(int H[],int length){
    int i;
    for(i=(length-1)/2;i>=0;i--){//最後一個有孩子的節點的位置 i=  (length -1) / 2  
        heapAdjust(H,i,length);
    }   
}
/** 
 * 堆排序算法 
 */  
 void heapSort(int H[],int length){
    int i,temp;
    buildHeap(H,length);//初始化堆
    for(i=length-1;i>=0;i--){//從最後一個元素開始對序列進行調整  
        temp = H[i]; //交換堆頂元素H[0]和堆中最後一個元素  
        H[i] = H[0];
        H[0] = temp;
        heapAdjust(H,0,i);//每次交換堆頂元素和堆中最後一個元素之後,都要對堆進行調整  
    }
 } 
/** 
 * 打印 
 */  
void print(int r[],int n){
    int i;
    for(i=0;i<n;i++){
        printf("%3d",r[i]);
    }
    printf("\n");
}

int main(){
    int H[10] = {3,1,5,7,2,4,9,6,10,8};  
    printf("排序前:\n");
    print(H,10); 
    printf("堆排序後:\n");
    heapSort(H,10);
    print(H,10);

    return 0;
 }

執行結果

這裏寫圖片描述

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