内容简介:堆排序是指利用堆这种数据结构所设计的一种排序算法。堆是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。在本文中我们以大顶堆为例上浮 : 是指在构建堆时,若节点的值大于父节点则将当前节点与父节点互相交换;直至该节点小于父节点时终止上浮(可以理解为一个新入职的员工能力出众晋升更高的职位); 效果如下图
堆排序是指利用堆这种数据结构所设计的一种 排序 算法。堆是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。
堆的特征
- 堆的数据结构近似完全二叉树,即每个节点存在两个子节点
- 当节点的值小于或等于父节点值,大于或等于子节点值称为大顶堆(也即根节点的值最大)
- 当节点的值大于或等于父节点值,小于或等于子节点值称为小顶堆(也即根节点的值最小)
- 若当前节点的索引为 k , 那么左子节点索引为 2 k + 1, 右子节点索引为 2 k + 2, 父节点索引为 (k - 1) / 2
在本文中我们以大顶堆为例
堆的动作 - 上浮
上浮 : 是指在构建堆时,若节点的值大于父节点则将当前节点与父节点互相交换;直至该节点小于父节点时终止上浮(可以理解为一个新入职的员工能力出众晋升更高的职位); 效果如下图
代码如下:
private void siftUp(int k) {
// k == 0 时表明上浮到根节点,结束上浮操作
while (k > 0) {
// 获取父节点索引
int parent = (k - 1) / 2;
// 小于父节点时退出,结束上浮操作
if (less(parent, k)) {
break;
}
// 与父节点交换数据
swap(parent, k);
// 改变 k 的指向 继续上浮
k = parent;
}
}
复制代码
堆的动作 - 下沉
下沉 : 是指构建堆的过程中,若当前节点值小于子节点则将当前节点与子节点互相交换,直至该节点大于子节点时终止下沉(可以理解为一个leader能力平庸的时候被降职的过程,是不是有点很惨); 效果如下图
代码如下:
private void siftDown (int k, int length) {
// 获取左子节点索引
int childIndex = 2 * k + 1;
// 判断是否存在子节点
while (childIndex < length) {
// 判断左右子节点 查找最大的子节点
if (childIndex + 1 < length && !less(childIndex, childIndex + 1)) {
childIndex++;
}
// 若当前节点大于子节点 退出循环
if (less(k, childIndex)) {
break;
}
// 判断当前节点是否小于子节点, 若小于执行交换
swap(k, childIndex);
// 改变 k 指向
k = childIndex;
childIndex = 2 * k + 1;
}
}
复制代码
堆排序
那么如何采用堆的数据结构,对一个无序的数组进行排序呢 ?
- 首先将无序数组构造成一个最大堆,此时根节点为最大值
- 将最后一个节点与根节点值交换,剔除最大值节点;
- 将剩下节点重新执行构造堆
- 循环执行第 2,3 两步操作
无序数组构造堆
将无序数组构造堆,可以采用上浮, 也可以采用下沉的方式处理
如上图所示,为采用 上浮 的方式构建堆,其流程是依次从下标为 0 开始对数组的每个元素进行上浮操作,直至最后得到一个有序的大顶堆。
如上图所示,为采用 下沉 的方式构建堆,其流程是依次从非叶子节点 开始对数组的每个元素进行下沉操作,直至最后得到一个有序的大顶堆。
代码如下:
for (int i = 0; i < array.length; i++) {
// 上浮方式构建堆
siftUp(i);
}
复制代码
// 因为堆是完全二叉树的特性, 所以下标小于等于 array.length / 2 的节点为非叶子节点
// 采用下沉的方式 从下往上构建子堆
for (int i = array.length / 2; i >= 0; i--) {
siftDown(i, array.length);
}
复制代码
完成初始堆构造之后,剩下的工作就是重复进行以下逻辑(这个地方就不画图了):
- 尾节点和根节点交换元素
- 剔除尾节点,对余下的元素进行子堆构造(构造堆的方式与初始堆一样)
完整代码如下 :
public class HeapSort {
private int[] array;
public HeapSort(int[] array) {
this.array = array;
}
private boolean less (int i, int j) {
return array[i] > array[j];
}
private void swap (int k, int j) {
int temp = array[k];
array[k] = array[j];
array[j] = temp;
}
/**
* 下沉操作
*
* @param k
*/
private void siftDown(int k, int length) {
// loop
// 判断是否存在子节点
int childIndex = 2 * k + 1;
while (childIndex < length) {
// 查找最大的子节点
if (childIndex + 1 < length && !less(childIndex, childIndex + 1)) {
childIndex++;
}
// 若当前节点大于子节点 退出循环
if (less(k, childIndex)) {
break;
}
// 判断当前节点是否小于子节点, 若小于执行交换
swap(k, childIndex);
// 改变 k 指向
k = childIndex;
childIndex = 2 * k + 1;
}
}
/**
* 上浮操作
*
* @param k
*/
private void siftUp(int k) {
while (k > 0) {
int parent = (k - 1) / 2;
// 小于父节点时退出
if (less(parent, k)) {
break;
}
// 与父节点交换数据
swap(parent, k);
// 改变 k 的指向
k = parent;
}
}
public void sort () {
// 构造堆
for (int i = 0; i < array.length; i++) {
siftUp(i);
}
print();
int n = array.length - 1;
while (n > 0) {
// 因为每次完成堆的构造后, 根节点为最大(小)值节点
// 将根节点与最后一个节点交换
swap(0, n);
for (int i = 0; i <= n - 1; i++) {
// 排除有序的节点
// 重新构造堆
siftUp(i);
}
print();
n--;
}
}
private void sort1 () {
// 构建堆
// 因为堆是完全二叉树的特性, 所以下标小于等于 array.length / 2 的节点为非叶子节点
// 采用下沉的方式 从下往上构建子堆
for (int i = array.length / 2; i >= 0; i--) {
siftDown(i, array.length);
}
print();
int n = array.length - 1;
while (n > 0) {
// 因为每次完成堆的构造后, 根节点为最大(小)值节点
// 将根节点与最后一个节点交换
swap(0, n);
for (int i = n / 2; i >= 0; i--) {
// 排除有序的节点
// 重新构造堆
siftDown(i, n);
}
print();
n--;
}
}
private void print () {
for (Integer num : array) {
System.out.print(num);
System.out.print(",");
}
System.out.println("");
}
public static void main(String[] args) {
int[] array = {10, 40, 38, 20, 9, 15, 25, 30, 32};
new HeapSort(array).sort();
System.out.println("");
new HeapSort(array).sort1();
}
}
复制代码
小结 : 堆排序在哪里应用到了呢 ? 可参考优先队列 PriorityQueue 的实现
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 图形化排序算法比较:快速排序、插入排序、选择排序、冒泡排序
- 排序算法下——桶排序、计数排序和基数排序
- 算法之常见排序算法-冒泡排序、归并排序、快速排序
- 【JS面试向】选择排序、桶排序、冒泡排序和快速排序简介
- 计数排序vs基数排序vs桶排序
- 排序算法--冒泡排序
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。