初入數據結構的堆(Heap)以及Java實現
如果覺得對你有幫助,能否點個贊或關個注,以示鼓勵筆者呢?!博客目錄 | 先點這裏
-
堆的基本概念
- 什麼是二叉堆
- 最大堆和最小堆
- 注意點
-
二叉堆
- 實現基礎
- 動態數組
- 上浮
- 下沉
- 添加元素
- 取最大值
- 取最大值,同時插入新元素
- 將任意數組堆化
-
二叉堆Java代碼實現
- 描述
- 實現功能
- 完整代碼
-
堆排序(補)
- 描述
- 代碼實現
堆的基本概念
二叉堆
什麼是堆?
通常我們所說的堆,就是二叉樹的一種變形,所以它本質也是一棵二叉樹,只不過有一些自己的特點,所以我們有叫堆爲二叉堆
- 堆,也叫二叉堆,它是一棵完全二叉樹
- 堆的每棵子樹都是一個堆
- 堆可以分爲最大堆和最小堆
- 堆中的元素必須可以比較
堆的常用場景:
- 構建優先隊列(可以參考Java的優先隊列
PriorityQueue<E>
) - 支持堆排序
- 快速找出一個集合中的最小值(或者最大值)
比如我們要實現一個優先隊列的時候,通常會以下幾種底層數據結構
數據結構 | 入隊 | 出隊 |
---|---|---|
普通線性結構 | O(1)[順序入隊] | O(n)[每次都求優先級最高,類似求最大值] |
順序線性結構 | O(n)[入隊,每次都找到插入的位置] | O(1) [因爲已經排好序,直接取優先級最高] |
堆 | O(logn) | O(logn) |
最大堆和最小堆
堆分爲兩種:
最大堆
(大根堆)
在最大堆中,父結點的值比所有子結點的值都要大(或相等)最小堆
(小根堆)
在最小堆中,父結點的值要比所有子結點的值要小(或相等)
注意點
以下是一棵最大堆
- 從最大堆的特性中,我們知道,父結點的值一定是大於等於孩子結點的值,那麼如果有一個最大堆的高是
3
,排除根結點,高層級的結點一定是大於低層級結點的值嗎? 這個是不一定的,我們可以看到上面圖中的最大堆,第三層的結點E
的值就比第四層結點H
的值要小。
最大堆
實現基礎
我們通常說的堆數據結構,就是二叉堆,本質上是一棵完全二叉樹;通常在代碼的實現中,二叉堆的底層數據結構是使用數組而不是二叉鏈,因爲如果使用數組,二叉堆可以存在以下特性:
如果我們以數組來存儲堆的結點元素,從數組的第二個索引1
開始存儲,堆中的取任意結點,索引爲n
, 那麼可以滿足:
- 父結點索引爲
n / 2
- 左孩子索引爲
2 * n
- 右孩子索引爲
(2 * n) + 1
爲什麼要從數組的第二個位置,索引1開始存放元素呢?因爲很多的教材就是從索引開始存放的,公式也簡單好記。只不過在我們自己實現代碼時,可能就要多注意一下地方。
在我們瞭解堆使用數組存儲的特性後,爲了讓代碼更簡潔高效,我們要從數組第一個位置開始存儲,即索引爲0的地方也存放元素,優化一下,所以規律就變成了
- 父結點索引爲
(n-1)/2
- 左孩子索引爲
2*n + 1
- 右孩子索引爲
(2*n + 1) + 1
=2*n + 2
僅僅是沒這麼好記了罷了,不過代碼上的實現就簡單了,下面我們就來實現一個二叉堆的最大堆
, 最小堆差不多的啦,就反過來而已。
動態數組
這裏我們主要是實現堆,所以不想考慮過多的數組細節,就複製了網上的一份動態數組的實現源碼,感覺就類似ArrayList吧,用來代替數組成爲二叉堆的
/**
* 動態數據
*
* @param <E>
*/
public class Array<E> {
private E[] data;
private int size;
public Array(int capacity) { // user assign size
data = (E[]) new Object[capacity];
size = 0;
}
public Array() {
this(10); // default size
}
public Array(E[] arr) {
data = (E[]) new Object[arr.length];
for (int i = 0; i < arr.length; i++) {
data[i] = arr[i];
}
size = arr.length;
}
public int getSize() {
return size;
}
public int getCapacity() {
return data.length;
}
public boolean isEmpty() {
return size == 0;
}
public void rangeCheck(int index) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("Index is Illegal!");
}
}
public void add(int index, E e) {
if (index < 0 || index > size) {
throw new IllegalArgumentException("Index is Illegal ! ");
}
if (size == data.length) {
resize(data.length * 2);
}
for (int i = size - 1; i >= index; i--) {
data[i + 1] = data[i];
}
data[index] = e;
size++;
}
private void resize(int newCapacity) {
E[] newData = (E[]) new Object[newCapacity];
for (int i = 0; i < size; i++) {
newData[i] = data[i];
}
data = newData;
}
public void addLast(E e) { //末尾添加
add(size, e);
}
public void addFirst(E e) { //頭部添加
add(0, e);
}
public E get(int index) {
rangeCheck(index);
return data[index];
}
public E getLast() {
return get(size - 1);
}
public E getFirst() {
return get(0);
}
public void set(int index, E e) {
rangeCheck(index);
data[index] = e;
}
public boolean contains(E e) {
for (int i = 0; i < size; i++) {
if (data[i].equals(e)) {
return true;
}
}
return false;
}
public int find(E e) {
for (int i = 0; i < size; i++) {
if (data[i].equals(e)) {
return i;
}
}
return -1;
}
public E remove(int index) { // remove data[index] and return the value
rangeCheck(index);
E res = data[index];
for (int i = index; i < size - 1; i++) {
data[i] = data[i + 1];
}
size--;
data[size] = null;//loitering objects != memory leak
if (size == data.length / 4 && data.length / 2 != 0) {
resize(data.length / 2); //防止複雜度的震盪
}
return res;
}
public E removeFirst() {
return remove(0);
}
public E removeLast() {
return remove(size - 1);
}
public void removeElement(E e) { //only remove one(may repetition) and user not know whether is deleted.
int index = find(e);
if (index != -1) {
remove(index);
}
}
// new method
public void swap(int i, int j) {
if (i < 0 || i >= size || j < 0 || j >= size) {
throw new IllegalArgumentException("Index is illegal.");
}
E t = data[i];
data[i] = data[j];
data[j] = t;
}
@Override
public String toString() {
StringBuilder res = new StringBuilder();
res.append(String.format("Array : size = %d, capacity = %d\n", size, data.length));
res.append("[");
for (int i = 0; i < size; i++) {
res.append(data[i]);
if (i != size - 1) {
res.append(", ");
}
}
res.append("]");
return res.toString();
}
}
- 用於代替數組
上浮 shift up
- 新添加的元素放到數組末尾
- 判斷新添加的元素與其父結點的大小,如果新添元素大於其父結點,則上浮
- 直到新添元素小於其父結點值,或新添元素已經上浮到根結點位置
/**
* index索引的元素執行上浮操作
*
* @param index
*/
private void siftUp(int index) {
//index不可以是根節點,所以必須>0 ,且上浮結點的值必須大於其父節點的值,只要滿足條件,一直上浮
while (index > 0 && array.get(index).compareTo(array.get(parent(index))) > 0) {
//交換位置
array.swap(index, parent(index));
//下一個
index = parent(index);
}
}
下沉 shift down
- 首先判斷下沉元素的孩子結點,看是左孩子大還是右孩子大?取最大的那個孩子與下沉結點比較,如果下沉元素比最大孩子的值還要大,就不需要下沉,已經滿足最大堆特性
- 如果下沉元素比最大孩子小,則交換位置。
- 不斷重複判斷是否執行下沉操作,直到下沉元素比孩子結點的值大或者下沉元素已經是葉子結點
/**
* 對索引爲index的元素進行下沉操作
*
* @param index
*/
private void siftDown(int index) {
/**
* 下沉同樣是一個循環,只要不是葉子結點不斷循環
* 1. 只要下沉元素的左孩子的索引小於等於數組的最大索引,就代表下沉元素還不是葉子結點,還可以循環
*/
while (lchild(index) <= array.getSize() - 1) {
/**
* 1. 求左右孩子誰的值大,就取誰的索引
*/
//獲得左右孩子索引
int lIndex = lchild(index);
int rIndex = rchild(index);
int max = 0;
//求最大
//如果其右孩子索引大於數組的最大索引,則越界,不存在右孩子 | while循環已經保證了肯定有左孩子 |右孩子索引沒有越界,就代表有右孩子
if (rIndex > array.getSize() - 1) {
max = lIndex;
//如果有右孩子,則比較左右孩子的大小,取最大的孩子的索引
} else {
max = array.get(lIndex).compareTo(array.get(rIndex)) > 0 ? lIndex : rIndex;
}
/**
* 下沉元素與最大孩子結點比較
* 1. 如果下沉元素比最大的孩子結點都要大,那麼這就代表下沉已經結束,堆結構特性已經滿足
*/
if (array.get(index).compareTo(array.get(max)) >= 0) {
break;
}
//如果下沉元素沒有最大孩子結點大,則交換位置,繼續下沉
array.swap(index, max);
//下一個
index = max;
}
}
添加元素 add
- 時間複雜度O(logn)
- 追加元素到數組尾部
- 對新添元素進行上浮操作,直到滿足最大堆特性
/**
* 給堆添加一個元素
*
* @param data
*/
public void add(T data) {
//動態數組中追加元素
array.addLast(data);
//新添元素執行上浮操作,傳入新添元素的索引,即最後一個位置
siftUp(array.getSize() - 1);
}
取最大值 extractMax
- 時間複雜度O(logn)
- 取出最大值,把堆中(數組)最後的元素與根結點交換位置,刪除最後的元素
- 對交換後的根結點元素進行下沉操作,直到滿足最大堆特性
/**
* 取出堆中的最大值
*
* @return
*/
public T extractMax() {
//找到最大值
T max = array.get(0);
//最後元素和根結點交換位置
array.swap(0, array.getSize() - 1);
//刪除最後的元素
array.removeLast();
//下沉操作
siftDown(0);
return max;
}
取最大值,並插入新元素 replace
- 原思想,extractMax + add 兩個O(logn)操作
- 但是,我們直接把要插入的元素替換到根結點位置,再下沉,就只需要一個O(logn)了
/**
* 取出最大元素,同時插入一個新元素
* 原思想,extractMax + add 兩個O(logn)操作
* 但是,我們直接把要插入的元素替換到根結點位置,再下沉,就只需要一個O(logn)了
*
* @param data
* @return
*/
public T replace(T data) {
//獲得最大值
T max = array.get(0);
//根結點替換爲新元素
array.set(0, data);
//對新元素進行下沉
siftDown(0);
return max;
}
將任意數組堆化 heapify
重點步驟:
- 先找到堆的第一個非葉子節點(方式可以通過找到最後一個結點,它的父結點,就是第一個非葉子結點)
- 所有非葉子結點,逐一下沉,直到根結點也完成下沉,就是整棵完全二叉樹堆化完成
好處:
- 將n各元素逐個插入到一個空堆中,時間複雜度是O(nlogn)
- heapify的時間複雜度是O(n), 推算比較複雜,這裏記住就好
/**
* 實現構造方法中
* 將數組堆化,heapify過程
*
* @param array
*/
public MaxHeap(T[] array) {
this.array = new Array<>(array);
/**
* heapify過程
* 1. i 初始化爲 第一個非葉子結點的索引,通過最後一個元素的父結點的方式確定
* 2. i之後每次減1,就是上一個非葉子結點,直到根結點也完成下沉化
* 3. 最後完成堆化
*/
for (int i = parent(array.length - 1); i >= 0; i--) {
siftDown(i);
}
}
Java代碼實現
描述
- 以動態數組爲底層數據結構
- 從數組的第一個位置,即索引爲0的地方開始存放元素
- 最大堆
- 堆內元素必須可以比較,即實現了Comparable接口
實現功能
- 上浮 shift up;
- 下沉 shift down
- 添加元素 add
- 取最大值 extractMax
- 取最大值,同時插入新元素 replace
- 將任意數組堆化 heapify
完整代碼
動態數組
package com.snailmann.datastructure.heap;
/**
* 動態數據
*
* @param <E>
*/
public class Array<E> {
private E[] data;
private int size;
public Array(int capacity) { // user assign size
data = (E[]) new Object[capacity];
size = 0;
}
public Array() {
this(10); // default size
}
public Array(E[] arr) {
data = (E[]) new Object[arr.length];
for (int i = 0; i < arr.length; i++) {
data[i] = arr[i];
}
size = arr.length;
}
public int getSize() {
return size;
}
public int getCapacity() {
return data.length;
}
public boolean isEmpty() {
return size == 0;
}
public void rangeCheck(int index) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("Index is Illegal!");
}
}
public void add(int index, E e) {
if (index < 0 || index > size) {
throw new IllegalArgumentException("Index is Illegal ! ");
}
if (size == data.length) {
resize(data.length * 2);
}
for (int i = size - 1; i >= index; i--) {
data[i + 1] = data[i];
}
data[index] = e;
size++;
}
private void resize(int newCapacity) {
E[] newData = (E[]) new Object[newCapacity];
for (int i = 0; i < size; i++) {
newData[i] = data[i];
}
data = newData;
}
public void addLast(E e) { //末尾添加
add(size, e);
}
public void addFirst(E e) { //頭部添加
add(0, e);
}
public E get(int index) {
rangeCheck(index);
return data[index];
}
public E getLast() {
return get(size - 1);
}
public E getFirst() {
return get(0);
}
public void set(int index, E e) {
rangeCheck(index);
data[index] = e;
}
public boolean contains(E e) {
for (int i = 0; i < size; i++) {
if (data[i].equals(e)) {
return true;
}
}
return false;
}
public int find(E e) {
for (int i = 0; i < size; i++) {
if (data[i].equals(e)) {
return i;
}
}
return -1;
}
public E remove(int index) { // remove data[index] and return the value
rangeCheck(index);
E res = data[index];
for (int i = index; i < size - 1; i++) {
data[i] = data[i + 1];
}
size--;
data[size] = null;//loitering objects != memory leak
if (size == data.length / 4 && data.length / 2 != 0) {
resize(data.length / 2); //防止複雜度的震盪
}
return res;
}
public E removeFirst() {
return remove(0);
}
public E removeLast() {
return remove(size - 1);
}
public void removeElement(E e) { //only remove one(may repetition) and user not know whether is deleted.
int index = find(e);
if (index != -1) {
remove(index);
}
}
// new method
public void swap(int i, int j) {
if (i < 0 || i >= size || j < 0 || j >= size) {
throw new IllegalArgumentException("Index is illegal.");
}
E t = data[i];
data[i] = data[j];
data[j] = t;
}
@Override
public String toString() {
StringBuilder res = new StringBuilder();
res.append(String.format("Array : size = %d, capacity = %d\n", size, data.length));
res.append("[");
for (int i = 0; i < size; i++) {
res.append(data[i]);
if (i != size - 1) {
res.append(", ");
}
}
res.append("]");
return res.toString();
}
}
最大堆:
package com.snailmann.datastructure.heap;
import java.util.Arrays;
import java.util.Random;
/**
* 最大堆 | 採用動態數組結構
* 從索引爲0的地方開始存儲,動態數組
* 最大堆元素必須可以比較,即實現Comparable接口
* 父結點: (n - 1)/ 2
* 左孩子: 2 * n + 1
* 右孩子: 2 * n + 2
*
* @param <T>
*/
public class MaxHeap<T extends Comparable<T>> {
/**
* 底層數據結構 | 動態數組
*/
private Array<T> array;
public MaxHeap() {
this.array = new Array<>();
}
/**
* 將數組堆化,heapify過程
*
* @param array
*/
public MaxHeap(T[] array) {
this.array = new Array<>(array);
//heapify
heapify(array);
}
/**
* heapify過程 | 最大堆
* 1. i 初始化爲 第一個非葉子結點的索引,通過最後一個元素的父結點的方式確定
* 2. i之後每次減1,就是上一個非葉子結點,直到根結點也完成下沉化
* 3. 最後完成堆化
*
* @param array
*/
public void heapify(T[] array) {
for (int i = parent(array.length - 1); i >= 0; i--) {
siftDown(i);
}
}
/**
* 返回堆的元素個數
*
* @return
*/
public int size() {
return array.getSize();
}
/**
* 堆是否爲空
*
* @return
*/
public boolean isEmpty() {
return array.isEmpty();
}
/**
* 獲取某個結點的父結點索引
*
* @param index
* @return
*/
private int parent(int index) {
if (index == 0) {
throw new RuntimeException("根結點沒有父結點");
}
return (index - 1) / 2;
}
/**
* 獲取某個結點的左孩子索引
*
* @param index
* @return
*/
private int lchild(int index) {
return (2 * index) + 1;
}
/**
* 獲取某個結點的右孩子索引
*
* @param index
* @return
*/
private int rchild(int index) {
return (2 * index) + 2;
}
/**
* 給堆添加一個元素 | 時間複雜度O(logn)
*
* @param data
*/
public void add(T data) {
//動態數組中追加元素
array.addLast(data);
//新添元素執行上浮操作,傳入新添元素的索引,即最後一個位置
siftUp(array.getSize() - 1);
}
/**
* index索引的元素執行上浮操作
*
* @param index
*/
private void siftUp(int index) {
//index不可以是根節點,所以必須>0 ,且上浮結點的值必須大於其父節點的值,只要滿足條件,一直上浮
while (index > 0 && array.get(index).compareTo(array.get(parent(index))) > 0) {
//交換位置
array.swap(index, parent(index));
//下一個
index = parent(index);
}
}
/**
* 取出堆中的最大值 | 時間複雜度O(logn)
*
* @return
*/
public T extractMax() {
//找到最大值
T max = array.get(0);
//最後元素和根結點交換位置
array.swap(0, array.getSize() - 1);
//刪除最後的元素
array.removeLast();
//下沉操作
siftDown(0);
return max;
}
/**
* 對索引爲index的元素進行下沉操作
*
* @param index
*/
private void siftDown(int index) {
/**
* 下沉同樣是一個循環,只要不是葉子結點不斷循環
* 1. 只要下沉元素的左孩子的索引小於等於數組的最大索引,就代表下沉元素還不是葉子結點,還可以循環
*/
while (lchild(index) <= array.getSize() - 1) {
/**
* 1. 求左右孩子誰的值大,就取誰的索引
*/
//獲得左右孩子索引
int lIndex = lchild(index);
int rIndex = rchild(index);
int max = 0;
//求最大
//如果其右孩子索引大於數組的最大索引,則越界,不存在右孩子 | while循環已經保證了肯定有左孩子 |右孩子索引沒有越界,就代表有右孩子
if (rIndex > array.getSize() - 1) {
max = lIndex;
//如果有右孩子,則比較左右孩子的大小,取最大的孩子的索引
} else {
max = array.get(lIndex).compareTo(array.get(rIndex)) > 0 ? lIndex : rIndex;
}
/**
* 下沉元素與最大孩子結點比較
* 1. 如果下沉元素比最大的孩子結點都要大,那麼這就代表下沉已經結束,堆結構特性已經滿足
*/
if (array.get(index).compareTo(array.get(max)) >= 0) {
break;
}
//如果下沉元素沒有最大孩子結點大,則交換位置,繼續下沉
array.swap(index, max);
//下一個
index = max;
}
}
/**
* 取出最大元素,同時插入一個新元素
* 原思想,extractMax + add 兩個O(logn)操作
* 但是,我們直接把要插入的元素替換到根結點位置,再下沉,就只需要一個O(logn)了
*
* @param data
* @return
*/
public T replace(T data) {
//獲得最大值
T max = array.get(0);
//根結點替換爲新元素
array.set(0, data);
//對新元素進行下沉
siftDown(0);
return max;
}
public static void main(String[] args) {
int len = 100;
Random random = new Random();
MaxHeap<Integer> maxHeap = new MaxHeap<>();
for (int i = 0; i < len; i++) {
maxHeap.add(random.nextInt(100));
}
int[] arr = new int[len];
for (int i = 0; i < len; i++) {
arr[i] = maxHeap.extractMax();
}
System.out.println(Arrays.toString(arr));
}
}
- 同一個無序數組,通過一個一個add出來的堆結構和heapify出來的堆結構,實際的結點位置是會有偏差的,不過都滿足堆的特性
堆排序
描述
什麼是堆排?
本來這裏主要是講一下堆的結構和實現,但是其實堆排序其實也是一個挺重要的數據結構知識,畢竟屬於基本的八大排序之一嘛,所以這裏就再補充一些堆排的知識
我們知道,堆的底層結構就是一個數組,所以我們就可以利用這個數組同時是堆的底層結構的特性,利用堆的特性,對這個數組進行排序,得到一個有序數組。 利用堆的特性對底層無序數組排序的過程就是堆排
堆排的特性:
堆排不跟其他排序算法一樣,不依賴什麼東西,堆排的實現非常的依賴堆這個數據結構,所以無堆則無堆排,所以如果我們要對一個無序數組進行排序,首先就要將該無序數組構造成一個最大堆或最小堆,不同種的堆也會造成不同的順序排序
最大堆
堆排後的結果是升序序列
最小堆
堆排後的結果是降序序列
堆排的時間複雜度是:
基本思想和實現步驟
堆排是對一個無序數組堆化,再排的過程,所以它的核心思想就是:
- 看無序數組是想進行什麼排序?升序還是降序?
- 如果升序,就先將無序數組堆化成最大堆,反之則最小堆。通過heapify去實現
- 堆化後,將堆頂元素與末尾元素交換,堆的結點長度減1,對當前堆進行重建,下沉,直到重新滿足堆特性
- 遍歷堆中未交換過的結點,遍歷交換完畢後,就是數組排序的結束
代碼實現
package com.snailmann.datastructure.heap;
/**
* 堆排 | 升序 |O(nlogn)
* 最大堆 -> 升序排序
*
* @param <T>
*/
public class MaxHeapSort<T extends Comparable<T>> {
/**
* 底層數據結構 | 動態數組
*/
private Array<T> array;
public MaxHeapSort() {
this.array = new Array<>();
}
/**
* heapify過程 | 最大堆
* 1. i 初始化爲 第一個非葉子結點的索引,通過最後一個元素的父結點的方式確定
* 2. i之後每次減1,就是上一個非葉子結點,直到根結點也完成下沉化
* 3. 最後完成堆化
*
* @param array
*/
public void heapify(T[] array) {
for (int i = parent(array.length - 1); i >= 0; i--) {
siftDown(i, array.length);
}
}
/**
* 獲取某個結點的父結點索引
*
* @param index
* @return
*/
private int parent(int index) {
if (index == 0) {
throw new RuntimeException("根結點沒有父結點");
}
return (index - 1) / 2;
}
/**
* 獲取某個結點的左孩子索引
*
* @param index
* @return
*/
private int lchild(int index) {
return (2 * index) + 1;
}
/**
* 獲取某個結點的右孩子索引
*
* @param index
* @return
*/
private int rchild(int index) {
return (2 * index) + 2;
}
/**
* 對索引爲index的元素進行下沉操作
*
* @param index 下沉元素索引
* @param len 要重建的堆的結點個數
*/
private void siftDown(int index, int len) {
/**
* 下沉同樣是一個循環,只要不是葉子結點不斷循環
* 1. 只要下沉元素的左孩子的索引小於整個數組的長度,就代表下沉元素還不是葉子結點,還可以循環
*/
while (lchild(index) <= len - 1) {
/**
* 1. 求左右孩子誰的值大,就取誰的索引
*/
//獲得左右孩子索引
int lIndex = lchild(index);
int rIndex = rchild(index);
int max = 0;
//求最大
//如果其右孩子索引大於數組的最大索引,則越界,不存在右孩子 | while循環已經保證了肯定有左孩子 |右孩子索引沒有越界,就代表有右孩子
if (rIndex > len - 1) {
max = lIndex;
//如果有右孩子,則比較左右孩子的大小,取最大的孩子的索引
} else {
max = array.get(lIndex).compareTo(array.get(rIndex)) > 0 ? lIndex : rIndex;
}
/**
* 下沉元素與最大孩子結點比較
* 1. 如果下沉元素比最大的孩子結點都要大,那麼這就代表下沉已經結束,堆結構特性已經滿足
*/
if (array.get(index).compareTo(array.get(max)) >= 0) {
break;
}
//如果下沉元素沒有最大孩子結點大,則交換位置,繼續下沉
array.swap(index, max);
//下一個
index = max;
}
}
/**
* 堆排序 | 最大堆 -> 升序 | 時間複雜度O(nlogn)
* 1. 把無序數組堆化,構建二叉堆
* 2. 將堆頂元素與末尾元素交換,循環下沉直至重新滿足最大堆結構,待所有元素都交換完畢後,排序完成
*
* @param arary
*/
public void heapSort(T[] arary) {
//將無序數組構建成一個最大堆
this.array = new Array<>(arary);
heapify(arary);
System.out.println(this.array.toString());
//循環交換,重建的過程
for (int i = this.array.getSize() - 1; i > 0; i--) {
//交換位置
this.array.swap(0, i);
//每交換一次,實際的堆結構減少一個長度,因爲交換到尾部的大數值,已經排序完畢
int len = i - 1;
//重建,下沉
siftDown(0, len);
}
System.out.println(this.array.toString());
}
@Override
public String toString() {
return this.array.toString();
}
public static void main(String[] args) {
Integer[] nums = new Integer[]{3, 4, 1, 3, 0, 4, 7, 9};
MaxHeapSort<Integer> maxHeapSort = new MaxHeapSort<>();
maxHeapSort.heapSort(nums);
}
}
參考資料
- 數據結構:堆(Heap)- @作者:唐先僧
- ZXZxin/ZXBlog/優先隊列和堆的總結 - @作者:ZXZxin
- 圖解排序算法(三)之堆排序 - @作者:dreamcatcher-cx
- 如果覺得對你有幫助,能否點個贊或關個注,以示鼓勵筆者呢?!