【数据结构与算法基础】堆排序原理及实现
前言
数据结构,一门数据处理的艺术,精巧的结构在一个又一个算法下发挥着他们无与伦比的高效和精密之美,在为信息技术打下坚实地基的同时,也令无数开发者和探索者为之着迷。
也因如此,它作为博主大二上学期最重要的必修课出现了。由于大家对于上学期C++系列博文的支持,我打算将这门课的笔记也写作系列博文,既用于整理、消化,也用于同各位交流、展示数据结构的美。
此系列文章,将会分成两条主线,一条“数据结构基础”,一条“数据结构拓展”。“数据结构基础”主要以记录课上内容为主,“拓展”则是以课上内容为基础的更加高深的数据结构或相关应用知识。
欢迎关注博主,一起交流、学习、进步,往期的文章将会放在文末。
从这一节开始,我们将连续梳理几个排序算法。这一节的内容,先交给堆排序。
堆排序是一种相当出色的算法,他不需要大量外在的辅助空间,仅在线性表本身上进行数据操作。时间复杂度稳定在 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n)上,最好情况和最坏情况都是如此。
在前面的章节中我们介绍了二叉堆。简单来说就是二叉堆是一个完全二叉树,它满足父节点比子节点都大(或小),根节点是子树中最大(或小)元素的性质。如果根节点为子树中最大元素,则该堆被称为大根堆,否则称为小根堆。下面使用的堆默认为大根堆。
使用堆结构排序的一个优势在于,由于堆的特殊性质,顺序存放的数据元素可以直接组织称为一颗完全二叉树,并且通过调整顺序成为一个堆。因此该排序仅需要在数组内部进行,不需要额外的空间。
构建初始堆
构建初始堆是指将原来数组中的元素调换位置使其满足堆性质的过程。
例如,对于如下包含10个元素的序列:
26
,
5
,
77
,
1
,
61
,
11
,
59
,
15
,
48
,
19
26,5,77,1,61,11,59,15,48,19
26,5,77,1,61,11,59,15,48,19
对应未经过构建的完全二叉树为:
经过初始构建后的构建堆为:
要实现构建堆的过程,首先需要理解堆一种维护操作,下沉。
下沉操作
下沉操作是将某一元素递归的向下交换的过程,其简述如下:
- 如果该元素大于其子节点,该过程结束
- 如果该元素无子节点,该过程结束
- 挑选较大子节点进行交换,继续该过程。
用代码实现如下:
int a[N];
void down(int k,int n){//下沉元素下标,堆规模
int son = k << 1;
while(son <= n){
if(son + 1 <= n && a[son + 1] > a[son]){//子节点中挑选较大者
son++;
}
if(a[k] > a[son]){//如果父节点比子节点都大,则结束该过程
break;
}
//交换父子节点
int temp = a[k];
a[k] = a[son];
a[son] = temp;
//变更结点指向,重复该过程
k = son;
son <<= 1;
}
}
这个操作也是二叉堆那一节的重中之重,建议读者重点掌握。
构建堆
构建堆的过程用一句话来说就是倒序遍历数组中前 ⌊ n 2 ⌋ \lfloor\frac{n}{2}\rfloor ⌊2n⌋个元素,将它们都执行一遍下沉操作。
代码实现如下:
for(int i = n / 2;i >= 1;i--){
down(i,n);
}
hh,是不是非常简单?
完成了初始堆的构建,下面的任务就是在这个堆的基础上完成堆排序。
堆排序
堆排序的过程也十分明确,由于我们构建的是大根堆,堆顶一直处在数组的首位。所以排序过程产生的结果不能存放在堆顶,那样会破坏堆的结构。
所以排序结果的存放要从后往前进行,又由于需求是递增序列,从后往前进行就是每次从剩余元素集合中寻求最大值,这也是为什么使用大根堆。
每次操作,需要完成如下事情:
- 从堆顶获取剩余集合中的最大值,同当前未排序部分最后一个元素交换
- 堆的规模-1
- 对堆顶进行下沉操作,维护新的堆形态
不难证明的是,假设待排序序列规模为n,则上述操作仅需要进行n-1次即可对序列完成排序
对于上文中的示例,堆排序过程如下,图中虚线链接的结点不再参与堆的结构:
代码实现如下:
int temp;
for(int i = n;i > 1;i--){//循环n-1次
//交换堆顶和末尾元素
temp = a[i];
a[i] = a[1];
a[1] = temp;
//对堆顶进行下沉操作,堆规模-1
down(1,i - 1);
}
应用
学过了堆排序的原理及实现,下面就来使用它完成一个排序需求:
给定规模为n的序列,输出其升序排序的结果
输入格式:输入包括两行,第一行一个整数n表示规模。第二行n个整数,表示序列
输出格式:输出包含一行n个整数,为升序排序后的结果
代码如下:
#include<stdio.h>
int a[100050];
void down(int k,int size){
int son = k << 1;
while(son <= size){
if(son + 1 <= size && a[son + 1] > a[son]){
son++;
}
if(a[k] > a[son]){
break;
}
int temp = a[k];
a[k] = a[son];
a[son] = temp;
k = son;
son <<= 1;
}
}
int main(){
int n;
scanf("%d",&n);
for(int i = 1;i <= n;i++){
scanf("%d",&a[i]);
}
for(int i = n / 2;i >= 1;i--){
down(i,n);
}
for(int i = n;i > 1;i--){
int temp = a[1];
a[1] = a[i];
a[i] = temp;
down(1,i - 1);
}
for(int i = 1;i <= n;i++){
printf("%d ",a[i]);
}
}
测试上文示例,结果如下:
往期博客
- 【数据结构基础】数据结构基础概念
- 【数据结构基础】线性数据结构——线性表概念 及 数组的封装
- 【数据结构基础】线性数据结构——三种链表的总结及封装
- 【数据结构基础】线性数据结构——栈和队列的总结及封装(C和java)
- 【算法与数据结构基础】模式匹配问题与KMP算法
- 【数据结构与算法基础】二叉树与其遍历序列的互化 附代码实现(C和java)
- 【数据结构与算法拓展】 单调队列原理及代码实现
- 【数据结构基础】图的存储结构
- 【数据结构与算法基础】并查集原理、封装实现及例题解析(C和java)
- 【数据结构与算法拓展】二叉堆原理、实现与例题(C和java)
- 【数据结构与算法基础】哈夫曼树与哈夫曼编码(C++)
- 【数据结构与算法基础】最短路径问题
参考资料:
- 《数据结构》(刘大有,杨博等编著)
- 《算法导论》(托马斯·科尔曼等编著)
- OI WiKi
上一篇: 科技改变生活,Airdoc获亚洲设计大奖
下一篇: PHP调用百度天气接口API
推荐阅读
-
Python实现的堆排序算法原理与用法实例分析
-
Python数据结构与算法之图的基本实现及迭代器实例详解
-
【数据结构与算法Python描述】——Python列表实现原理探究及常用操作时间复杂度分析
-
【数据结构与算法基础】堆排序原理及实现
-
排序算法(三)堆排序原理与实现(小顶堆)
-
数据结构与算法1:二叉树(binary_tree)的前、中、后序(深度优先)和广度优先遍历及python代码实现
-
Python实现的堆排序算法原理与用法实例分析
-
【数据结构与算法-C语言版】顺序表(线性表的顺序存储结构)及C语言实现
-
Java基础-----与Java集合相关的数据结构&Java集合特点、底层实现、是否线程安全&集合使用选取原则&集合的常见方法及遍历方式
-
Python数据结构与算法之图的基本实现及迭代器实例详解