堆排序需要借助于一种数据结构「堆」,注意下文说的都是 「大根堆」。排序的过程中需要不断进行重组堆(heapify 阶段)。关于堆这种数据结构在上一篇文章已经讲过了。堆需要满足 2 个条件:
「a」、是一颗完全二叉树(完全二叉树是由满二叉树衍生出来的,满二叉树是指除最后一层无任何子节点外其它节点都有2个子节点,当二叉树的每个节点的编号都与其对应的满二叉树节点的编号对应,则这棵树为完全二叉树);
「 b 」、父节点的值大于等于子节点。
从上面的两个特点可以推出:堆的第一个元素最大。堆排序正是利用了这个特点来对数据进行排序,整个排序过程分为 2 个阶段:
1、根节点与最后一个节点交换位置;
2、对根节点进行重组堆(heapify);
整个排序过程中每次可以获得一个最大的元素放到最后,这样下来就可以得到一个有序序列。
理解堆排序需要掌握一个重要的特点,堆可以用数组表示,数组的索引正是堆的下标。「一图胜前言,看图吧」。
堆排序
以数组 arr[] = { 1, 2, 3, 11, 13, 12, 9 , 8, 10, 15, 14, 7 } 为例,进行堆排序之前需要是一个堆,所以第一步需要把数组转换成一个堆。
把二叉树转换成堆,对二叉树除最后一层外的节点从后往前进行heapify,分别为:12、13、11、3、2、1
对节点进行heapify的时候需要对其所有的子树再进行一次heapify,以第2个节点 3 为例:
转换后的堆
开始进行堆排序,最后一个节点与根节点交换位置,对根节点进行 heapify
最终的排序好的二叉树
把最终二叉树转换成数组:{1,3,3,7,,8,9,10,11,12,13,14,15}
代码
/**
堆排序的思想就是堆的根肯定是最大的
1.把最大的与最后一个元素交换
2.除最后一个元素外,对根节点进行一次堆重组(heapify)
3.重复1和2
*/
void heap_sort(int tree[], int n) {
// 先把一个数组组成一个堆
bulid_heap(tree, n);
// 从最后一个节点开始
for (int i = n - 1; i >= 0; i--) {
swap(tree, i, 0);
heapify(tree, i, 0);
}
}
// 根据数组创建一个堆
void bulid_heap(int tree[], int n) {
int last_node = n - 1;
// 节点i的父节点的索引i= (i - 1) / 2
int parent = (last_node - 1) / 2;
// 从最后一个父节点进行堆重组
for (int i = parent; i >= 0; i--) {
heapify(tree, n, i);
}
}
// 交换两个元素的值
void swap(int tree[], int max, int i) {
int temp = tree[i];
tree[i] = tree[max];
tree[max] = temp;
}
// 对第i个节点进行堆重组,n为数组tree元素的个数
void heapify(int tree[], int n, int i) {
// 递归出口
if (i >= n) {
return;
}
// 左孩子节点索引
int c1 = 2 * i + 1;
// 右孩子节点索引
int c2 = 2 * i + 2;
// 最大节点索引
int max = i;
if (c1 < n && tree[c1] > tree[max]) {
max = c1;
}
if (c2 < n && tree[c2] > tree[max]) {
max = c2;
}
// 说明不是大根堆,需要对其子树进行一次堆重组
if (max != i) {
// 交换 i 与 max 对应的值
swap(tree, max, i);
// 继续对子树进行堆重组
heapify(tree, n, max);
}
}
int arr[] = {1, 2, 3, 11, 13, 12, 9 , 8, 10, 15, 14, 7};
int n = sizeof(arr) / sizeof(int);
heap_sort(arr, n);
特点
稳定性:在堆不断重组的过程中,相同元素的相对位置可能会发生变化,故不稳定。
空间复杂度:在原序列堆元素进行操作,故为 O ( 1 );
时间复杂度:最好最坏都为 O(nlogn);
感想
堆排序利用了堆数据结构,堆本身是一棵二叉树,根据一个节点可以计算出它的父节点,左子节点和右子节点的下标。父节点=(i-1)/2,左子节点=2*i + 1,右子节点=2*i + 2。「 i表示第几个节点 」。整个思想就是不断进行堆重组,交换根节点与最后节点的位置,再对除最后一个节点外的其它元素进重组、交换。
推荐阅读:
探索技术之外的生活