性質:
- 度:子樹就是二叉樹的分支。度就是分支的數目。
- 完全二叉樹:除最後一層可能不滿以外,其他各層都達到該層節點的最大數,最後一層如果不滿,該層所有節點都全部靠左排。如果最後一層的節點數也達到最大就是慢二叉樹。
- 堆:實際上就是一棵完全二叉樹(以一個下標以0開頭的數組{16,7,3,20,17,8爲例,從上至下,從左至右一次填入堆中)。
- 有最大堆(堆頂元素的值是最大的)和最小堆(堆頂元素的值是最小的)。
- 總有以arr[i]爲根的節點,其左孩子節點爲arr[2*i+1],右孩子節點爲arr[2*i+2].(前提:節點總個數等於數組元素個數,2*i+1 < arr.length-1 , 2*i+2
堆排序基本思想:
構建最大堆,取出堆頂最大元素與最後一個元素交換位置;
此時堆頂已不是最大元素,需將剩餘節點以同樣算法構建成最大堆,取出堆頂可得剩餘節點中最大元素與剩餘節點的最後一個元素交換位置;
重複前兩步直至結束。
- 詳解
1. 構建最大堆
1. 首先,將數組按自然下標構建成一個無序堆。我們知道最大堆(假設節點數爲n吧)的每個子樹都符合最大堆的性質(根節點大於左右孩子節點)。一個子堆的堆頂元素的左右子節點下標必然滿足2*i+1<=n-1且2*i+2<=n-1,得i<=n/2-1. R[0]~R[n/2-1]這些節點纔會成爲子堆的堆頂元素,我們定義函數maxify(i)將以i位置節點爲根的子樹都構建成最大堆。
2. 取堆頂最大元素
1. 將初始待排序關鍵字序列(R0,R1,R2....R[n-1])構建成大頂堆,此堆爲初始的無序區;
2. 將堆頂元素R[0]與最後一個元素R[n-1]交換,此時得到新的無序區(R0,R1,R2,......Rn-2)和新的有序區(R[n-1]),且滿足R[0,1,2...n-2]<=R[n-1];
3. 由於交換後新的堆頂R[0]可能違反堆的性質,因此需要對當前無序區(R0,R1,R2,......Rn-2)調整爲新堆,然後再次將R[1]與無序區最後一個元素交換,得到新的無序區(R0,R1,R2....Rn-3)和新的有序區(Rn-1,Rn-2)。不斷重複此過程直到有序區的元素個數爲n-1,則整個排序過程完成。
代碼
import org.junit.Test;
public class HeapSortTest {
private int[] data = new int[]{5, 3, 6, 2, 1, 9, 4, 8, 7};
private int size = data.length;
@Test
public void run(){
buildMapHeap(size-1);//將所有元素構造在最大堆裏
System.out.print("--------begin:------");
for(int a : data){ System.out.print(a); } System.out.println();
fetchMax();//一層一層,逐一取出堆頂最大元素放在數組最後,剩下的元素生成最大堆
System.out.print("--------end:------");
for(int a : data){ System.out.print(a); } System.out.println();
}
//構建最大堆 getLeft(i)=i*2+1<=lastIndex && getRight(i)=i*2+2<=lastIndex
private void buildMapHeap(int lastIndex){
for(int i=(lastIndex-1)/2; i>=0; i--)
maxify(i);
}
/**
* 將以i爲根的子堆(無序)構建成最大堆
* @param i 子堆的根節點
*/
private void maxify(int i){
int left = getLeft(i);
int right = getRight(i);
// System.out.println("("+ data[i]+ ")i is "+ i +", (" + data[left] +")left is "+ left + ", ("+ data[right] +")right is " + right);
int maxIndex = i;
//如果"data[left]>data[maxIndex]"放在左邊作爲第一個條件的話,可能left>data.length-1,會報nullPointer錯誤
if( left<=size-1&& data[left]>data[maxIndex] )
maxIndex = left;
if( right<=size-1 && data[right]>data[maxIndex] )
maxIndex = right;
if( i!=maxIndex ){
swap(i, maxIndex);
maxify(maxIndex);//如果有位置交換,那麼交換後,data[maxIndex]的值發生變化,以data[maxIndex]爲根的堆現在最大值可能並不是data[maxIndex];在 3-2-1這個堆和2-8-7這個堆的根交換位置時就有這種情況
}
}
private void fetchMax(){
for(int i=data.length-1; i>0; i--){
swap(0, i);
size--;
buildMapHeap(i-1);
}
}
private int getLeft(int i){
return i*2+1;
}
private int getRight(int i){
return i*2+2;
}
private void swap(int x, int y){
int sum = data[x]+data[y];
data[y] = sum - data[y];
data[x] = sum - data[y];
}
}