找到一个堆排序讲的比较好的博客,自认为画图没有人家好所以摘抄下来分享给大家。
堆排序
堆排序是利用堆这种数据结构而设计的一种排序算法,堆排序是一种选择排序,它的最坏、最好、平均时间复杂度均为O(nlogn);空间复杂度O(1),它也是不稳定排序。
堆
堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大根堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小根堆。
因为是“完全二叉树“(设二叉树的深度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全二叉树。)可以通过数组结构实现,arr[i] 的子节点 arr[2i+1]、arr[2i+2]。
我们用公式来描述一下堆的定义:
大根堆:arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2]
小根堆:arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2]
图解
堆排序的基本思想是:将待排序序列构造成一个大根堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆,这样会得到n-1个元素的次小值。重复上述过程直至树节点数为1。
(1) 构造初始堆。将给定无序序列构造成一个大根堆(一般升序采用大堆,降序采用小根堆)。
a.假设给定无序序列结构如下
b.此时我们从最后一个非叶子结点(第一个非叶子结点 arr.length/2-1),从左至右,从下至上进行调整。
c.找到第二个非叶节点4,由于[4,9,8]中9元素最大,4和9交换。
d.交换导致了子根[4,5,6]结构混乱,继续调整,[4,5,6]中6最大,交换4和6。
此时,我们就将一个无需序列构造成了一个大根堆。
(2)将堆顶元素与末尾元素进行交换,使末尾元素最大。然后继续调整堆,再将堆顶元素与末尾元素交换,得到次大元素。如此反复进行交换、重建。
a.将堆顶元素9和末尾元素4进行交换
b.重新调整结构,使其继续满足堆定义
c.再将堆顶元素8与末尾元素5进行交换,得到第二大元素8.
后续过程,继续进行调整,交换,如此反复进行,最终使得整个序列有序
再简单总结下堆排序的基本思路:
a.将无需序列构建成一个堆,根据升序降序需求选择大根堆或小根堆;
b.将堆顶元素与末尾元素交换,将最大元素"沉"到数组末端;
c.重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序。
思考
1.排序结果是一个小根堆为什么刚开始的时候就不能构建一个小根堆?不能确定左右子树大小,类似冒泡的倒排序。
2.每次堆排序都要把几乎最小的元素移动到堆顶,再进行重排?只是一个分支重排。
3.单纯用一种算法实现可能不是最优解(如何判定最优?时间、空间),只是对算法特性有一定了解以便解决实际问题。
代码实现
/**
* HeapSort
*
* @Date 2018/8/24
* @Author joker
**/
public class HeapSortTest {
private int[] data;
{
initData(10, 200);
}
/**
* 堆排序:升序
* 数组下标 i 的 左子树 2i+1, 右子树 2i+2, 根 (i+1)/2
*/
@Test
public void bigHeapSortTest() {
int offset = data.length - 1;
// 找到最外侧非叶子节点
int no_leaf = (offset + 1) / 2;
// 创建大根堆,遍历非叶子节点
while (no_leaf >= 0) {
change(no_leaf, offset);
no_leaf--;
}
while (offset >= 0) {
// 交换跟节点和offset节点
changeNode(0, offset--);
// 恢复大根堆
change(0, offset);
}
System.out.println("排序结果:");
System.out.println(new Gson().toJson(data));
}
/**
* 递归交换当前非根节点并检查交换子树
*/
private void change(int index, int lastIndex) {
int left = index * 2 + 1, right = left + 1;
// 没有子节点
if (left > lastIndex) {
return;
}
// 只有左子树
if (right > lastIndex) {
if (data[left] > data[index]) {
changeNode(index, left);
}
} else {
// 两个子节点都有
if (data[left] >= data[right] && data[left] > data[index]) {
changeNode(index, left);
change(left, lastIndex);
} else if (data[left] < data[right] && data[right] > data[index]) {
changeNode(index, right);
change(right, lastIndex);
}
}
}
/**
* 交换节点
*/
private void changeNode(int i, int j) {
int tmp = data[i];
data[i] = data[j];
data[j] = tmp;
}
private void initData(int length, int maxRange) {
data = new int[length];
IntStream.range(0, length).forEach(e ->
data[e] = new Double(Math.random() * maxRange).intValue()
);
System.out.println("初始化:");
System.out.println(new Gson().toJson(data));
}
}