堆排序(Heap Sort)
堆排序是一种高效率的排序方法,它充分的利用了二叉堆的性质,无需借助额外的辅助空间,并且拥有O(n*log(n))的时间复杂度。
首先这里我从二叉堆讲起。二叉堆是一种具有一定逻辑关系的完全二叉树(这里需要和满二叉树做一下区分),它始终满足:任意节点的值均大于(或小于)其子节点,满足该条件的二叉堆又叫大根堆(或小根堆)。
再说堆排序,它即是利用了二叉堆的该性质,在每次调整二叉堆结构后取堆顶元素,即该二叉堆内最大(或最小)的元素,取n次后得到的序列即为一个有序序列。
接下来来看一下队的存储结构:
int heap[HEAPLENGTH];
int *anotherHeap = new int[HEAPLENGTH];
由于二叉堆满足“完全二叉树”的性质,因此二叉堆可以很轻松的由一个一维数组来表示,而祖先节点和儿子节点可以由索引很方便的计算出,来看代码:
int getLeftChild(const int &index) {
if (index < 0) return (0);
return (2 * index + 1);
}
int getRightChild(const int &index) {
if (index < 0) return (0);
return (2 * index + 2);
}
需要注意的是这里计算索引的前提是使用了“0~n - 1”下标的数组,这样可以不造成内存浪费。
通过这两个方法可以快速的得到与传入参数对应的该完全二叉树的左(右)儿子,方便后续的算法操作。
接下来就看一下构建堆的过程,首先笔者总结了一个规律:在一个拥有n个节点的完全二叉树中,最后一个拥有孩子的节点在层次遍历序列中的位置为(n / 2) - 1。因此,为了节省资源,可以从该位置进行递归向下调整,直至整个二叉完全树满足堆的条件。下面给出代码:
void adjustHeap(int *arr, const int &index, const int &n) {
int child = getLeftChild(index);
int now = index;
while (child < n) {
if (getRightChild(now) < n && arr[child] /**/ < /**/ arr[getRightChild(now)]) {
child = getRightChild(now);
}
if (arr[now] /**/ < /**/ arr[child]) {
dataSwap(arr[now], arr[child]);
}
else break;
now = child;
child = getLeftChild(now);
}
}
当然,你可能注意到该段代码里的getRightChild完全可以被child + 1替代(因为右儿子肯定紧挨着左儿子),这里只是为了易读。
在调整过后,我们可以由堆的性质得知堆顶(最前面那个元素)一定是大根堆(可以通过替换代码中有注释部分的“<”为“>”来将调整过程改为小根堆调整),然后再将第(0)的元素和第(n - 1 - 调整次数)的元素交换,来保证第(n - 1 - 调整次数)的元素到第(n - 1)的元素的序列为有序序列。此时堆顶元素变化,因此可能破坏了堆的性质,则再从堆顶进行递归向下调整,最终经过n - 2次调整,即从第(n - 1 - (n - 2) = 1)的位置到第(n - 1)的位置为有序序列,第0的位置为堆,自然整个序列满足了排序的最终要求,堆排序完成。下面给出堆排序代码:
void dataSwap(int &a, int &b) {
if (&a == &b || a == b) return;
a = a + b;
b = a + b;
a = b - a;
b = b - 2 * a;
}
void heapSort(int *arr, const int &n) {
if (arr == NULL || n <= 0) {
return;
}
for (int i = (n / 2) - 1; i >= 0; i--) {
adjustHeap(arr, i, n);
}
for (int i = n - 1; i > 0; i--) {
dataSwap(arr[0], arr[i]);
adjustHeap(arr, 0, i);
}
}
有一个有趣的函数,即“dataSwap”,本质上是一个交换函数,但是不需要借助中间量。
如有不对敬请指出,感谢阅读!
原文地址:https://www.cnblogs.com/yeehok/p/15312681.html
- Python(3):文件读写与异常
- 向ASP.NET Core迁移
- Gitlab CI 自动部署 asp.net core web api 到Docker容器
- 从XMLHttpRequest请求响应里getResponseHeader(header)报错:Refused to get unsafe header "**" 问题解决
- 全面理解 ASP.NET Core 依赖注入
- jq实现上传头像并实时预览功能
- 初探领域驱动设计(2)Repository在DDD中的应用
- js取整并保留两位小数的方法
- 异步编程 In .NET
- 判断标签是否包含class的方法
- vue.js使用props在父子组件之间传参
- JS中使用正则表达式替换对象里的大小写
- JS中const、var 和let的区别
- 动态计算rem的js代码
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- linux查找大文件指定内容的实现方法
- linux服务器上安装jdk的两种方法(yum+下载包)
- ubuntu中编写shell脚本开机自动启动(推荐)
- linux下使用 CentOS7安装jdk1.7
- 安装CentOS 6.x报错"Disk sda contains BIOS RAID metadata"解决方法
- 重启宝塔面板后提示-ModuleNotFoundError: No module named 'geventwebsocket'
- Linux动态启用/禁用超线程技术的方法详解
- 解决Ubuntu下使用linuxdeployqt打包Qt程序问题
- 使用python获取基金历史数据
- 如何在CentOS8上安装和配置Postfix邮件服务器的方法示例
- Linux下设置Vim编辑器里Tab的长度行号
- Centos定制rpm包、搭建yum仓库的教程
- linux手动、自动更改网卡MAC地址的方法
- Centos7的Firewalld防火墙基础命令详解
- Linux下安装或升级Python 2.7的操作方法