堆排序

(1)堆的概念:對n個關鍵字序列k1,k2,k3,...,kn,當且僅當滿足下述關係是成爲堆:

   ki<=k(2i)且ki<=k(2i+1)(1≤i≤ n/2),當然,這是小根堆,大根堆則換成>=號。k(i)相當於二叉樹的非葉子結點,K(2i)則是左子節點,k(2i+1)是右子節點。

    若將此序列所存儲的向量k[1..n]看做是一棵完全二叉樹的存儲結構,則堆實質上是滿足如下性質的完全二叉樹:樹中任一非葉子結點的關鍵字均不大於(或不小於)其左右孩子(若存在)結點的關鍵字。

(2)堆排序思想(小根堆)

    對n個待排序的記錄,首先根據各記錄的關鍵字按堆的定義排成一個序列(即建立初始堆),從而由堆頂得到最小關鍵字的記錄,如此反覆進行出堆和將剩餘記錄調整爲堆的過程,當堆僅剩下一個記錄出堆時,則n個記錄已按出堆的次序排成有序序列。因此,堆排序的過程分爲兩步:

    a、建立初始堆

    首先將待排序的n個關鍵字分別放到一顆完全二叉樹的各個節點,此時完全二叉樹各個節點不一定具備堆得性質。由二叉樹的性質可知,所有序號大於n/2的樹葉節點已經是堆(因爲葉子節點沒有子節點)。故初始建堆事宜序號爲n/2的最後一個非終端節點開始堆的構建的,n/2,n/2-1,n/2-2,...,爲根節點的子樹滿足堆的定義,直到序號爲1的根節點爲止,則n個關鍵字構成一個堆。在對根節點序號爲i的子樹建堆過程中,可能要對節點的位置進行調整以滿足堆的定義。但是這種調整可能會導致原來的堆的下一次子樹不滿足堆的定義,這就需要對下一層進行調整,直到樹葉節點。

    b、調整成新堆

    堆頂點的關鍵字輸出後,如何將剩下的n-1個節點調整爲堆?首先,將堆中序號爲n的最後一個節點與待出堆的序號爲1的堆頂節點交換,這時只需要是序號從1~n-1的節點滿足堆的定義,即可由這1~n-1個節點構成新的堆。相對於原來的堆,此時堆頂節點發生了改變,而其餘n-2個節點任然滿足堆的定義,我們只需要堆這個新的堆頂節點進行調整就ok了。

       調整方法:將根節點與左、右孩子節點中關鍵值較小的節點進行交換,若與左孩子交換,則左子樹堆被破壞,且僅左子樹的根節點不滿足堆的定義。若與右孩子交換,則右子樹堆被破壞,且僅右子樹的根節點不滿足堆的定義。對不滿足堆定義的子樹繼續進行上述的交換操作,這種調整需要持續到葉節點或者到某個節點滿足堆的定義爲止。

(3)堆排序的過程

    堆n個關鍵字序列先建成堆(構建初始堆),然後執行n-1趟排序。第一趟將序號爲1的根節點與序號爲n的節點交換(第n個節點用於存儲出堆節點),並調整前n-1個節點爲新的堆;第二趟將序號爲1的根節點與序號爲n-1的節點交換(第n-1個節點用於存儲出堆節點),並調整前n-2個節點爲新的堆...第n-1趟將序號爲1的根節點與序號爲2的根節點交換。此時,待調整的堆僅爲序號爲1的根節點故無需進行調整,整個堆排序過程結束。

注意:小根堆,對應降序排列(小的被放到後邊,大的放到前邊);大根堆,對應升序排列(大的放後邊,小的放前邊)

<pre name="code" class="cpp">/********************************************************************
*This function to do HeapSort
*Author:xiefanfan
*Time:2015-04-04  10:25
*All Rights Reseved
********************************************************************/
#include "stdafx.h"
#include <stdio.h>

/*******************this function to do heap sort based on large root heap*************************/
/*********************對R[s]~R[t]除R[s]外均滿足堆得定義,即只對R[s]進行調整,使R[s]爲根的完全二叉樹成爲一個堆***********************/
void HeapAdjust(int R[],int s,int t)
{
	int i,j;
	int temp = R[s];               //temprory variable
	i=s;
	for (j=2*i;j<=t;j=2*j)         //沿關鍵字較大的孩子向下調整,先假定爲左孩子   
	{
		if (j<t&&R[j]<R[j+1])     //如果左孩子比右孩子小,則沿右孩子向下調整
		{
			j=j+1;
		}
		if (temp>R[j])            //如果temp比左右孩子都大,滿足大根堆的定義了,不在向下調整
		{
			break;
		}
		R[i]=R[j];                //將關鍵字較大的孩子節點和雙親節點交換
		i=j;                      //定位於孩子節點  繼續向下調整
	}
	R[i]=temp;                    //找到合適的位置,將temp放置進去
}

//進行堆排序,R[]是一個數組,n是數組的個數
void HeapSort(int R[],int n)
{
	int i;
	for (i=n/2;i>0;i--)              //完全二叉樹  非終端節點需要進行堆定義調整,從R[n/2],R[n/2-1],.....R[0]
	{
		HeapAdjust(R,i,n);
	}
	int temp;
	for (i=n;i>0;i--)                //對初始堆進行n-1趟排序
	{
		temp = R[1];
		R[1] = R[i];
		R[i] = temp;
		HeapAdjust(R,1,i-1);         //將未排序的前i-1個節點重新調整爲堆
	}	
}

int main()
{
	int shuzu[] = {0,61,33,48,82,72,11,25,48};
	HeapSort(shuzu,8);                 //調用堆排序函數
	int index;
	for(index=1;index<9;++index)
	{
		printf("%d\t",shuzu[index]);
	}
}

    堆排序花費的時間主要在構建初始堆和n-1趟堆排序。最壞情況下的堆排序時間複雜度爲O(nlogn),所以最壞情況下的堆排序時間複雜度要低於快速排序。

    由於堆排序初始建堆比較此時較多,因此堆排序不適宜記錄較少的情況。對大量記錄的排序來說,堆排序非常有效。此外,堆排序只需要一個輔助空間,空間複雜度爲O(1)。而且,堆排序是不穩定的方法。





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