这是一个很有意思的算法!
堆的基本概述:
堆的本质:
实际上是一棵完全二叉树,所以将会有一下性质!
1、下标为 i 的节点的父节点下标 :( i - 1 ) / 2
2、下标为 i 的节点的左孩子下标 :i * 2 + 1
3、下标为 i 的节点的右孩子下标 :i * 2 + 2
堆的建立:
堆分为两类 :
大顶堆 : 根节点 > 左右孩子
小堆顶 : 根节点 < 左右孩子
注意:我们只是建堆并非排序、堆没有要求左右孩子值的关系!!
建堆(以大顶堆为例):
默认:从最后一个根节点开始建立,即 根 i =(n - 1 ) / 2
维护堆 :
判断根的左右孩子中最大值是否大于根节点
根节点数组下标 i 值arr[i] 寻找孩子中的大孩子 j = i * 2 + 1 左孩子值 arr[j] 右孩子值 arr[j+1] 如果左孩子大,大孩子就为 j ,如果右孩子大,大孩子就为 j+1 if ( arr[j] < arr[j+1] ) j++ 找到大孩子后与根进行比较 大孩子值 < 根 大孩子 > 根 swap(arr[i],arr[j]) break 判断当前交换的孩子是否还有孩子,如果有将重复以上操作 i = j ; j = i * 2 +1;
static void HeapSort0(int arr[], int n) {
//下标为i节点的父节点下标 : (i - 1)/2
//下标为i节点的左孩子下标 : i * 2 + 1
//下标为i节点的右孩子下标 : i * 2 + 2
for (int i = (n - 1) / 2; i >= 0; i--) {
//获取到所有根节点、从最后一个根节点开始
SiftHeap(arr, i, n);
}
}
static void SiftHeap(int[] arr, int k, int n) {
//建立大顶堆 根 > 左、右孩子的值
//注意 : 没有要求左孩子与右孩子的关系!
int i = k;
int j = 2 * i + 1;
while (j < n) { //判断该根节点是否有叶子节点
//左孩子为 j
//右孩子为 j+1
if (j < n - 1 && arr[j] < arr[j + 1]) j++; //判段俩个孩子的最大值
if (arr[i] > arr[j]) break; //如果根节点大于孩子值break;
else { //将根节点与孩子值交互
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
i = j; j = 2 * i + 1; //当新值出现后之前建立的将可能不满足条件、需要重新检查!
}
}
}
堆排序
可能会出乎你的意料: 大顶堆得到的是降序、小堆顶得到的是升序;
---
从堆的建立中我们会发现堆顶根永远为最大\最小
如此将堆顶根放到数组最后一个位置,并将数组的范围减去最后一个位置
大顶堆中、将堆顶根【当前树中最大元素】依次从数组最后一个位置放过来、就形成了升序队列
小顶堆中、将堆顶根【当前树中最大元素】依次从数组最后一个位置放过来、就形成了降序队列
static void HeapSort1(int arr[], int n) {
for (int i = 1; i <= n - 1; i++) {
//让所有根重新建堆
int temp = arr[0];
arr[0] = arr[n - i];
arr[n - i] = temp;
SiftHeap(arr, 0, n - i);
}
}
完整测试代码如下:
输入数组 {1, 5, 2, 6, 4, 10, 7, 21, 18, 3};
排序后 {1, 2, 3, 4, 5, 6, 7, 10, 18, 21 };
package ZuoYe;
public class 堆排序 {
public static void main(String[] args) {
int[] arr = {1, 5, 2, 6, 4, 10, 7, 21, 18, 3};
HeapSort(arr, arr.length);
for (int x : arr) {
System.out.print(x + " ");
}
}
static void SiftHeap(int[] arr, int k, int n) {
//建立大顶堆 根 > 左、右孩子的值
//注意 : 没有要求左孩子与右孩子的关系!
int i = k;
int j = 2 * i + 1;
while (j < n) { //判断该根节点是否有叶子节点
//左孩子为 j
//右孩子为 j+1
if (j < n - 1 && arr[j] < arr[j + 1]) j++; //判段俩个孩子的最大值
if (arr[i] > arr[j]) break; //如果根节点大于孩子值break;
else { //将根节点与孩子值交互
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
i = j; j = 2 * i + 1; //当新值出现后之前建立的将可能不满足条件、需要重新检查!
}
}
}
static void HeapSort(int arr[], int n) {
//下标为i节点的父节点下标 : (i - 1)/2
//下标为i节点的左孩子下标 : i * 2 + 1
//下标为i节点的右孩子下标 : i * 2 + 2
for (int i = (n - 1) / 2; i >= 0; i--) {
//获取到所有根节点、从最后一个根节点开始
SiftHeap(arr, i, n);
}
//大顶堆建立完成
//将大顶堆排序
for (int i = 1; i <= n - 1; i++) {
//让所有根重新建堆
int temp = arr[0];
arr[0] = arr[n - i];
arr[n - i] = temp;
//交换的目的:
//arr[0] 大顶堆
//n-i 当前数组中最后一个叶子
//最大值就依次丢到最后、构成了升序数组!
SiftHeap(arr, 0, n - i);
}
}
}
希望可以给你帮助!