前言
今天无意中翻了一下以前写的堆排序文章,发现写的也太Low了 . . .所以准备重新写一篇文章来更详细的讲解堆排序,主要参考啊哈算法 和以前的一些资料,希望大家喜欢,今天也是放假的日子,但我还是选择在学校留上几天,希望大家喜欢这一篇文章 ^ _ ^ . . .
相关树及C++堆排序一些文章:
1)《算法笔记》—— 堆排序算法( C++实现)
2) 二叉搜索树 —— 查找与排序
3) 红黑树基本功能 —— C++实现
4) 二叉树(链式存储)—— C++实现
5) 二叉树(顺序存储)—— C++实现
下面我将更详细的讲解堆排序 . . .
.
文章目录:
1)删除顶元素并且添加新元素
2)增加新元素
3)创建一个堆(关键的部分,如何创建),堆排序
那么什么是堆排序呢? 让我们看看度娘的解释:
—— “堆排序(英语:Heapsort)是指利用堆这种数据结构所设计的一种 排序算法
。
堆是一个近似完全二叉树的结构,并同时满足堆积的性质:即 子结点的键值或索引总是小于(或者大于)它的父节点。”
首先,它是一种排序算法,然后它拥有的一些特性 . . . 下面让我们来了解一下这个特性是什么样子的,走你 ~
.
堆是一种特殊的完全二叉树
,就像下面这棵树一样:
这棵二叉树有这么一个特性:所有父结点都比子结点要小
。
那么这个特性有什么用呢?—— 下面我们将讲解堆的一些优秀的特性 . . .
.
一)删除顶元素并且添加新元素
我们现在需要删除堆顶部的元素,然后将一个数23放到堆顶,我们将23调整到合适的位置
。那们我们如何调整呢?当前结果为如下的图:
当然,聪明的你们肯定想到了, 向下调整!!
我们将 23 与 它的两个儿子(2,5)进行比较,与较小的一个互换位置,结果如下:
同理,我们依次重复的比较,将 23调整到最终的位置,包含它的移动路线:
我们将这个 23进行调整,竟然只进行了 3次比较,是不是非常的快速呢? 现在最小的数在堆顶(2)。
效率提升的不是一星半点,当前我们肯定需要一个东西来进行比较,比如我们有 14个数字,分别是99、5、36、7、22、17、46、12、2、19、25、28 和 92,假如我们删除最小的数后再添加一个新数,并且这样的操作进行 14次,那么它的时间复杂度将为 O(14 ^2)即 O( N ^2) . . .
假如现在我们有 1Y 个数,对它进行删除最小数并且 新增一个数的操作(进行1Y次),我们只需要 2.7秒,它的复杂度为 O(logN) . . .
结点数据向下调整的代码如下:
下面我会展示出完整的代码示例,这个图片样式的仅供参考学习 . . .
.
二)增加新元素
如果我们想直接将新元素插入到堆之中,只需要将新元素放入到末尾,再将新元素向上调整,直至满足堆的特性为止 . . . 时间复杂度为 O(logN),例如我们现在需要新增一个元素为 3,它的运动轨迹如下所示:
最终效果如下所示:
结点数据向上调整的代码如下:
.
三)创建一个堆(关键的部分,如何创建)
首先,我们将数据依次的放入到堆中,然后设计一个算法将其中的数据进行调整,以便其满足堆的特性(最小堆或者最大堆) . . .
将数据依次放入到堆中:
n = 0;
for(i = 1; i <= m; i++)
{
++n;
h[n] = a[i];
}
堆中数据进行调整:
for(i = n / 2; i >= 1; --i)
siftdown(i);
这两行代码是什么意思呢? 为什么 i 要从 n / 2 开始呢?
因为我们调整堆中的数据满足堆特性,只需要将每个根结点向下调整就行了,而最后叶子结点是没有儿子结点的,所以我们只需要从倒数第二排开始即可,如下所示:
—— 堆排序
我们用这样的思想就可以把一个堆调整好了,然后就可以使用我们的堆排序了,堆排序的时间复杂度是 O(NlogN),它与快速排序是一样的,实现堆排序我们只需要每次删除顶部元素并将顶部元素输出,然后用最后一个元素赋值给顶结点,并且将这个新的顶点进行向下调整即可 . . .
删除最大的元素代码如下所示(长度 - 1,假如是最小堆):
int deletemax()
{
int t;
t = h[1]; // 用于返回的值
h[1] = h[n]; // 最后一个元素赋值给顶结点
--n; // 长度 - 1
siftdown(1); // 向下调整(调整最小值到上面去)
return t; // 返回的数据
}
建堆以及堆排序的完整代码如下所示:
当我们输入下面的这些数据后:
14
99 5 36 7 22 17 46 12 2 19 25 28 1 92
运行结果是:
1 2 5 7 12 17 19 22 25 28 36 46 92 99
.
当然我们还有更好的办法,就是我们刚开始不是创建的最小堆,而是创建的是最大堆,最大堆创建好之后,最大的元素在 h[1],我们需要的是将数据从小到大进行排序,大的数据放到最后面。因此,我们每次将最大值放到最后面,并且长度 - 1,这样获得的最大值就不会被影响到,然后我们利用与最大值交换的那个最小值进行向下调整,重复如此直至n的值为0
. . .
代码设计如下所示:
// 堆排序
void heapsort(){
while(n > 1){
swap(1, n);
--n;
siftdown(1);
}
}
这样我们只需要在 main函数中调用这个方法之后,直接输出堆中的数据即可:
// 堆排序
heapsort();
// 输出
for(i = 1; i <= num; ++i)
printf("%d ", h[i]);
结果与上面是一样的 . . .
.
.
.