排序算法的c++实现——堆排序
我们利用最大堆可以实现数组从小到大的原址排序,利用最小堆的可以实现对数组从大到小的原址排序。
1 二叉堆的简单介绍:
最大堆与最小堆可以当作通过数组来实现的一个完全二叉树,除了最底层之外其它层都是满的,并且最底层也是从左到右填充的。在最大堆中,父结点的值大于或等于子结点的值;在最小堆中,父结点的值小于或等于子结点的值。
当堆的元素在数组中下标从1开始时,很容易计算出父结点/左子结点/右子结点的下标:当父结点的下标为 i 时,左孩子下标为2i, 右孩子的下标为2i+1;当孩子结点为j时,父结点的下标为 j/2。
但是呢,数组的下标都是从0开始的,所以呢,关于父结点与子结点的关系要稍微绕一下:当父结点的下标为 i 时,左孩子下标为2i+1, 右孩子的下标为2(i+1);当孩子结点为j时,父结点的下标为 (j-1)/2。
2 使用最大堆或最小堆实现排序
以使用最大堆进行从小到大的排序为例,假设最大堆使用长度为N的数组表示,数组下标为0(对应堆根结点)的值最大。因此呢,我们可以把数组下标为0的值与数组下标为N-1的值进行交换,并把堆的大小由N减小为N-1并维护最大堆的性质;接下来不断重复以上过程,直到堆的大小变为1为止。
说明几点:
1 堆排序为为原址排序,它不需要额外的内存空间;
2 通常使用最大堆实现从小到大的排序,使用最小堆来实现从大到小的排序;
3 堆排序不是稳定的排序算法;
4 堆排序的时间复杂度为O(NlogN);
代码如下:
/***********************************************************************
* Copyright (C) 2019 Yinheyi. <chinayinheyi@163.com>
*
* This program is free software; you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version. * Brief:
* Author: yinheyi
* Email: chinayinheyi@163.com
* Version: 1.0
* Created Time: 2019年05月07日 星期二 21时41分02秒
* Modifed Time: 2019年05月09日 星期四 22时12分52秒
* Blog: http://www.cnblogs.com/yinheyi
* Github: https://github.com/yinheyi
*
***********************************************************************/ // 堆使用一个数组表示, 它可以当作一个完全二叉树,除了最底层之外,其它层都是满的,
// 并且最底层也是从左到右填充的。
//
// 当堆的下标(数组的下标)从1开始时比较好计算。因为:
// 1. 当父结点为i时, 左孩子为2i, 右孩子为2i+1;
// 2. 当孩子结点的下标为j时,父结点的下标为j/2 (根结点为父结点);
// 3. 一个结点的下标为k时, 则它所有的深度为 floor(logK);
// 4. 当一个堆共n个元素时,它的高度为floor(logN).
//
// 1
// / \
// 2 3
// / \ / \
// 4 5 6 7
// / \ / \ / \ / \
// 8 9 10 11 12 13 14 15
//
//
// 但是呢,数组的下标都是从0开始的,所以呢,我们下代码时,还是需要从0开始,而此时:
// 1. 当父结点的下标为i时,左孩子为2i+1, 右孩子为2*(i+1)
// 2. 当孩子结点的下标为j时,父结点的下标为(j-1)/2.
//
// 0
// / \
// 1 2
// / \ / \
// 3 4 5 6
// / \ / \ / \ / \
// 7 8 9 10 11 12 13 14
//
//
//
/************************** 代码如下 ****************************/ // 定义三个宏,分别用于求左孩子/右孩子/父结点的下标。
#define LEFT(i) (((i) << 1) + 1)
#define RIGHT(i) (((i) + 1) << 1)
#define PARENT(i) (((i) - 1) >> 1) // 小于比较函数
bool less(int lhs, int rhs)
{
return lhs < rhs;
} // 大于比较函数
bool greate(int lhs, int rhs)
{
return lhs > rhs;
}
typedef bool (*Comp)(int, int); // 交换两个元素的值
static inline void swap(int& lhs, int & rhs)
{
int _nTemp = lhs;
lhs = rhs;
rhs = _nTemp;
} // 假设一个节点的左子树与右子树都满足堆的性质,而该节点不满足最大堆或最小堆的性质,该
// 函数实现调整节点位置,维护堆的性质。
// 输入参数分别表示: 堆的数组/数组长度/节点i的下标/表示比较的二元谓词
// 复杂度为O(logN).
void Heapify(int array[], int nLength_, int nIndex_, Comp CompFunc)
{
if (array == nullptr || nIndex_ >= nLength_ || nIndex_ < || CompFunc == nullptr)
return; int _nLeft = LEFT(nIndex_);
int _nRight = RIGHT(nIndex_); // 初始化最大值节点的下标;
int _nLargest = nIndex_;
if ( _nLeft < nLength_ && !CompFunc(array[_nLargest], array[_nLeft]))
{
_nLargest = _nLeft;
}
if (_nRight < nLength_ && !CompFunc(array[_nLargest], array[_nRight]))
{
_nLargest = _nRight;
} /* 此时不需要维护堆的性质,直接返回 */
if (_nLargest == nIndex_)
{
return;
} swap(array[nIndex_], array[_nLargest]);
Heapify(array, nLength_, _nLargest, CompFunc);
} // 使用一个数组建立一个最小堆或最大堆。
// 输入参数为:一维数组/数组的长度/表示比较的二元谓词,用于控制建最小堆还是最大堆
// 复杂度为O(N).
void BulidHeap(int array[], int nLength_, Comp CompFunc)
{
if (array == nullptr || nLength_ <= || CompFunc == nullptr)
return; // 从最后一个元素的父节点开始调用Heapify()函数来建堆。
for (int i = PARENT(nLength_ - ); i >=; --i)
{
Heapify(array, nLength_, i, CompFunc);
}
} // 堆排序的函数
// 说明:1. 通过建立[最大堆]可以实现[从小到大]的排序;
// 2. 通过建立[最小堆]可以实现[从大到小]的排序
// 3. 堆排序为原址排序,它不需要借助额外的空间.(归并排序不是原址排序)
// 4. 堆排序的复杂度为O(NlogN).
//
// 堆排序的思想 (以从小到大排序说明):
// 第一步:建立一个最大堆;
// 第二步:把首元素与最后一个元素进行交换;
// 第三步:把堆的大小减1,对新的堆进行维护维的性质;
// 第四步:把首元素与倒数第二个元素进行交换;
// 第五步:把堆的大小减1,对新的堆进行维护维的性质;
// .......
//
void HeapSort(int array[], int nLength_, Comp CompFunc)
{
if (array == nullptr || nLength_ <= || CompFunc == nullptr)
return; BulidHeap(array, nLength_, CompFunc);
for (int i = nLength_; i >= ; /* 循环内 */) // i表示当前堆的大小
{
swap(array[], array[--i]);
Heapify(array, i, , CompFunc);
}
} /************ 测试 *****************/
#include <iostream> // 打印数组函数
void PrintArray(int array[], int nLength_)
{
if (nullptr == array || nLength_ <= )
return; for (int i = ; i < nLength_; ++i)
{
std::cout << array[i] << " ";
} std::cout << std::endl;
} // 主函数
int main(int argc, char* argv[])
{
int array[] = { , , , -, , , , , -, -}; PrintArray(array, );
HeapSort(array, , greate);
PrintArray(array, ); return ;
}
排序算法的c++实现——堆排序的更多相关文章
- 排序算法c语言描述---堆排序
排序算法系列学习,主要描述冒泡排序,选择排序,直接插入排序,希尔排序,堆排序,归并排序,快速排序等排序进行分析. 文章规划: 一.通过自己对排序算法本身的理解,对每个方法写个小测试程序.具体思路分析不 ...
- 排序算法(三)堆排序及有界堆排序Java实现及分析
1.堆排序基数排序适用于大小有界的东西,除了他之外,还有一种你可能遇到的其它专用排序算法:有界堆排序.如果你在处理非常大的数据集,你想要得到前 10 个或者前k个元素,其中k远小于n,它是很有用的. ...
- 排序算法<No.5>【堆排序】
算法,是系统软件开发,甚至是搞软件的技术人士的核心竞争力,这一点,我坚信不疑.践行算法实践,已经有一段时间没有practise了,今天来一个相对麻烦点的,堆排序. 1. 什么是堆(Heap) 这里说的 ...
- 排序算法Java实现(堆排序)
算法描述:对于给定的n个记录,初始时把这些记录看作一棵顺序存储的二叉树,然后将其调整为一个大顶堆,然后将堆的最后一个元素与堆顶元素进行交换后,堆的最后一个元素即为最大记录:接着将前(n-1)个元素重新 ...
- 排序算法C语言实现——堆排序
/*堆排nlog(n)*//*堆排复杂度分析1.建堆((n*log(n))/2) 循环n/2次,每次调用HeapAdjust函数 HeapAdjust内部循环log(n)2.调整堆(((n ...
- java排序算法(三):堆排序
java排序算法(三)堆排序 堆积排序(HeapSort)是指利用堆积树这种结构所设计的排序算法,可以利用数组的特点快速定位指定索引的元素.堆排序是不稳定的排序方法.辅助空间为O(1).最坏时间复杂度 ...
- Java各种排序算法详解
排序大的分类可以分为两种:内排序和外排序.在排序过程中,全部记录存放在内存,则称为内排序,如果排序过程中需要使用外存,则称为外排序.下面讲的排序都是属于内排序. 内排序有可以分为以下几类: (1).插 ...
- 各种排序算法的分析及java实现
排序一直以来都是让我很头疼的事,以前上<数据结构>打酱油去了,整个学期下来才勉强能写出个冒泡排序.由于下半年要准备工作了,也知道排序算法的重要性(据说是面试必问的知识点),所以又花了点时间 ...
- (转)各种排序算法的分析及java实现
转自:http://www.cnblogs.com/liuling/p/2013-7-24-01.html 排序一直以来都是让我很头疼的事,以前上<数据结构>打酱油去了,整个学期下来才勉强 ...
随机推荐
- Q1094
一,看题 1,字符串确实是我的弱项. 2, 二,看题解 #include<iostream> #include<string> using namespace std; int ...
- es4x 使用nodejs 开发vertx 应用框架试用
es4x 是将vertx 的特性带到nodejs 的开发中,性能很不错,同时开发方式和nodejs 一样,可以加速vertx 应用的开发,同时也可以方便的集成java 软件包,提供的cli 工具也很方 ...
- VueCli3新特性
升级VueCli3的理由: 1.构建速度大大加快,之前看到一个升级的例子是2的3倍速度,具体可以在自己迁移一个项 目测试下,这里的优化有默认开启了多核构建.缓存 并行和缓存 2.webpack被内置到 ...
- Vue自动化注册全局组件脚本
今天有一个idea,vue一些组件,可能会全局都用到,我觉得在main.js写 Vue.component(name, instance) 然后很命令式,写着也不好看,想着能够有一个函数可以指定加载比 ...
- typora的使用技巧
目录 Typora 的 markdown 语法 标题: 插入图片: 链接: 字体变化: 删除: 文字高亮: 角标: 文本方位: :-:| :- | -: 制作表格: 常用快捷键(补充): 下划线: T ...
- requests.session()会话保持
可能大家对session已经比较熟悉了,也大概了解了session的机制和原理,但是我们在做爬虫时如何会运用到session呢,就是接下来要讲到的会话保持. 首先说一下,为什么要进行会话保持的操作? ...
- Android 10 终于来了!增加了不少新特性
前言 Android 10 正式发布了,根据官网的介绍,聚焦于隐私可控.手机自定义与使用效率,此版本主要带来了十大新特性: image 智能回复 使用机器学习来预测你在回复信息时可能会说些什么,这 ...
- Android Q Beta 6 终极测试版发布!
前言 当今手机市场可谓是百花齐放,但手机系统却屈指可数,其中Android和iOS就占据了整个手机系统市场的99%,单单Android就占据了整个手机系统市场的86%,可谓是占据绝对优势. 其 ...
- concurrent(三)互斥锁ReentrantLock & 源码分析
参考文档:Java多线程系列--“JUC锁”02之 互斥锁ReentrantLock:http://www.cnblogs.com/skywang12345/p/3496101.html Reentr ...
- libevent笔记4:Filter_bufferevent过滤器
Filter_bufferevent是一种基于bufferevent的过滤器,其本身也是一个bufferevent.能够对底层bufferevent输入缓存区中的数据进行操作(加/解密等)后再读取,同 ...