經典排序算法之堆排序

首先介紹堆的定義:堆是具有下列性質的完全二叉樹:每個結點的值都大於或等於其左右孩子結點的值,稱爲大頂堆;或者每個結點的值都小於或等於其左右孩子結點的值,稱爲小頂堆。由於堆的表象結構是一棵完全二叉樹,我們稱之爲二叉堆。下面分別就是兩個大頂堆和小頂堆:




隨後我們都是以大頂堆爲例來介紹堆排序的,小頂堆也是一樣的。


對於大頂堆,我們直到,根結點肯定是整個堆中最大的結點,因此我們如果每次都取出根結點,然後再將堆的剩餘元素重新調整成大頂堆,再取根結點,即可以獲得一個有序序列。但是這裏要考慮到幾個問題:


1、怎麼存儲這個大頂堆?

2、如何初始化一個大頂堆?

3、每次取出根結點後該如何調整成一個新的大頂堆?


大頂堆的存儲

從上面的介紹我們可以看出,大頂堆是一個完全二叉樹結構,但是,如果使用二叉樹來存儲這個二叉堆,對於整個過程中將經常出現的遍歷會非常浪費時間。因此我們直接選擇使用一個數組來存儲。但是數組如何才能表示一個二叉堆呢?這就是存儲的關鍵。我們直到,完全二叉樹的特點是:假如以層序遍歷的順序對每個結點進行標號(從0開始),則對與編號爲i的結點,其父親結點編號爲(i - 1)/2,左孩子結點編號爲2 * i + 1,右孩子爲2 * i + 2。我們可以使用一個數組來表示一棵二叉堆的層序遍歷結果,然後標號對應每個結點在數組中的下標即可。

每次取出根結點後該如何調整成一個新的大頂堆


假設現在有一個大頂堆,如果取出根結點,當然我們總不能讓根結點爲空來調整吶,有一個一舉兩得的辦法就是,將根結點的元素與堆中最後一個元素互換一下,這樣就可以每次都將最大的元素存放在最後的位置了。然後問題就轉換成了將根結點“下沉”從而來調整成大頂堆問題了。如圖:



將末尾元素換到根部之後,然後開始“下沉”,下沉的過程中與兩個孩子結點互相比較,取最大者互換,若根結點本身最大則無需互換了。圖中將20換到根部,然後20、70、80中80最大,20與80互換,以此類推,一直"下沉"到合適的位置。

將這個“下沉”的過程量化:假設需要“下沉”的元素下標爲i,則i處元素將與兩個孩子2*i+1和2*i+2處的元素比較,取較大者交換。“下沉”的終止條件是:左孩子2*i+1已經超出數據下標界限n-1;下沉過程的代碼如下:

void ElemDown(int *array, int index, int arrayCount)
{
	while(2 * index + 1 < arrayCount - 1)
	{
		if(2 * index + 2 < arrayCount - 1)  //右孩子也存在
		{
			if(array[index] >= array[2 * index + 1] && array[index] >= array[2 * index + 2])  //比兩個孩子都大
				break;
			int bigIndex = array[2 * index + 1] >= array[2 * index + 2]?2 * index + 1:2 * index + 2;  //取較大孩子下標
			int temp = array[bigIndex];  //互換元素
			array[bigIndex] = array[index];
			array[index] = temp;
			index = bigIndex;  //繼續下沉
		}
		else  //只存在左孩子,則左孩子肯定是葉子結點
		{
			if(array[index] < array[2 * index + 1])  //比左孩子小則互換
			{
				int temp = array[index];
				array[index] = array[2 * index + 1];
				array[2 * index + 1] = temp;
			}
			break;  //都是要結束了
		}
	}
}

初始化一個大頂堆

現在有一個亂序的序列,在排序的第一步我們要做的就是將此數組調整成一個大頂堆。以下面的例子爲例:



