【Java】基础排序算法-堆排序
前言
堆排序的基本原理也是选择排序,只是不再使用遍历的方式查找无序区间的最大的数,而是通过堆来选择无序区间的最大的数。
一、堆(heap)
概念
- 堆逻辑上是一棵完全二叉树(下文称其为:二叉堆),因此二叉堆物理上是保存在数组中,且不会有空间的浪费;
- 若堆满足任意结点的值都大于其子树中结点的值,则此堆叫做大堆,或者大根堆;
- 若堆满足任意结点的值都小于其子树中结点的值,则此堆叫做小堆,或者小根堆;
- 堆的基本作用是可以快速找集合中的最值,可以辅助实现集合的排序问题;
堆在数组中的存储
1. 已知双亲(parent)的下标,则:
a. 左孩子(left)下标 = 2 * parent + 1;
b. 右孩子(right)下标 = 2 * parent + 2;
2. 已知孩子(不区分左右)(child)下标,则:
c. 双亲(parent)下标 = (child - 1) / 2;
二、将数组调节为最大堆或最小堆
最大堆为例:(向下调整)
1.找到数组中代表二叉堆里最后一个非叶子节点的位置索引index(即找到最后一个叶子节点的双亲节点:index = (数组最后一个元素索引-1) / 2);
2.以步骤1找到节点为根节点与其左右子树值进行比较(向下调整);
根据最大堆特点,根节点值应该为当前树中的最大值,所以:
a.若根节点值大于左右孩子的值,则说明此小树已经满足最大堆条件;
b.若根节点值小于左右孩子中的任意一个,则将根节点值与左右孩子中的较大值进行交换;
c.以步骤b交换值后的那个孩子为根节点,重复a、b、c步骤,不断向下调整,直到满足步骤a的条件;
3.index--,再重复2步骤,直到将index = 0 即根节点处理结束后,此时的二叉堆就是一个最大堆;
三、 利用最大堆进行排序:
根据最大堆特点,堆顶元素为当前堆中最大值
将数组进行区间划分:[0,arr.length-i)为待排序区间;[arr.length-i,arr.length)为已排序的升序区间,i初始值为0;
a.将数组[0,arr.length-i)区间调整为最大堆,此时待排序区间首元素为最大值;
b.将待排序区间首元素值与尾元素值进行交换,此时最大值到区间最后即i的位置;
c.待排序区间缩小即i++;已排序区间随之扩大
d.将交换到区间首位置的元素进行向下调整,使待排区间满足最大堆条件
重复a、b、c、d步骤,直到整个数组变得升序有序
时间复杂度:O(N*logN)
空间复杂度:O(1)
稳定性:不稳定,有大量元素交换过程
代码实现:
/**
* 堆排序
* 排升序用大堆;排降序用小堆
* 以排升序为例
*/
public static void heapSort(int[] arr) {
//index为最后一个非叶子节点元素索引,即最后一个叶子节点的双亲节点
int index = (arr.length - 1 - 1) / 2;
//将数组调节为最大堆
for (; index >= 0; index--) {
siftDown(arr, index, arr.length);
}
//堆顶元素依次交换至末尾
for (int i = arr.length - 1; i > 0; i--) {
int max = arr[0];
arr[0] = arr[i];
arr[i] = max;
//调整待排区间为最大堆
siftDown(arr, 0, i);
}
}
//向下调整,实现最大堆过程
public static void siftDown(int[] arr, int index, int length) {
//index位置对应的左孩子索引为2 * index + 1
//越向下调整,index的子节点索引值越大
while (2 * index + 1 < length) {
//当前index位置的左孩子
int k = 2 * index + 1;
//找左右孩子中的较大值
if (k + 1 < length && arr[k + 1] > arr[k]) {
k = k + 1;
}
//此时满足最大堆条件
if (arr[index] > arr[k]) {
break;
} else {
//将根节点值与左右孩子中的较大值进行交换
int temp = arr[index];
arr[index] = arr[k];
arr[k] = temp;
//以交换值后的那个孩子为根节点继续向下调整
index = k;
}
}
}
测试:
简单的对堆排序、希尔排序、插入排序进行测试,发现堆排序处理不同的数据集时效率还是非常不错的
内容如有任何不妥之处,恳请指正