堆
博客書寫不易,您的點贊收藏是我前進的動力,千萬別忘記點贊、 收**藏 ^ _ ^ !
- 心中有堆 https://blog.csdn.net/luo_boke/article/details/106928990
- 心中有樹——基礎 https://blog.csdn.net/luo_boke/article/details/106980011
- 心中有棧 https://blog.csdn.net/luo_boke/article/details/106982563
堆是一顆完全二叉樹,是一種經過排序的樹形數據結構,它滿足如下性質:
- 堆序性:任一結點值均小於(或大於)它的所有後代結點值,最小值結點(或最大值結點)在堆的根上。
- 結構性:堆總是一棵完全二叉樹,即除了最底層,其他層的結點都被元素填滿,且最底層儘可能地從左到右填入。
二叉樹又是啥?請查看我的另一篇博文《心中有樹——基礎》
小根堆與大根堆
小根堆:結點值小於後代結點值的堆,也叫最小堆,圖左
大根堆:結點值大於後代結點值的堆,也叫最大堆,圖右
二叉堆
二叉堆是一種特殊的堆,即每個結點的子結點不超過2個。堆排序就是使用的二叉堆。
堆的存儲
一般使用線性數據結構(如數組)存儲堆:
- 根結點存儲在第0個位置
- 結點i的左孩子存儲在2*i+1的位置
- 結點i的右孩子存儲在2*i+2的位置
堆構建過程
1)根據子結點推父結點:(n-1)/2
2)根據父結點推子結點:左子結點(2n+1),右子結點(2n+2)
索引由0開始計數
我們以9、12、5、24、0、1、99、3、10、7 這10個數來構建大根堆如下,
- 首先我們將現在的無序序列看成一個堆結構,一個沒有規則的二叉樹,將序列裏的值按照從上往下,從左到右依次填充到二叉樹中。
- 從最後一個葉子7遍歷父結點,如果父<左右子結點最大值,則父結點值與最大子結點交換值。發現7和10都小於35,切換至99和3的父結點,發現99>24,交換24與99的位置。
- 繼續比較0和1的父結點,因0<5,1<5,往下走比較99和35的父結點,最大子結點99>12,交換位置。交換後,此時需要對12這個父結點進行排序,發現24>12,此時需要交換12和24的位置。
- 繼續對父結點9進行遍歷操作,需要和99換位置,同理9需要最大的子結點35換位置。
- 最終獲得了大堆根
構建代碼
/***
* 構建大根堆
* @param array 數據源
*/
private void buildHeap(int[] array) {
//從右向左,從下到上依次遍歷父結點,建立大根堆,時間複雜度:O(n*log2n)
for (int i = (array.length - 1 - 1) / 2; i >= 0; i--) {
adjust(array, i, array.length - 1);
}
}
/**
* 將指定堆構建成大堆根函數
* 邏輯
* 1. 如果起始索引無子結點,則跳出該方法
* 2. 如果只有一個左子結點,進行大小比較並置換值
* 3. 如果有兩個子結點,選擇最大值與父結點比較,然後置換其位置。
* 如果子結點大於父結點,置換完成後,遞歸進行同樣操作,其子結點索引即是函數的start值
*
* @param array 源數組
* @param start 起始索引
* @param end 結尾索引
*/
public void adjust(int[] array, int start, int end) {
// 左子結點的位置
int leftIndex = 2 * start + 1;
if (leftIndex == end) {
//只有一個左結點,進行比較並置換值
if (array[leftIndex] > array[start]) {
int temp = array[leftIndex];
array[leftIndex] = array[start];
array[start] = temp;
}
} else if (leftIndex < end) {
//有兩個子結點
int temp = array[leftIndex];
int tempIndex = leftIndex;
if (array[leftIndex + 1] > array[leftIndex]) {
temp = array[leftIndex + 1];
tempIndex = leftIndex + 1;
}
if (temp > array[start]) {
array[tempIndex] = array[start];
array[start] = temp;
}
adjust(array, tempIndex, end);
}
}
堆排序過程
上面我們將大根堆構建好了,現在我們對堆進行排序。其構建思想是:
- 根據堆的特點進行編寫,先將一組擁有n個元素的數據構建成大根堆或者小根堆(我按照大根堆進行介紹,小根堆是一樣的思想)。
- 再將根結點上的數和堆最後一位數據進行互換,此時,第n位的數就是整個序列中最大的數。
- 然後再將前n-1爲元素進行構建形成大根堆,再將根結點與第n-1位數據進行互換,得到第二大數據,此時倒數兩個數據無疑是有序的。
- 然後將前n-2個數據構建成大根堆,依次循環直到剩下一位元素,則說明第一位後面的數字都是有序的,並且比第一位數大,此時排序完成。
- 1)將99和7替換,對排除99的堆進行重新構建大根堆
- 2)替換35和9的位置,對剩下的8個數進行重新構建大堆根
- 3)24與3交換位置,對剩餘的7個數重新構建大根堆
- 4)後續過程同理,最終得到經過排序完成的堆 0、1、3、5、7、9、10、12、24、35、99
排序代碼
/**
* 堆排序
*
* @param array 源數組
*/
public void heapSort(int[] array) {
buildHeap(array);
int tmp;
//要與root結點置換位置元素的索引
int end = array.length - 1;
//n個結點只用構建排序n-1次,最後只有1個元素不用在排序
for (int i = array.length - 1; i > 0; i--) {
tmp = array[0];
array[0] = array[end];
array[end] = tmp;
end--;
//頭尾置換後,將堆重新構建爲大堆根,置換尾部大元素不參加構建
//因爲除了root結點,其他都是由大到小有序的,所以再次構建大根堆時,不用在進行adjust()前的那個循環
adjust(array, 0, end);
}
}
堆添加元素
添加元素時,新元素被添加到數組末尾,但是添加元素後,堆的性質可能會被破壞,需要向上調整堆結果。如給大堆根堆添加元素100。
此時此時堆的結構被破壞,需要從下往上進行調整。因100大於0,5,99,則新的大堆根爲
元素添加代碼
/**
* 在 array 是大堆根的前提下添加元素然後重構大堆根
*
* @param array 大堆根數組
* @param value 添加的元素值
*/
private void addHeap(int[] array, int value) {
int[] arr = new int[array.length + 1];
System.arraycopy(array, 0, arr, 0, array.length);
arr[arr.length - 1] = value;
int currentIndex = arr.length - 1;
int parentIndex = (arr.length - 1) / 2;
while (parentIndex >= 0) {
if (value > arr[parentIndex]) {
int temp = arr[parentIndex];
arr[parentIndex] = value;
arr[currentIndex] = temp;
//如果最後一個元素的父結點還有父結點需要繼續進行對比
currentIndex = parentIndex;
parentIndex = (currentIndex - 1) / 2;
} else {
break;
}
}
}
堆刪除元素
堆刪除元素都是從結點刪除,然後以這個結點爲root結點的數組的最後一個元素移動到根結點的位置,並向下調整堆結構,直至重新符合堆序性。如我們刪除結點35,將7移到35的位置
將7與其子結點逐個比較,直至符合大根堆規則
刪除元素代碼
/**
* 在 array 是大堆根的前提下刪除元素然後重構大堆根
*
* @param array 大堆根數組
* @param deleteIndex 刪除元素的索引
*/
private int[] deleteHeap(int[] array, int deleteIndex) {
array[deleteIndex] = array[array.length - 1];
int[] arr = new int[array.length - 1];
System.arraycopy(array, 0, arr, 0, array.length - 1);
int lefeIndex = 2 * deleteIndex + 1;
while (lefeIndex >= arr.length - 1) {
int maxIndex = lefeIndex;
if (arr.length - 1 > lefeIndex) {
if (arr[lefeIndex + 1] > arr[lefeIndex]) {
maxIndex = lefeIndex + 1;
}
}
if (arr[maxIndex] > arr[deleteIndex]) {
int temp = arr[maxIndex];
arr[maxIndex] = arr[deleteIndex];
arr[deleteIndex] = temp;
lefeIndex = 2 * maxIndex + 1;
} else {
break;
}
}
return arr;
}
博客書寫不易,您的點贊收藏是我前進的動力,千萬別忘記點贊、 收**藏 ^ _ ^ !