1. 程式人生 > >堆與堆排序

堆與堆排序

分別是 步驟 最大的 第一次 img 二叉 wrap kaa lang

堆排序與快速排序,歸並排序一樣都是時間復雜度為O(N*logN)的幾種常見排序方法。

堆排序是就地排序,輔助空間為O(1)。

它是不穩定的排序方法。(排序的穩定性是指如果在排序的序列中,存在前後相同的兩個元素的話,排序前 和排序後他們的相對位置不發生變化)

先說說什麽是堆,堆通常是一個可以被看做一棵樹的數組對象。滿足下列性質:

1.堆中某個節點的值總是不大於或不小於其父節點的值;

2.堆總是一棵完全樹(完全樹就是葉結點僅在層次最大的兩層出現的樹)。

將根節點最大的堆叫做最大堆或大根堆,根節點最小的堆叫做最小堆或小根堆。常見的堆有二叉堆、斐波那契堆等。由於其它幾種堆(二項式堆,斐波納契堆等)用的較少,一般將二叉堆就簡稱為堆。

堆的存儲

一般都用數組來表示堆,i結點的父結點下標就為(i – 1) / 2。它的左右子結點下標分別為2 * i + 1和2 * i + 2。如第0個結點左右子結點下標分別為1和2。

技術分享圖片技術分享圖片?

堆的操作

建立堆:

一般情況下,樹並不滿足堆的條件,通過重新排列元素,可以建立一棵”堆化“的樹。如初始表:55 12 16,堆化後為:12 55 16。

堆的插入:

每次插入都是將新數據放在數組最後。然後樹被更新以恢復堆次序。如初始表:12 22 7 ,插入新數據後,數組為12 22 7 16,然後重排樹的順序,數組為12 16 7 22。

可以發現從這個新數據的父結點到根結點必然為一個有序的數列。

//  新加入i結點  其父結點為(i - 1) / 2
void MinHeapFixup(int a[], int i)
{
    int j, temp;
	
	temp = a[i];
	j = (i - 1) / 2;      //父結點
	while (j >= 0 && i != 0)
	{
		if (a[j] <= temp)
			break;
		
		a[i] = a[j];     //把較大的子結點往下移動,替換它的子結點
		i = j;
		j = (i - 1) / 2;
	}
	a[i] = temp;
}
技術分享圖片
//在最小堆中加入新的數據nNum
void MinHeapAddNumber(int a[], int n, int nNum)
{
<span style="white-space:pre">	</span>a[n] = nNum;
<span style="white-space:pre">	</span>MinHeapFixup(a, n);
}
技術分享圖片


堆的刪除

堆中每次都只能刪除第0個數據。為了便於重建堆,實際的操作是將最後一個數據的值賦給根結點,然後再從根結點開始進行一次從上向下的調整。調整時先在左右兒子結點中找最小的,如果父結點比這個最小的子結點還小說明不需要調整了,反之將父結點和它交換後再考慮後面的結點。相當於從根結點將一個數據的“下沈”過程。

如初始表:12 16 50 22,刪除第0個數據後,數組為22 16 50 _,然後重排樹的順序,數組為16 22 50。

//  從i節點開始調整,n為節點總數 從0開始計算 i節點的子節點為 2*i+1, 2*i+2
void MinHeapFixdown(int a[], int i, int n)
{
    int j, temp;

	temp = a[i];
	j = 2 * i + 1;
	while (j < n)
	{
		if (j + 1 < n && a[j + 1] < a[j]) //在左右孩子中找最小的
			j++;

		if (a[j] >= temp)
			break;

		a[i] = a[j];     //把較小的子結點往上移動,替換它的父結點
		i = j;
		j = 2 * i + 1;
	}
	a[i] = temp;
}
//在最小堆中刪除數
void MinHeapDeleteNumber(int a[], int n)
{
	Swap(a[0], a[n - 1]);
	MinHeapFixdown(a, 0, n - 1);
}
技術分享圖片

堆化數組

關於怎樣把一個數據進行堆化。可能很多人會想,要一個一個的從數組中取出數據來建立堆?不用。

比如說:int A[0] = {8,11,16,29,49,19,59,64,3,18};

如果把這個數組看成是一棵樹,那麽它的葉子結點19,59,64,3,18都分別是一個合法的堆。只要把49開始向下調整就可以了。然後再取29,16,11,9結點分別作一次向下調整操作就可以了。

//建立最小堆
void MakeMinHeap(int a[], int n)
{
	for (int i = n / 2 - 1; i >= 0; i--)
		MinHeapFixdown(a, i, n);
}
技術分享圖片


就這樣,堆的操作就全部完成了。

說了這麽多,終於到主角登場了。

根據堆的性質,堆建好之後。堆中第0個數據是堆中最小的數據。取出這個數據再執行下堆的刪除操作。這樣堆中第0個數據又是堆中最小的數據,重復上述步驟直至堆中只有一個數據時就直接取出這個數據。

由於堆也是用數組模擬的,故堆化數組後,第一次將A[0]與A[n - 1]交換,再對A[0…n-2]重新恢復堆。第二次將A[0]與A[n – 2]交換,再對A[0…n - 3]重新恢復堆,重復這樣的操作直到A[0]與A[1]交換。由於每次都是將最小的數據並入到後面的有序區間,故操作完成後整個數組就有序了。

void MinheapsortTodescendarray(int a[], int n)
{
	for (int i = n - 1; i >= 1; i--)
	{
		Swap(a[i], a[0]);
		MinHeapFixdown(a, 0, i);
	}
}
技術分享圖片

註意使用最小堆排序後是遞減數組,要得到遞增數組,可以使用最大堆。

應用:

堆是一種經典的數據結構,向堆中插入、刪除元素時間復雜度都是 O(lgN), N 為堆中元素的個數,而獲取最小 key 值(小根堆)的復雜度為 O(1)。

libevent中的定時事件管理就是用一個以時間作為 key 的小根堆結構做的,放棄了原來的紅黑樹,大概就是堆比紅黑樹簡單吧。

參考:

http://blog.csdn.net/morewindows/article/details/6709644

堆與堆排序

堆與堆排序