堆排序
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
算法与数据结构程设计报告
一.设计题目:
堆排序的算法
二.运行环境:
1、硬件:计算机
2、软件:Microsoft Visual C++6.0
三.目的和意义:
目的:创建一个大堆,按从大到小顺序输出堆元素,实现堆排序。
意义:利用堆排序,即使在最坏情况下的时间复杂度也是O(nlog2n),相对于快速排序来说,时间复杂度小,这是堆排序的最大优点,可用于对若干元素进行排序,加快排序速度。
四.算法设计的基本思想:
堆排序算法设计基本思想:
堆排序利用了大根堆堆顶记录的关键字最大这一特征,使得在当前无序区中选取最大关键字的记录变得简单。先将初始文件R[1..n]建成一个大根堆,此堆为初始的无序区。再将关键字最大的记录R[1](即堆顶)和无序区的最后一个记录r[n]交换,由此得到新的无序区r[1..n-1]和有序区r[n],且满足r[1..n-1].keys≤r[n].key。由于交换后新的根R[1]可能违反堆性质,故应将当前无序区r[1..n-1]调整为堆。然后再次将r[1..n-1]中关键字最大的记录r[1]和该区间的最后一个记录R[n-1]交换,由此得到新的无序区r[1..n-2]和有序区r[n-1..n],且仍满足关系r[1..n-2].keys≤r[n-1..n].keys,同样要将r[1..n-2]调整为堆。直到无序区只有一个元素为止.
. .
流程图3:打印数组函数print()
六.算法设计分析:
性能分析:堆排序的时间主要由建立初始堆和不断重复建堆这两部分的时间开销构成,它们均是通过调用Heapify实现的。其中:建立初始堆时,总共需进行的关键字比较次数≤ 4n =O(n) ;排序过程中进行n-1次重建堆所进行的总比较次数不超过下式:2*(└ log2(n-1) ┘+└ log2(n-2) ┘+…+ └ log22 ┘) < 2n └ log2n ┘=O(n log2n)因此堆排序总的时间复杂度是 O(n+ n log2n)= O(n log2n)。堆排序在最坏情况下的时间复杂度也是O(nlog2n),相对于快速排序来说,这是堆排序的最大优点。另外,堆排序仅需一个记录大小供交换用的辅助空间。由于建初始堆所需的比较次数较多,所以堆排序不适宜于记录较少的文件,但对n较大的文件还是很有效的。堆排序是就地排序,辅助空间为O(1),它是不稳定的排序方法。
堆的描述:堆排序(HeapSort)是一树形选择排序。堆是对基于数组的树的重要应用场合之一。它是节点间具有层次次序关系的完全二叉树。其中,父节点值大于或等于其孩子节点值的,叫“最大堆(maximum heap)”;父节点值小于或等于孩子节点值的,叫“最小堆(minimum heap)”.在最大堆中,根中的元素值最大;在最小堆中,根中的元素值最小。堆排序的特点是:在排序过程中,将R[l..n]看成是一棵完全二叉树的顺序存储结构,利用完全二叉树中双亲结点和孩子结点之间的内在关系,在当前无序区中选择关键字最大(或最小)的记录。
从算法描述来看,堆排序需要两个过程,一是建立堆,二是堆顶与堆的最后一个元素交换位置。所以堆排序有两个函数组成。一是建堆的渗透函数,二是反复调用渗透函数实现排序的函数。
堆的存储特点:在这里,讨论作为顺序表存储的堆。它是按某种次序将一系列数据以完全二叉树形式存放的一种表。它要求堆中的节点的值都大于或等于其孩子节点的值。
按照这种存储方式,可知第i个元素的左右儿子分别是第2i和第2i+1个元素,而它的父亲节点是第i/2个元素。由于父亲节点和儿子节点具有这样的顺序关系,所以可以方便地由父亲节点找到儿子节点,反之亦然。
可见,这种存储方式大大节省了存储空间,高效快速。
堆的相关操作:作为抽象表结构,堆允许增加和删除表项。插入过程不用假定新表项占有一个特定的位置而只需维持堆顺序。但是删除操作总是删去表中的最大项(根)。堆可以用于那些客户程序想直接访问最大(小)元素的场合。作为表,堆并不提供Find操作,而对表的直接访问是只读的。所有的堆处理算法都有责任更新树和维持堆顺序。
1.建堆:数组具有对应的树表示形式。一般情况下,树并不满足堆的条件。通过重新排列元素,可以建立一棵“堆化”的树。
2.插入一个元素:新元素被加入到表中,随后树被更新以恢复堆次序。例如,下面的步骤将15加入到表中。
3.删除一个元素:删除总是发生在根节点处。用表中的最后一个元素来填补空缺位置,结果树被更新以恢复堆条件。
在堆实现时我们是采用数组来存储堆的完全二叉树表示,并且用一种有效的算法来保证对堆的所有操作不破坏堆的性质。这种表示的主要问题在于数组的大小需要事先确定,这使得对堆的大小有了一个初始的限定。在堆中数据增长到
超过这个界限时虽然可以通过复制的方法建立更大的向量来存放堆,但整个向量的复制是不可避免的,这大大降低了操作效率。为避免这个问题,可把二叉树的顺序存储改为二叉树的链表存储 .
根据算法复杂性分析的结果。Williams & Floyd在1964年提出的堆排序算法在最坏情况的时间复杂性为2 n log n + O(n)。但在判定树的模型下,为n 个数排序的算法时间复杂性的下界为n log n + O(n)。可见,Williams & Floyd 的算法或许可以改进。其中在进入实质性排序的第i步,在把第i大元素(在堆顶)与当时处在第i大的目标位置的元素对调之后,总是让堆顶元素往下沉,可能直到叶子才完成局部(重新)堆化。如果当时处在第i大的目标位置元素很小,则下沉过程中要做很多次的比较。如果能把这个次数降下来,或许能对Williams & Floyd的算法作出效率上的显著改进。顾训穰,诸宇章就是这样循着发现问题、提出问题、分析问题和解决问题的线索,通过让空缺结点一下下沉h/2达到改进的目的,于1994年在《软件学报》上发表他们的成果。改进后的堆排序算法在最坏情况下的时间复杂性为n log n + n log log n + O(n)主项系数由2降为 1,与下界的主项系数同。
进一步,王晓东、傅清祥等还是沿着发现问题、提出问题、分析问题和解决问题的思路,发现顾训穰、诸宇章的算法可以通过让空缺结点下沉f(h)=h-logh (不是h/2) 改进其时间复杂性的次项,于1996年在《软件学报》上发表他们的成果。进一步改进后的堆排序算法在最最坏情况下的时间复杂性为n log n + n α3(n) + O(n)其中,函数x=α3(y) 是3阶Ackerman函数y=A (x,3)的逆函数。众多的反映表明以上的设计是可取的。
七.源代码:
程序源代码如下:
/*
Name: heapsort2.c
Author: huangxing
Description: 堆排序算法的过程演示
Date: 30/11/2005
Copyright:
*/
#include
#define N 6
int k,j;
/* 建堆函数 */
void build(int *a,int i,int n){
int tmp;
k=i;
j=2*k+1;
while(j<=n){
if(j