目錄
一 準備知識
1.1 大根堆和小根堆
二 堆排序基本步驟
2.1 構造堆
2.2 固定最大值再構造堆
三 總結
四 代碼
一 準備知識
堆的結構可以分爲大根堆和小根堆,是一個完全二叉樹,而堆排序是根據堆的這種數據結構設計的一種排序,下面先來看看什麼是大根堆和小根堆
1.1 大根堆和小根堆
性質:每個結點的值都大於其左孩子和右孩子結點的值,稱之爲大根堆;每個結點的值都小於其左孩子和右孩子結點的值,稱之爲小根堆。如下圖
我們對上面的圖中每個數都進行了標記,上面的結構映射成數組就變成了下面這個樣子
1.父結點索引:(i-1)/2(這裏計算機中的除以2,省略掉小數)
2.左孩子索引:2*i+1
3.右孩子索引:2*i+2
所以上面兩個數組可以腦補成堆結構,因爲他們滿足堆的定義性質:
大根堆:arr(i)>arr(2i+1) && arr(i)>arr(2i+2)
小根堆:arr(i)<arr(2i+1) && arr(i)<arr(2i+2)
二 堆排序基本步驟
基本思想:
-
首先將待排序的數組構造成一個大根堆,此時,整個數組的最大值就是堆結構的頂端
-
將頂端的數與末尾的數交換,此時,末尾的數爲最大值,剩餘待排序數組個數爲n-1
3.將剩餘的n-1個數再構造成大根堆,再將頂端數與n-1位置的數交換,如此反覆執行,便能得到有序數組
2.1 構造堆
將無序數組構造成一個大根堆(升序用大根堆,降序就用小根堆)
假設存在以下數組
主要思路:第一次保證0~0位置大根堆結構(廢話),第二次保證0~1位置大根堆結構,第三次保證0~2位置大根堆結構...直到保證0~n-1位置大根堆結構(每次新插入的數據都與其父結點進行比較,如果插入的數比父結點大,則與父結點交換,否則一直向上交換,直到小於等於父結點,或者來到了頂端)
插入6的時候,6大於他的父結點3,即arr(1)>arr(0),則交換;此時,保證了0~1位置是大根堆結構,如下圖:
(友情提示:待交換的數爲藍色,交換後的數爲綠色)
插入8的時候,8大於其父結點6,即arr(2)>arr(0),則交換;此時,保證了0~2位置是大根堆結構,如下圖
插入5的時候,5大於其父結點3,則交換,交換之後,5又發現比8小,所以不交換;此時,保證了0~3位置大根堆結構,如下圖
插入7的時候,7大於其父結點5,則交換,交換之後,7又發現比8小,所以不交換;此時整個數組已經是大根堆結構
2.2 固定最大值再構造堆
此時,我們已經得到一個大根堆,下面將頂端的數與最後一位數交換,然後將剩餘的數再構造成一個大根堆
(友情提示:黑色的爲固定好的數字,不再參與排序)
此時最大數8已經來到末尾,則固定不動,後面只需要對頂端的數據進行操作即可,拿頂端的數與其左右孩子較大的數進行比較,如果頂端的數大於其左右孩子較大的數,則停止,如果頂端的數小於其左右孩子較大的數,則交換,然後繼續與下面的孩子進行比較
下圖中,5的左右孩子中,左孩子7比右孩子6大,則5與7進行比較,發現5<7,則交換;交換後,發現5已經大於他的左孩子,說明剩餘的數已經構成大根堆,後面就是重複固定最大值,然後構造大根堆
如下圖:頂端數7與末尾數3進行交換,固定好7,
三 總結
到這裏,大家應該對堆排序都有了自己的見解,我們對上面的流程總結下:
1、首先將無需數組構造成一個大根堆(新插入的數據與其父結點比較)
2、固定一個最大值,將剩餘的數重新構造成一個大根堆,重複這樣的過程
四 代碼
代碼中主要兩個方法:
1、將待排序數組構造成一個大根堆(元素上升)
2、固定一個最大值,將剩餘的數再構造成一個大根堆(元素下降)
//堆排序
public static void heapSort(int[] arr) {
//構造大根堆
heapInsert(arr);
int size = arr.length;
while (size > 1) {
//固定最大值
swap(arr, 0, size - 1);
size--;
//構造大根堆
heapify(arr, 0, size);
}
}
//構造大根堆(通過新插入的數上升)
public static void heapInsert(int[] arr) {
for (int i = 0; i < arr.length; i++) {
//當前插入的索引
int currentIndex = i;
//父結點索引
int fatherIndex = (currentIndex - 1) / 2;
//如果當前插入的值大於其父結點的值,則交換值,並且將索引指向父結點
//然後繼續和上面的父結點值比較,直到不大於父結點,則退出循環
while (arr[currentIndex] > arr[fatherIndex]) {
//交換當前結點與父結點的值
swap(arr, currentIndex, fatherIndex);
//將當前索引指向父索引
currentIndex = fatherIndex;
//重新計算當前索引的父索引
fatherIndex = (currentIndex - 1) / 2;
}
}
}
//將剩餘的數構造成大根堆(通過頂端的數下降)
public static void heapify(int[] arr, int index, int size) {
int left = 2 * index + 1;
int right = 2 * index + 2;
while (left < size) {
int largestIndex;
//判斷孩子中較大的值的索引(要確保右孩子在size範圍之內)
if (arr[left] < arr[right] && right < size) {
largestIndex = right;
} else {
largestIndex = left;
}
//比較父結點的值與孩子中較大的值,並確定最大值的索引
if (arr[index] > arr[largestIndex]) {
largestIndex = index;
}
//如果父結點索引是最大值的索引,那已經是大根堆了,則退出循環
if (index == largestIndex) {
break;
}
//父結點不是最大值,與孩子中較大的值交換
swap(arr, largestIndex, index);
//將索引指向孩子中較大的值的索引
index = largestIndex;
//重新計算交換之後的孩子的索引
left = 2 * index + 1;
right = 2 * index + 2;
}
}
//交換數組中兩個元素的值
public static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}