堆排序
1 基本原理
1 核心思想:在堆的数据结构中,堆中的最大值总是位于根节点(在优先队列中使用堆的话堆中的最小值位于根节点),堆排序就是把最大堆堆顶的最大数取出,将剩余的堆继续调整为最大堆,再次将堆顶的最大数取出,这个过程持续到剩余数只有一个时结束。
** 算法分析:
1. 最大堆调整(Max-Heapify):将堆的末端子节点作调整,使得子节点永远小于父节点
2. 创建最大堆(Build-Max-Heap):将堆所有数据重新排序,使其成为最大堆
3. 堆排序(Heap-Sort):移除位在第一个数据的根节点,并做最大堆调整的递归运算 **
相关概念:
1. 二叉树:二叉树是每个节点最多有两个子树的树结构。通常子树被称作“左子树”(left subtree)和“右子树”(right subtree)。二叉树常被用于实现二叉查找树和二叉堆。
二叉树的每个结点至多只有二棵子树(不存在度大于 2 的结点),二叉树的子树有左右之分,次序不能颠倒。二叉树的第 i 层至多有 2i - 1 个结点;深度为 k 的二叉树至多有 2k - 1 个结点;对任何一棵二叉树 T,如果其终端结点数为 n0,度为 2 的结点数为 n2,则n0 = n2 + 1。
—————————————————————————————————————
2. 二叉树又分为完全二叉树(complete binary tree)和满二叉树(full binary tree)
(1) 满二叉树:一棵深度为 k,且有 2k - 1 个节点称之为满二叉树
(2) 完全二叉树:深度为 k,有 n 个节点的二叉树,当且仅当其每一个节点都与深度为 k 的满二叉树中序号为 1 至 n 的节点对应时,称之为完全二叉树
—————————————————————————————————————-
3. 堆: 堆(二叉堆)可以视为一棵完全的二叉树,完全二叉树的一个“优秀”的性质是,除了最底层之外,每一层都是满的,这使得堆可以利用数组来表示(普通的一般的二叉树通常用链表作为基本容器表示),每一个结点对应数组中的一个元素。
2 实例说明
如上图所示,以一组数据a {4,1,3,2,16,9,10,14,8,7} 为例,进行堆排序的算法演示:
- 构建最大堆:将数组转化为二叉树形式,依次从(n/2 - 1)节点递减,判断是否符合最大堆性质。
(1)16>7,符合;
(2)2<14,不符合,且14>8,则交换14为父节点。
(3)3<10,且10>9,交换10至父节点。
(4)1<16,16>14,交换16至父节点。
(5)4<16,16>10,交换16至父节点。最大堆构造完成。
2.堆排序:
(1)将最大堆顶部元素16与尾部元素1交换,将16去除,再重新构建最大堆,如图(b)。
(2)重复操作,去除顶部元素14,再构建最大堆,如图(c)。
(3)直至堆中只剩最后一元素,则堆排序完成。
3 代码实现
// 堆排序(C++)
void HeapAdjust(vector<int> &a, int i, int len) //构建最大堆
{
int left, right, j;
while ((left = 2 * i + 1) <= len) //判断当前父节点有无左节点(有无孩子节点)
{
right = left + 1; //右节点
j = left;
if (j<len && a[left]<a[right])
{
j++;
}
if (a[i]<a[j])
{
swap(a[i], a[j]);
}
else
{
break; //父节点比孩子节点都大
}
i = j; //交换位置后,对子节点继续进行判断
}
}
void HeapSort(vector<int> &a)
{
int len = a.size() - 1;
for (int i = len / 2 - 1; i >= 0; i--) //构造堆
{
HeapAdjust(a, i, len);
//中间排序过程输出
for (auto x : a)
cout << x << " ";
cout << endl;
}
cout<<"堆构造完成!"<< endl;
//堆排序
while (len >= 0)
{
swap(a[0], a[len--]); //首尾元素交换,长度减一,尾部元素最大
HeapAdjust(a, 0, len);
//中间排序过程输出
for (auto x : a)
cout << x << " ";
cout << endl;
}
}
4 性能分析
1 时间复杂度:
堆排序的时间等于建堆和进行堆调整的时间。构建最大堆大概需进行n/2 * 2 = n次比较和n/2次交换,故时间复杂度为O(n)。而堆调整的时间为(n - 1)*O(log2(n))。所以堆排序的时间复杂度为O(n*logn)。2 空间复杂度:
堆排序为就地排序,因此空间复杂度为O(1)。- 3 算法稳定性:
堆排序是不稳定的算法,在构建堆过程中元素的顺序可能会发生变化。
本文主要参考文章 常见排序算法 - 堆排序 (Heap Sort)