Algorithm之排序之堆排序(Heap Sort)

[color=green][size=medium][b]Algorithm之排序之堆排序(Heap Sort)[/b][/size][/color]

[size=medium][b]一、什麼是堆[/b][/size]

在講述堆之前,首先看一下二叉樹。

二叉樹:
每個節點最多有兩個子節點,且這兩個子節點有左右次序之分。


[b]1、滿二叉樹[/b]
二叉樹的所有非葉子節點都被填滿。
因此:一顆深度爲 K 的滿二叉樹,其節點總數爲:2的k次方 - 1

= 1 + 2 + 4 + 8 + ... + 2 pow(k-1)

= 2(0) + 2(1) + 2(2) + 2(3) + ... + 2(k-1) = 2pow(k) - 1

圖(深度爲 3 的滿二叉樹):
[img]http://dl2.iteye.com/upload/attachment/0122/7202/db4bf4a1-5d42-3b9b-8083-e163fc10dc50.png[/img]


[b]2、完全二叉樹[/b]
有 n 個節點,深度爲 k 的二叉樹,
當且僅當,這 n 個節點的排列序號,與深度爲 k 的滿二叉樹中
序號爲 1 至 n 的節點一一對應時,稱之爲完全二叉樹。

即:
- 除了最外層,其它層都是有序且填滿的。
- 上一層沒有排完,不能排下一層。


圖(深度爲 3 的完全二叉樹):
[img]http://dl2.iteye.com/upload/attachment/0122/7204/99f07258-3bee-3841-b21e-fbdecfd698e6.png[/img]


[b]3、堆[/b]
僅從數據結構角度看:符合完全二叉樹的數據結構稱之爲:堆。
且,所有父節點比其下子節點都大(或小),
但,堆不要求每層中左右子節點的大小順序。

將根節點最大的堆叫做最大堆或大根堆,根節點最小的堆叫做最小堆或小根堆。


[size=medium][b]二、堆的操作方法[/b][/size]

[b]1、插入節點[/b]
每次插入都是將新數據放在數組最後。然後依次向上調整。
直至符合:父節點比子節點都大(或小)。

[b]2、刪除節點[/b]
按定義,堆中每次都只能刪除最頂層的那個節點(數組中索引爲0的元素)。
爲了便於重建堆,實際的操作是將數組最末尾的元素與根節點位置調換。
然後再只需調整根節點使其符合堆特性即可。(其它節點是已調整好的)

以最小堆爲例:
調整根節點時先在左右子結點中找最小的,如果根結點比這個最小的子結點還小說明不需要調整了,
反之將父結點和它交換後,還需要再考慮後面的結點。相當於從根結點將一個數據的“下沉”過程。


[size=medium][b]三、堆與數組的關係[/b][/size]
基於:數組的索引是從 0 開始(Zero-Based):
[img]http://dl2.iteye.com/upload/attachment/0122/7224/d0bf9796-34c0-316a-8f05-ee06ef9a229b.png[/img]

從上圖中可以發現,以數組表示的堆,其數據結構中的每個節點的索引值存在這樣的特點:

父節點:parent(i) = (i - 1) >> 1;

子節點(左):leftChild(i) = i * 2 + 1;

子節點(右):rightChild(i) = i * 2 + 2;


任何一個數組,附以上述規則,那麼這個數組就可以被看作是一個堆。
也就是說,堆可以用數組表示。反之:任何一個數組,都是一個堆。

堆的邏輯結構:完全二叉樹
堆的存儲結構:數組(內存中一塊地址連續的空間),或:一堆空間。

既於,數組具天然有序索引這一特性:數組的索引就是一個排好序的堆。
那麼,只需要按照數組的索引將其值放置爲符合堆的規則即可——堆排序。


[size=medium][b]四、堆排序的過程[/b][/size]

堆排序共分兩步:

[b]第一步:將數組堆化[/b]

把數組的每個節點,調整爲具有堆的該特性:所有的父節點都比子節點大(或小)。

方法:遍歷數組的每個節點

1、遍歷的方向:從最外層向內層遍歷(自底向上)

2、從哪裏開始:從後向前數,從第一個非葉子節點開始遍歷。
沒有必要從葉子節點開始。(
葉子節點可以看作是已符合堆特點的節點。
因爲它沒有子節點,所以作爲父節點,它就是最大的。)

第一個非葉子節點的索引值可以根據最後一個元素的索引值求得:
lastNodeIndex = (lastLeafIndex - 1) >> 1 = (arr.length - 2) >> 1;

3、每步遍歷的規則:
雖然堆的子節點是不要求有大小順序的,但是因爲我們要對堆進行排序,
故:對於每個節點和它的子節點都要相互比較大小,
對於大堆,最大的那個排在父節點的位置。
對於小堆,最小的那個排在父節點的位置。

4、每步遍歷多少次:如果子節點與父節點交換了位置,則交換位置後的父節點仍需要向下驗證。


[img]http://dl2.iteye.com/upload/attachment/0122/7306/83565875-f4ca-3150-a0d7-a69e63e7b01b.png[/img]
[img]http://dl2.iteye.com/upload/attachment/0122/7304/ccf70294-16ed-3d35-9d3e-f31b9471c09a.png[/img]


[b]第二步:將堆化數組排序[/b]

堆排序的過程就是,不斷移出最頂層(數組的第一個)元素的過程。
1、把移出的頂層元素與數組最後一個元素交換位置。
2、同時遍歷的長度減 1,
3、然後從新調整剩餘的數據,使其符合堆的特性。(
此時只需要調整最頂層元素即可,其它層都是已經被初始化好的)

重複該過程,直至需要遍歷的數組長度爲 0 。


