排序演算法(四):堆排序(Heap Sort)
堆排序是一種樹形選擇排序,是對直接選擇排序的有效改進。
基本思想:
堆的定義如下:具有n個元素的序列(k1,k2,...,kn),當且僅當滿足
時稱之為堆。由堆的定義可以看出,堆頂元素(即第一個元素)必為最小項(小頂堆)。 若以一維陣列儲存一個堆,則堆對應一棵完全二叉樹,且所有非葉結點的值均不大於(或不小於)其子女的值,根結點(堆頂元素)的值是最小(或最大)的。如:
(a)大頂堆序列:(96, 83,27,38,11,09)
(b) 小頂堆序列:(12,36,24,85,47,30,53,91)
初始時把要排序的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