由於完全二叉樹的特性,在層序排列的序列中,葉子結點總是全部排在後面,而所有擁有孩子的父結點總是排在前面。假設結點個數爲n,則最後一個結點的下標爲n-1,則其父親結點的下標爲n/2 - 1。即整個序列,0到n-1,父親結點是從0到n/2-1,葉子結點是從n/2到n-1。我們可以從最後一個父結點開始,以該父結點爲根結點的子樹中我們構造大頂堆,那麼可以保證每次選取一個父結點的時候,其左右子樹均已經是一個大頂堆了,則可以對該父結點做前面所說的“下沉”操作了。上面的例子,最後一個父結點是30,以30爲根結點所在的樹(紅色標記內的),對30做“下沉”操作。下一個父結點是90,然後對其做“下沉”操作。再下一個父結點是10,此時10的兩個左右子樹均爲大頂堆了,則可以直接對10做“下沉操作”。以此類推,直到整棵樹的根結點做完“下沉”爲止,此時,我們便將一棵亂序的完全二叉樹變成一棵大頂堆了。代碼如下:

void MakeBigHeap(int *array, int arrayCount)
{
	int fatherNo = arrayCount/2 - 1;  //每顆子樹的父結點下標
	for(; fatherNo >= 0; --fatherNo)
		ElemDown(array, fatherNo, arrayCount);
}

好了,現在可以對其進行排序了。對於給定的一個亂序數組,第一步將其初始化爲一個大頂堆,然後每次將根結點跟最後一個結點互換,然後在對這個新的(除去最後一個元素)的樹進行“下沉”操作(因爲此時只有根結點是不在原位的,其左右子樹都是大頂堆)。最後所得到的就是一個升序的序列了。代碼如下:

void HeapSort(int array[], int arrayCount)
{
	MakeBigHeap(array, arrayCount);  //構造大頂堆
	int temp;
	for(int newArrayCount = arrayCount; newArrayCount > 0; --newArrayCount)  //去除根結點
	{
		temp = array[0];
		array[0] = array[newArrayCount - 1];
		array[newArrayCount - 1] = temp;
		ElemDown(array, 0, newArrayCount);
	}
}

到此爲止,整個堆排序就結束了。

下面是一個完整的程序來測試一下堆排序。

#include <iostream>
using namespace std;

void ElemDown(int *array, int index, int arrayCount)
{
	while(2 * index + 1 < arrayCount - 1)
	{
		if(2 * index + 2 < arrayCount - 1)  //右孩子也存在
		{
			if(array[index] >= array[2 * index + 1] && array[index] >= array[2 * index + 2])  //比兩個孩子都大
				break;
			int bigIndex = array[2 * index + 1] >= array[2 * index + 2]?2 * index + 1:2 * index + 2;  //取較大孩子下標
			int temp = array[bigIndex];  //互換元素
			array[bigIndex] = array[index];
			array[index] = temp;
			index = bigIndex;  //繼續下沉
		}
		else  //只存在左孩子,則左孩子肯定是葉子結點
		{
			if(array[index] < array[2 * index + 1])  //比左孩子小則互換
			{
				int temp = array[index];
				array[index] = array[2 * index + 1];
				array[2 * index + 1] = temp;
			}
			break;  //都是要結束了
		}
	}
}

void MakeBigHeap(int *array, int arrayCount)
{
	int fatherNo = arrayCount/2 - 1;
	for(; fatherNo >= 0; --fatherNo)
		ElemDown(array, fatherNo, arrayCount);
}

void HeapSort(int array[], int arrayCount)
{
	cout<<"before sort: \n";
	for(int index = 0; index < arrayCount; ++index)
		cout<<array[index]<<" ";

	MakeBigHeap(array, arrayCount);  //構造大頂堆

	cout<<"\nafter make big sort: \n";
	for(int index = 0; index < arrayCount; ++index)
		cout<<array[index]<<" ";

	int temp;
	for(int newArrayCount = arrayCount; newArrayCount > 0; --newArrayCount)  
	{
		temp = array[0];
		array[0] = array[newArrayCount - 1];
		array[newArrayCount - 1] = temp;
		ElemDown(array, 0, newArrayCount);
	}

	cout<<"\nafter sort: \n";
	for(int index = 0; index < arrayCount; ++index)
		cout<<array[index]<<" ";
}

void main()
{
	int array[] = {9, 1, 5, 8, 3, 7, 4, 6, 2};
	HeapSort(array, 9);

	system("pause");
}

運行結果如下:




發佈了68 篇原創文章 · 獲贊 42 · 訪問量 22萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章