[img]http://dl2.iteye.com/upload/attachment/0122/7310/7f9fb25f-fc14-3cf1-8f36-b31c1a817e3a.png[/img]
[img]http://dl2.iteye.com/upload/attachment/0122/7312/b5bdead8-3b40-31b2-9ebd-b06efda63c1a.png[/img]


排好序的堆就是一顆完全二叉樹了:
1、所有的父節點都比子節點小(或大)
2、同一節點的左子節點比右子節點小(或大)

[img]http://dl2.iteye.com/upload/attachment/0122/7314/e9d92ee0-6dfe-3fcd-8f55-9f36cd70fb2f.png[/img]


排序動畫:
[img]http://dl2.iteye.com/upload/attachment/0122/7316/1c3804d9-cf1f-3c93-a665-f00971c88a95.gif[/img]


Javascript代碼實現:


function heapSort(arr){

// step 1. heapify array
var len = arr.length - 1;
var lastNodeIndex = (len - 1) >> 1;
for(var i = lastNodeIndex; i >= 0; i--){
maxHeapify(arr, i, len);
}

// step 2. reduce heap
for(var i = 0; i < len; i++){
swap(arr, 0, len - i);
maxHeapify(arr, 0, len - i - 1);
}

return arr;
}

function swap(arr, i, j){
var temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}


function maxHeapify(arr, index, len){
var li = (index << 1) + 1, // left child index.
ri = li + 1, // right child index.
cMax = li; // default Child Max valule.

if(li > len) return; // left child is out of range.
if(ri <= len && arr[ri] > arr[li]) // choose the bigger one.
cMax = ri;
if(arr[cMax] > arr[index]){ // if
swap(arr, index, cMax); // swapped,
maxHeapify(arr, cMax, len); // next check should be executed.
}
}

/*

// alternative method for maxHeapify();
function maxHeapify2(array, index, len) {
var iLeft, iRight, iMax;
while (true) {
iLeft = (index << 1) + 1;
iRight = iLeft + 1;
iMax = iLeft;

if(iLeft > len) break;
if(iRight <= len && array[iRight] > array[iLeft])
iMax = iRight;
if(array[iMax] <= array[index]) break;
swap(array, iMax, index);
index = iMax;
}
}

*/



測試:


var arr = [1,23,14,8,10,9,235,21,45,56,4,32,79,15,56,985,343];

heapSort(arr);

// [1, 4, 8, 9, 10, 14, 15, 21, 23, 32, 45, 56, 56, 79, 235, 343, 985]




Java 版本:

import java.util.Arrays;

public class HeapSort {

private int[] arr;

public HeapSort(int[] arr){
this.arr = arr;
}

/**
* 堆排序的主要入口方法,共兩步。
*/
public void sort(){
/*
* 第一步:將數組堆化
* beginIndex = 第一個非葉子節點。
* 從第一個非葉子節點開始即可。無需從最後一個葉子節點開始。
* 葉子節點可以看作已符合堆要求的節點,根節點就是它自己且自己以下值爲最大。
*/
int len = arr.length - 1;
int beginIndex = (len - 1) >> 1;
for(int i = beginIndex; i >= 0; i--){
maxHeapify(i, len);
}

/*
* 第二步:對堆化數據排序
* 每次都是移出最頂層的根節點A[0],與最尾部節點位置調換,同時遍歷長度 - 1。
* 然後從新整理被換到根節點的末尾元素,使其符合堆的特性。
* 直至未排序的堆長度爲 0。
*/
for(int i = len; i > 0; i--){
swap(0, i);
maxHeapify(0, i - 1);
}
}

private void swap(int i,int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}

/**
* 調整索引爲 index 處的數據,使其符合堆的特性。
*
* @param index 需要堆化處理的數據的索引
* @param len 未排序的堆(數組)的長度
*/
private void maxHeapify(int index,int len){
int li = (index << 1) + 1; // 左子節點索引
int ri = li + 1; // 右子節點索引
int cMax = li; // 子節點值最大索引,默認左子節點。

if(li > len) return; // 左子節點索引超出計算範圍,直接返回。
if(ri <= len && arr[ri] > arr[li]) // 先判斷左右子節點,哪個較大。
cMax = ri;
if(arr[cMax] > arr[index]){
swap(cMax, index); // 如果父節點被子節點調換,
maxHeapify(cMax, len); // 則需要繼續判斷換下後的父節點及其以下的子節點,是否符合堆的特性。
}
}

/**
* 測試用例
*
* 輸出:
* [0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 9]
*/
public static void main(String[] args) {
int[] arr = new int[]{3,5,3,0,8,6,1,5,8,6,2,4,9,4,7,0,1,8,9,7,3,1,2,5,9,7,4,0,2,6};
new HeapSort(arr).sort();
System.out.println(Arrays.toString(arr));
}

}





[size=medium][b]五、堆排序分析[/b][/size]


堆排序與歸併排序一樣,是一種時間複雜度爲O(N * logN)的算法,
同時和插入排序一樣,是一種就地排序算法(不需要額外的存儲空間)。


-
轉載請註明,
原文出處:http://lixh1986.iteye.com/blog/2354246


-
引用:

堆_(數據結構)
https://zh.wikipedia.org/wiki/堆_(數據結構)

常見排序算法 - 堆排序 (Heap Sort)
http://bubkoo.com/2014/01/14/sort-algorithm/heap-sort/

堆排序詳解
http://blog.csdn.net/daiyudong2020/article/details/52529791

堆排序詳解
http://www.cnblogs.com/hdk1993/p/4635923.html

堆排序 - 維基百科
https://zh.wikipedia.org/wiki/堆排序
發佈了279 篇原創文章 · 獲贊 4 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章