1. 簡介
堆是具有以下性質的完全二叉樹:每個結點的值都大於或等於其左右孩子結點的值,稱爲大頂堆;或者每個結點的值都小於或等於其左右孩子結點的值,稱爲小頂堆。
堆通常是一個可以被看做一棵樹的數組對象。堆總是滿足下列性質:
1.堆中某個節點的值總是不大於或不小於其父節點的值;
2.堆總是一棵完全二叉樹。
常見的堆有二叉堆、斐波那契堆等。
堆的定義:n個元素的序列{k1,k2,ki,…,kn}當且僅當滿足下關係時,稱之爲堆。
(ki <= k2i,ki <= k2i+1)或者(ki >= k2i,ki >= k2i+1), (i = 1,2,3,4…n/2)
2. 算法演示
排序動畫過程解釋
-
首先,將所有的數字存儲在堆中
-
按大頂堆構建堆,其中大頂堆的一個特性是數據將被從大到小取出,將取出的數字按照相反的順序進行排列,數字就完成了排序
-
在這裏數字 5 先入堆
-
數字 2 入堆
-
數字 7 入堆, 7 此時是最後一個節點,與最後一個非葉子節點(也就是數字 5 )進行比較,由於 7 大於 5 ,所以 7 和 5 交互
-
按照上述的操作將所有數字入堆,然後從左到右,從上到下進行調整,構造出大頂堆
-
入堆完成之後,將堆頂元素取出,將末尾元素置於堆頂,重新調整結構,使其滿足堆定義
-
堆頂元素數字 7 取出,末尾元素數字 4 置於堆頂,爲了維護好大頂堆的定義,最後一個非葉子節點數字 5 與 4 比較,而後交換兩個數字的位置
-
反覆執行調整+交換步驟,直到整個序列有序
3. 代碼
/**
* Created by lizq on 2020/3/3.
*/
public class HeapSort {
public static void main(String[] args) {
long[] arrs = RandomArr.createLongArr(20, 0, 200);
long[] arrs2 = Arrays.copyOf(arrs, arrs.length);
Arrays.stream(arrs).forEach(l -> {
System.out.print(l + " ");
});
System.out.println();
sort(arrs);
Arrays.stream(arrs).forEach(l -> {
System.out.print(l + " ");
});
System.out.println();
Arrays.stream(arrs2).sorted().forEach(l -> {
System.out.print(l + " ");
});
}
public static void sort(long[] arrs) {
buildHeap(arrs);
int len = arrs.length;
for (int i = len - 1; i > 0; i--) {
swap(arrs, 0, i);
len--;
heapify(arrs, 0, len);
}
}
public static void buildHeap(long[] arrs) {
// 找到最後一個沒有子樹的節點
for (int i = (int) Math.floor(arrs.length / 2); i >= 0; i--) {
heapify(arrs, i, arrs.length);
}
}
public static void heapify(long[] arrs, int i, int len) {
int l = getLeft(i);
int r = getRight(i);
long tmp;
int largest = i;
// 如果左子樹大於根節點,則把左子樹設置最大值
if (l < len && arrs[l] > arrs[largest]) {
largest = l;
}
// 如果右子樹大於根節點,則把右子樹設置最大值
if (r < len && arrs[r] > arrs[largest]) {
largest = r;
}
if (largest != i) {
// 只交換最大那個分支
swap(arrs, i, largest);
// 遞歸修正換了節點那個分支
heapify(arrs, largest, len);
}
}
/**
* 獲取左子節點的索引
*
* @param i
* @return
*/
public static int getLeft(int i) {
return i * 2 + 1;
}
/**
* 獲取右子節點的索引
*
* @param i
* @return
*/
public static int getRight(int i) {
return i * 2 + 2;
}
/**
* 獲取父親節點
*
* @param i
* @return
*/
public static int getParent(int i) {
// 根節點
if (i == 0) {
return -1;
}
return (i - 1) / 2;
}
/**
* 交換元素
*
* @param arrs
* @param i
* @param j
*/
public static void swap(long[] arrs, int i, int j) {
// 交換元素
long tmp = arrs[i];
arrs[i] = arrs[j];
arrs[j] = tmp;
}
}
4. 總結
構建堆的過程就是把最大值(最小值) 從最底層升到最上層, 根節點一定是最大值(最小值)
排序過程把首節點和尾節點互換,然後對之前的再次進行從上倒下構造堆