堆的定義
堆是計算機科學中一類特殊的數據結構的統稱,堆通常可以被看做是一棵完全二叉樹的數組對象。
堆的特性:
1.它是完全二叉樹,除了樹的最后一層結點不需要是滿的,其它的每一層從左到右都是滿的,如果最后一層結點不 是滿的,那么要求左滿右不滿。

2.它通常用數組來實現。
具體方法就是將二叉樹的結點按照層級順序放入數組中,根結點在位置1,它的子結點在位置2和3,而子結點的子 結點則分別在位置4,5,6和7,以此類推。(不使用數組的第一個位置)

如果一個結點的位置為k,則它的父結點的位置為[k/2],而它的兩個子結點的位置則分別為2k和2k+1。這樣,在不 使用指針的情況下,我們也可以通過計算數組的索引在樹中上下移動:從a[k]向上一層,就令k等於k/2,向下一層就 令k等於2k或2k+1。
3.每個結點都大於等於它的兩個子結點。這里要注意堆中僅僅規定了每個結點大於等於它的兩個子結點,但這兩個 子結點的順序並沒有做規定,跟我們之前學習的二叉查找樹是有區別的。
上浮和下沉算法
堆的操作會首先進行一些簡單的改動,打破堆的狀態,然后再遍歷堆並按照要求將堆的狀態恢復,我們稱這個過程叫做堆的有序化。
在有序化的過程中,我們會遇到兩種情況。
- 當節點的優先級上升時(在堆底加入一個新的元素時),我們需要由下至上恢復堆的順序(即上浮 swim)。
- 當節點的優先級下降(例如將根節點替換為一個較小的元素時),我們需要由上至下恢復堆的順序(即下沉 sink)。
上浮算法swim實現
先實現堆的比較和交換方法:
public class Heap<T extends Comparable<T>> {
private T[] items;
private int N;
public Heap(int capacity) {
this.items = (T[])new Comparable[capacity+1];
this.N = 0;
}
//比較i位置元素是否比j位置元素小
private boolean less(int i, int j) {
return items[i].compareTo(items[j]) < 0;
}
//交換i位置元素和j位置元素
private void exch(int i, int j) {
T temp = items[i];
items[i] = items[j];
items[j] = temp;
}
}
因為k位置的父節點的位置是k/2,所以只要循環往上比較並交換就行了
/**
* @author wen.jie
* @date 2021/8/24 9:59
* 上浮算法
*/
private void swim(int k) {
while (k > 1 && less(k/2, k)){
exch(k/2, k);
k = k/2;
}
}
下沉算法sink實現
下沉算法中:我們需要通過父節點和它的兩個子節點中的較大者交換來恢復堆。所以我們同樣需要循環比較並交換,代碼實現如下:
/**
* @author wen.jie
* @date 2021/8/24 9:59
* 下沉算法
*/
private void sink(int k) {
while (2*k <= N){
int j = 2*k;
if(j < N && less(j, j+1)) j++;
if(!less(k, j)) break;
exch(k, j);
k = j;
}
}
堆的插入和刪除最大元素實現
堆的插入
我們將新元素加到數組末尾,增加堆的大小並讓新元素上浮到合適的位置:
/**
* @author wen.jie
* @date 2021/8/24 10:59
* 向堆中添加一個元素
*/
public void insert(T t) {
items[++N] = t;
swim(N);
}
刪除最大元素
我們從數組頂端刪除最大的元素,並將數組的最后一個元素放到頂端,減小堆的大小並讓這個元素下沉到合適位置。
代碼實現如下:
/**
* @author wen.jie
* @date 2021/8/24 10:59
* 刪除堆中最大的元素
*/
public T delMax() {
T t = items[1];
items[1] = null;
exch(N--, 1);
sink(1);
return t;
}
下面提供完整的堆數據結構代碼:
public class Heap<T extends Comparable<T>> {
private T[] items;
private int N;
public Heap(int capacity) {
this.items = (T[])new Comparable[capacity+1];
this.N = 0;
}
//比較i位置元素是否比j位置元素小
private boolean less(int i, int j) {
return items[i].compareTo(items[j]) < 0;
}
//交換i位置元素和j位置元素
private void exch(int i, int j) {
T temp = items[i];
items[i] = items[j];
items[j] = temp;
}
/**
* @author wen.jie
* @date 2021/8/24 10:59
* 向堆中添加一個元素
*/
public void insert(T t) {
items[++N] = t;
swim(N);
}
/**
* @author wen.jie
* @date 2021/8/24 9:59
* 上浮算法
*/
private void swim(int k) {
while (k > 1 && less(k/2, k)){
exch(k/2, k);
k = k/2;
}
}
/**
* @author wen.jie
* @date 2021/8/24 9:59
* 下沉算法
*/
private void sink(int k) {
while (2*k <= N){
int j = 2*k;
if(j < N && less(j, j+1)) j++;
if(!less(k, j)) break;
exch(k, j);
k = j;
}
}
/**
* @author wen.jie
* @date 2021/8/24 10:59
* 刪除堆中最大的元素
*/
public T delMax() {
T t = items[1];
items[1] = null;
exch(N--, 1);
sink(1);
return t;
}
}
堆排序
基於堆,我們可以實現一種經典而優雅的排序算法---堆排序。
堆排序實現原理:
1.構造堆;
2.得到堆頂元素,這個值就是最大值;
3.交換堆頂元素和數組中的最后一個元素,此時所有元素中的最大元素已經放到合適的位置;
4.對堆進行調整,重新讓除了最后一個元素的剩余元素中的最大值放到堆頂;
5.重復2~4這個步驟,直到堆中剩一個元素為止。
代碼實現:
/**
* @author wen.jie
* @date 2021/8/24 14:07
*/
public class HeapSort {
public static void sort(Comparable[] pq) {
int n = pq.length;
// 構造堆
for (int k = n/2; k >= 1; k--)
//從長度一半處開始(跳過大小為1的堆)
sink(pq, k, n);
// 排序
int k = n;
while (k > 1) {
//交換第一個最大的元素和第k個元素
exch(pq, 1, k--);
//下沉重排堆,就可以得到第1個到第k-1個最大的元素
sink(pq, 1, k);
}
}
private static void sink(Comparable[] pq, int k, int n) {
while (2*k <= n) {
int j = 2*k;
if (j < n && less(pq, j, j+1)) j++;
if (!less(pq, k, j)) break;
exch(pq, k, j);
k = j;
}
}
private static boolean less(Comparable[] pq, int i, int j) {
return pq[i-1].compareTo(pq[j-1]) < 0;
}
private static void exch(Object[] pq, int i, int j) {
Object swap = pq[i-1];
pq[i-1] = pq[j-1];
pq[j-1] = swap;
}
}
測試:
@Test
public void test(){
String[] arr = new String[]{"S", "O", "R", "T", "E", "X", "A", "M", "P", "L", "E"};
HeapSort.sort(arr);
System.out.println(Arrays.toString(arr));
}

