奧法之劫 (offa) 題解
堆
大頂堆:子節點的鍵值或索引總是小於(或等於)它的父節點
小頂堆:子節點的鍵值或索引總是大於(或等於)它父節點
堆排序
堆排序(英語:Heapsort)是指利用堆這種資料結構所設計的一種排序演算法,是選擇排序的擴充套件,它的最好和最壞的平均複雜度都為O(nlogn),是不穩定排序演算法。
堆排序步驟
案例:使用大頂堆模式對陣列[5, 15, 10, 20, 13]進行升序排序
步驟一:對給定陣列[5, 15, 10, 20, 13]進行調整,轉換成大頂堆陣列(一般升序使用大頂堆,降序使用小頂堆)
(1). 建立一個指標指向調整起點(第一次調整時指向給定陣列的二叉樹結構的最後一個非葉子節點 )
(1.1). 將指標指向的節點的值與它子節點的最大值進行比較,當指標指向的節點的值小於子節點的最大值時,進行調整交換
(1.2). 當發生交換時將指標指向與它發生交換的子節點位置迴圈步驟(1),如果沒有發生交換或指標指向葉子節點則結束步驟(1)
(2). 再從最後一個非葉子節點開始,往上遍歷節點,以遍歷節點為調整起點迴圈步驟(1),直到遍歷完根節點
步驟二:將堆頂元素(即陣列0下標位置)與末尾元素進行交換,使得尾部元素最大。
(1). 因為經過步驟一後,陣列已經變成了大頂堆結構,這時陣列的第一個元素就是陣列中最大的元素,而排序要求是升序,所以要把第一個元素與最後一個元素 (排除掉切割出的長度) 進行交換,使得最大元素在運算元組的最後。交換完後需把運算元組中的末尾元素切割出去(實際操作通過調整長度來實現切割,而不是建立一個新的陣列)。
(2). 因為經過步驟(1)後,陣列的大頂堆結構已經被破壞了,所以需重新調整,即跳到步驟一,又因為根節點元素髮生了變化,所以此時調整起點為根節點。
步驟三:迴圈執行步驟一和步驟二,當運算元組的元素都被切割出去時(調整長度為0),則表明陣列已經按升序順序排序完成
程式碼實現
public class HeapSort {
public static void main(String[] args) {
int array[] = {5, 15, 10, 20, 13};
heapSort(array);
System.out.println(Arrays.toString(array));
}
//堆排序(從小到大排序,大頂堆模式實現)
public static void heapSort(int array[]){
int temp = 0;
/*
在二叉樹中由下到上,把二叉樹陣列調整成大頂堆陣列
array.length/2-1 :指向最後一個非葉子節點
*/
for (int i = array.length/2-1; i>=0; i--){
adjustHeap(array, i, array.length);
}
/*
在經過調整後,陣列的第一個索引0的值必定是陣列調整長度內最大的值,
因為是從小到大進行排序,所以要把索引0的值放到調整長度內最後一個索引的值,
即放到調整長度-1的索引位置
*/
for (int j = array.length-1; j>0; j--){
//將調整長度內最大值array[0]和調整長度內最後位置的值array[j]進行交換
temp = array[j];
array[j] = array[0];
array[0] = temp;
/*
又因為經過交換後,大頂堆的結構會被破壞,所以要重新進行調整,
調整起點應為根節點(因為根節點發生了變化),即陣列0下標位置,
而array[j]已經是不需要調整的了,所以調整長度為j(因為在調整方法裡j引數是被當作長度處理的,所以無需-1)
*/
adjustHeap(array,0, j);
}
}
/**
* 將陣列調整成大頂堆陣列
* @param array 需調整的陣列
* @param i 索引指標,開始指向傳入的非葉子節點,後面指向最後進行交換(插入)的節點
* @param length 需調整的長度,即調整的元素個數,從0下標索引開始
*/
public static void adjustHeap(int array[], int i, int length){
//輔助變數,儲存傳入的非葉子節點的值
int temp = array[i];
/*
遍歷傳入的非葉子節點下的子節點,i*2+1為左節點,i*2+2為右節點
如果該節點不是非葉子節點則退出迴圈
*/
for (int k = i*2+1; k<length; k = k*2+1){
/*
判斷該節點是否有右節點,即k+1是否超過陣列範圍索引
如果有,則比較大小,拿出最大值的節點
*/
if (k+1 < length && array[k] < array[k+1]){
//如果右節點大,把索引k+1即可使k指向右節點
k++;
}
//判斷傳入的非葉子節點的值是否小於它子樹節點的最大值
if (temp < array[k]){
//如果小於則把大於它的子節點的值賦給該節點
array[i] = array[k];
//再把非葉子節點的指標指向該子節點的位置,繼續迴圈
i = k;
}else {
/*
因為在調整二叉樹陣列成大頂堆陣列時,是從下到上進行遍歷調整的,
而迴圈條件是從上到下遞減的,所以當上訴條件不成立時直接退出即可
*/
break;
}
}
/*
因為在上面節點和節點的交換中使用的是插入演算法,所以在退出迴圈時,
需要將初始傳入的非葉子節點的值temp賦給最後一個實現交換(插入)的節點,即i指向的節點
*/
array[i] = temp;
}
}