二叉堆/堆排序概念理解以及Java語言實現(學習筆記)
1.基礎概念介紹
(1)滿二叉樹:每一層都達到了最大節點數,高度爲h的滿二叉樹節點數爲pow(2,h)-1 ( pow(x,y)代表x^y )
如圖所示:
(2)完全二叉樹:高度爲h的二叉樹每一個節點都和高度爲h的滿二叉樹節點編號一一對應,那麼它就是滿二叉樹。
如圖所示:
[1] 高度爲h的完全二叉樹節點數取值範圍[pow(2,h-1),pow(2,h)-1],可見完全二叉樹的高度是O( log(n) )
[2] 滿二叉樹是特殊的完全二叉樹
[3]根節點編號記爲1,對於節點i,它的左孩子編號爲2i,右孩子編號爲2i+1,對於節點i,它的父節點爲floor(i/2) (floor表示向下取整)。完全二叉樹適合用順序存儲結構來表示
(4)二叉堆概念:
[1] 首先是一顆完全二叉樹
[2] 小根堆:任何一個節點小於等於它的左孩子且小於等於他的右孩子
[3] 大根堆:任何一個節點大於等於它的左孩子且大於等於他的右孩子
舉個例子(大根堆):
2.堆排序
[1] 堆排序屬於選擇排序,第i趟在n-i+1個元素中選擇最大(值),作爲子序列的第i個元素。
[2]和AVL樹、紅黑樹、B樹和B+樹不同,他們是用於查找的,但堆是用於排序的。
2.1 建立大根堆(在已有序列基礎上建立)
設序列長度爲n,則最後一個父節點編號爲floor(n/2) (floor表示向下取整)
[1] i=floor(n/2),查看節點i和它的孩子,如果節點i小於兩個(也可能是一個)孩子的最大值,則把節點i和最大孩子交換
[2] 令i減少1,查看節點i和它的孩子,如果節點i小於兩個(也可能是一個)孩子的最大值,則把節點i和最大孩子交換,以交換後的孩子爲根節點的樹可能不再滿足堆的定義,如果不滿足需要對它進行調整(下調)(多級的調整)
[3] 重複[2],直到i=1
如圖:
空間複雜度:O(1)
時間複雜度:O(n)
Java實現:
實際上Java可以比較任何對象的大小,我的上篇博客有講到(https://blog.csdn.net/weixin_42111859/article/details/104132520),這裏用int數組(ArrayList更合適,可以動態添加刪除,更加靈活)舉個例子:
// 從0編號,i左孩子爲2i+1,右孩子爲2i+2,父節點爲(i+1)/2-1
private static void buildMaxHeap(int[] array) {
for (int i = array.length / 2 - 1; i >= 0; i--) {
adjustDown(i, array);
}
}
// 使得i爲頂點的子樹成爲堆
private static void adjustDown(int i, int[] array) {
for (int j = i; j < array.length; ) {
// k爲較大的孩子
int k;
// 有兩個孩子
if (array.length >= 2 * j + 3) {
k = array[2 * j + 1] > array[2 * j + 2] ? 2 * j + 1 : 2 * j + 2;
}
// 只有左孩子
else if (array.length >= 2 * j + 2) {
k = 2 * j + 1;
}
// 沒有孩子
else {
break;
}
// 父節點更大,不必交換
if (array[j] >= array[k]) {
break;
}
// 交換
int temp = array[j];
array[j] = array[k];
array[k] = temp;
j = k;
}
}
@Test
public void testBuildMaxHeap() {
int[] a = {1, 2, 3, 4, 5, 6, 7, 8, 9};
System.out.println(Arrays.toString(a));
buildMaxHeap(a);
System.out.println(Arrays.toString(a));
}
控制檯輸出結果:
代碼和上面的例子是對應的
2.2 堆排序
第i趟,先取堆頂元素(堆頂一定是最大值),然後把堆底賦給堆頂,對n-i個元素下調(建立大根堆)
這也是不斷刪除最大元素的過程
時間複雜度:O( n*log(n) )
空間複雜度:O(1) 下面代碼中用一個數組存儲結果了,實際上可以輸出或者用迭代器返回結果,只需要常數個輔助單元
如果只求前m大的值,時間複雜度爲 O( m*log(n) )
java代碼:
public static int[] heapSort(int[] array) {
buildMaxHeap(array);
int[] rlt = new int[array.length];
int index = 0;
for (int i = array.length - 1; i >= 0; i--) {
// 保存堆頂元素
rlt[index++] = array[0];
// 堆底給堆頂
array[0] = array[i];
// 構建大根堆
adjustDown(0, array, i);
}
return rlt;
}
@Test
public void testHeapSort() {
int[] a = {1, 2, 3, 4, 5, 6, 7, 8, 9};
System.out.println(Arrays.toString(a));
System.out.println(Arrays.toString(heapSort(a)));
}
運行結果爲:
2.3 堆的插入
堆是可以插入元素的,尾接新元素,如果比父元素更大就交換,然後重複這個步驟(上調)
舉一個例子(不寫代碼了):
轉載請註明出處,謝謝合作