堆排序
来源:互联网 发布:javascript跑马灯效果 编辑:程序博客网 时间:2024/05/22 08:02
堆排序是一种时间复杂度为
堆的相关知识
1. 堆的定义
以下堆的定义摘自《数据结构-C语言描述》(第三版)西安电子科技大学出版社。
一个大小为n的堆(heap)是一颗包含n个节点的完全二叉树,该树中每个节点的关键字值大于等于双亲节点的关键字值。完全二叉树的根称为堆顶,它的关键字值是最小的,这样定义的堆称为最小堆(MinHeap)。我们可以用类似的方式定义最大堆(MaxHeap)。
由于堆是一个完全二叉树,因此,当它采用顺序方式存储时,存在以下关系:(最小堆)
下图是一个堆的最小堆和最大堆以及顺序存储的例子。
图1 堆的例子
(a)最小堆; (b)最大堆; (c)最小堆与最大堆的顺序存储
2. 堆的建立
1. 堆的存储结构
由于我们使用的事顺序方式存储,因此在建堆之间必须分配好它的空间大小(MaxSize),即这个堆最多需要存储多少元素。
由图1我们可以看出,我们的堆是从下标为1开始存储的,因此,我们在规划存储空间的时候需要按最多元素加1来规划。
#define MaxSize 100 //根据需要修改
接下来考虑堆的结构类型。一个堆的结构里面必然需要包含需要建堆的元素,我们用顺序方式存储,因此需要包含一个元素的数组,然后还需要有一个值来统计该堆中有几个元素,所以给出如下的结构类型:
typedef struct minheap{ int Size; T Elements[MaxSize]; }MinHeap;
需要注意的是,建堆的元素一定要是能够比较大小的类型,否则堆将没办法建立。
2. 向下调整运算
接下来我们来了解一下堆建立的过程——向下调整运算。现在以最小堆来说明一下向下调整运算。
若有一组数(a[i+1],a[i+2],…,a[n]),满足最小堆的关系a[j]≤a[2j]且a[j]≤a[2j+1](若a[2j]或a[2j+1]不存在,则说明这个数是二叉树的最后一层),添加一个数a[i]后,通过位置调整让这组数(a[i],a[i+1],a[i+2],…,a[n])任然满足最小堆的关系,调整方法是使用向下调整法。
具体的调整过程如下:对于新插入的数temp,情况1:若其不大于它的左右两个孩子(即啊a[i]=temp,a[2i]≥a[i],a[2i+1]≥a[i]),则这个数直接插入在当前位置,调整结束;情况2:若temp大于它的左孩子或者右孩子,则将这个数与其左右孩子中较小的那个交换位置,继续向下比较,直到满足情况1或者到达堆底为止。下图是一个向下调整法的例子。
图2 向下调整运算举例
如图2(a)所示,其中,temp=46是插入的元素,比较它的左右孩子,发现都比它小,所以跟其较小的孩子(18)交换,得到图2(b);交换完成后,再次比较temp与它的左右孩子,然后交换结果,得到如图2(c)结果。此时到达堆底,调整结束。
3. 建堆运算
从2.2中我们可以看出,对于一个满足最小堆关系的元素序列,在其前端插入一个数后,通过向下调整运算,可以得到一个新的满足最小堆关系的元素序列。因此,对于一个任意次序排列的元素序列,理论上我们从最后一个数开始,依次递减1对其进行向下调整运算,即可得到我们需要构建的一个最小堆(最大堆同理)。
但我们看到,对于有n个元素(含第0位空位)的序列,堆底必然有[n/2]个元素,这些元素进行向下调整运算时将直接结束,因此,我们不需要对其进行该运算。所以我们的建堆运算应该从第[n/2-1]位开始,依次递减1,直到下标为1的元素调整完成后,建堆结束。下面的表格表示的是一个通过向下调整运算建堆的过程。
表1 向下调整运算建堆过程
表中黄色表示当前插入的元素temp,灰色的两个是表示通过向下调整运算后交换的两个元素。
3. 向下调整运算和建堆运算的代码
向下调整运算中,需要传递的参数有需要运算的数组h[],待排元素的下标号i以及序列的长度n。具体实现如下:
void AdjustDown(T h[],int i,int n){ int child=2*i;//取得待排序元素的左孩子下标 T temp=h[i];//得到待排序元素值 while(child<=n){//当到达堆底时退出 if(child<n&&h[child]>h[child+1])child++;//得到左右孩子中较小孩子的下标 if(temp<h[child])break;//当插入元素小于左右孩子时,直接退出,调整结束 h[i]=h[child];//交换h[i]和其较小的孩子,由于h[i]保存在temp中,所以可以直接赋值 i=child;//迭代,为下一次调整做好准备 child*=2; } h[i]=temp;//填入temp,调整结束}
建堆运算中需要传递的参数是待建堆的序列h,具体代码如下:
void CreatHeap(MinHeap* h){ int n=h->Size;//得到序列长度,该长度包含下标为0的空位 for(int i=n/2-1;i>0;i--){//从i=n/2开始进行向下调整运算,直到i=1为止 AdjustDown(h->Elements,i,n); }}
通过以上几个步骤即可完成最小堆的建立。对于最大堆的建立,只需要修改向下调整运算中的比较关系即可得到。下图是在Visual Studio 2010中运行得到的上面表格例子的结果。
图3 最小堆建立例子结果图
堆排序
有了上述的知识后,实现堆排序是一个非常简单的过程,具体思路如下:
(1)使用n个元素,建立一个最小堆(或者是最大堆);
(2)该最小堆(或最大堆)的堆顶元素(h[ 1 ])是这个序列的最小(或最大)元素;
(3)将堆顶元素(h[ 1 ])与该序列最后一个元素(h[n])交换,然后将交换后的堆顶元素与前n-1的元素进行向下调整运算,进行重排,得到一个新的最小(或最大堆);
(4)重复第(3)步,直至最后一个元素的下标为1为止,堆排序即可完成。
具体实现代码如下:
void HeapSort(MinHeap* h){ int n=h->Size; T temp; CreatHeap(h);//建堆 for(int i=n-1;i>0;i--){ temp=h->Elements[i]; //首位交换 h->Elements[i]=h->Elements[1]; h->Elements[1]=temp; AdjustDown(h->Elements,1,i-1);//剩余元素重新建堆 }}
图3的例子数据通过堆排序运行结果如下图所示。
图4 堆排序运行结果图
从结果我们可以看出,排序结果是降序排列的,如果要让结果升序排列有两种方法:第一种方法是在上述基础上将数组直接进行逆序;第二种方法是建立最大堆,然后进行堆排序。(建立最大堆只需要修改向下调整运算代码即可。)
堆排序分析
从之前的内容我们知道,堆排序是利用最小堆(或最大堆)堆顶元素的关键字最小(或最大)的特征,来简单地从无序的序列中得到最小(或最大)的元素。
现在对堆排序的时间复杂度和空间复杂度进行分析。
1. 时间复杂度与空间复杂度
堆排序的过程包括堆的建立以及反复的向下调整运算(堆的重建)两个步骤,因此,时间也主要耗费在这两个步骤上。
设树高为
堆排序在最坏情况下,时间复杂度为
在空间复杂度方面,整个过程只用了一个中间变量进行临时存储,因此,其空间复杂度为
2. 堆排序的特点
堆排序算法具有以下几个特点:
(1)堆排序是一种不稳定的排序方法;
(2)只能用于顺序结构,不能用于链式结构;
(3)堆排序适合用于记录较多的排序。由于初始建堆所需的比较次数较多,因此记录数较少时不宜采用。堆排序在最坏情况下,时间复杂度为
- 堆及堆排序
- 堆/堆排序特点
- 【二叉堆、堆排序】
- 二叉堆 & 堆排序
- 二叉堆 & 堆排序
- 堆与堆排序
- 堆与堆排序
- 堆与堆排序
- 堆与堆排序
- 堆与堆排序
- 堆与堆排序
- 堆和堆排序
- 堆排序(最大堆)
- 堆和堆排序
- 堆和堆排序
- 堆及堆排序
- 堆和堆排序
- 堆与堆排序
- 统计你的手机号码中出现次数最多的数字,并打印出此数字及其出现次数
- Java TreeSet练习
- SPOJ15709 Two Circles
- 工厂模式
- 归并排序 & 数组中的逆序对 【java实现】
- 堆排序
- java中的基本类型
- 算法导论 思考题 2-2
- 视频直播,手机直播,视频秀的app简介
- HTML5-WebSocket简单易懂的介绍
- JavaScript数组去重
- 不久前被问到的一个问——GDB为什么能够修改一个进程中的数据
- Android使用本地缓存解析远程服务器JSON数据
- 基于泰勒展开式的高精三角函数实现,方法三