效果图
1. 题目
采用顺序存储结构来实现抽象数据类型 堆
数据对象:D={ ai | ai∈RcdType, i=1,2,...,n, n≥0 }
数据关系:其所有非叶子结点均不大于(或不小于)其左右孩子结点。即按完全二叉树的结点编号排列,n个结点的关键字序列(k1,k2,…,kn)称为堆,当且仅当满足以下关系:
小顶堆:ki<=k2i,ki<=k2i+1
大顶堆:ki>=k2i,ki>=2i+1 (i=1,2,…,n/2(向下取整))
基本操作:
(1)InitHeap(Heap &H,int size,int tag)
初始条件:堆H已声明,tag已赋值为1或0,size由操作者赋好值
操作结果:初始化一个最大容量为size的空堆H,当tag为0或1时分别表示小顶堆和大顶堆,成功初始化就返回OK。
(2)MakeHeap(Heap &H,RcdType *E,int n,int size,int tag)
初始条件:堆H已声明,E[1..n]是堆的n个结点,0号单元闲置,n为堆的长度,tag为0或1。
操作结果:用E建长度为n的堆H,容量为size,当tag为0或1时分别表示小顶堆和大顶堆。
(3)RemoveFirstHeap(Heap &H,RcdType &e)
初始条件:堆H已存在。
操作结果:删除堆H的堆顶结点且用e来返回。 若H.n<0返回ERROR,成功就返回OK。
(4)RemoveHeap(Heap &H,int pos,RcdType &e)
初始条件:堆H已存在,pos>0&&pos<=H.n。
操作结果:删除位置pos的结点,用e返回其值。 成功返回OK,否则返回ERROR。
(5)swapHeapElem(Heap &H,int i,int j)
初始条件:堆H已存在,i和j都大于0且小于等于H.n。
操作结果:交换堆H中的第i结点和第j结点,初始条件符合且交换成功返回OK,否则返回ERROR。
(6)DestroyHeap(Heap &H)
初始条件:堆H已存在。
操作结果:销毁堆H。 成功返回OK。
(7)InsertHeap(Heap &H,RcdType e)
初始条件:堆H已存在。
操作结果:将结点e插入到堆H中,且不改变H堆的特性。成功返回OK,堆已满则返回ERROR 。
(8)ShiftDown(Heap &H,int pos)
初始条件:堆H已存在,pos结点的左右子树都是堆。
操作结果:对堆H中位置为pos的结点做筛选,将以pos为根的子树调整为子堆。
(9)printHeap(Heap H)
初始条件:堆H已存在
操作结果:把H用顺序存储结构和树的表示在屏幕上打印出来
(10)HeapSort(RcdSqList &L,int tag)
初始条件:L[1..n]存要排序的记录,tag分别用1或0表示大顶堆或小顶堆
操作结果:对L[1..n]进行对排序,完成时L中记录是有序的,若tag=1则是从小到大排,否则从大到小排。
(11)greatPrior(KeyType x,KeyType y)
操作结果:大顶堆用的优先函数,若x>=y,返回值为1,否则为0。
(12)lessPrior(KeyType x,KeyType y)
操作结果:小顶堆用的优先函数,若x<=y,返回值为1,否则为。
#include<stdio.h>
#include<stdlib.h>
#include<math.h>
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
typedef int Status;
typedef int KeyType; //关键字类型为整数类型
typedef struct{
KeyType key; //关键字项
}RecordType,RcdType; //记录类型,RcdType为RecordType的简写
typedef struct{
RcdType *rcd; //存储空间基址
int length; //当前长度
int size; //存储容量
}RcdSqList;//记录的顺序表
typedef struct{
RcdType *rcd; //堆基址,0号单元闲置
int n; //堆长度
int size; //堆容量
int tag; //小顶堆与大顶堆的标志:tag=0为小顶堆,tag=1为大顶堆
}Heap; //堆类型
int greatPrior(KeyType x,KeyType y){ //大顶堆优先函数
return x>=y;
}
int lessPrior(KeyType x,KeyType y){ //小顶堆优先函数
return x<=y;
}
//初始化一个最大容量为size的空堆H,当tag为0或1时分别表示小顶堆和大顶堆
Status InitHeap(Heap &H,int size,int tag){
H.rcd=NULL;
H.size=size;
H.n=0;
H.tag=tag;
return OK;
}
//交换堆H中的第i结点和第j结点
Status swapHeapElem(Heap &H,int i,int j){
RcdType temp;
if(i<=0||i>H.n||j<=0||j>H.n){
return ERROR;
}
temp=H.rcd[i];
H.rcd[i]=H.rcd[j];
H.rcd[j]=temp;
return OK;
}
//对堆H中位置为pos的结点做筛选,将以pos为根的子树调整为子堆,前提是pos结点的左右子树都是堆
void ShiftDown(Heap &H,int pos){
int t,rt;
int (*prior)(KeyType,KeyType);
if(H.tag==1) prior=greatPrior;
else prior=lessPrior;
while(pos<=H.n/2){ //2*pos>n时没有左孩子,更没有右孩子,是叶子结点
t=pos*2; //pos的左孩子位置
rt=pos*2+1; //pos的右孩子位置
if(rt<=H.n&&prior(H.rcd[rt].key,H.rcd[t].key))
t=rt; //t为pos结点的左右孩子中较为优先者的位置
if(prior(H.rcd[pos].key,H.rcd[t].key))
return; //若pos结点较优先,则筛选结束
swapHeapElem(H,pos,t);//否则pos和较优先者t交换位置
pos=t; //继续向下调整
}
}
//用E建长度为n的堆H,容量为size,当tag为0或1时分别表示小顶堆和大顶堆
void MakeHeap(Heap &H,RcdType *E,int n,int size,int tag){
int i;
H.rcd=E; //E[1..n]是堆的n个结点,0号单元闲置
H.n=n;
H.size=size;
H.tag=tag;
int (*prior)(KeyType,KeyType);
if(H.tag==1) prior=greatPrior;
else prior=lessPrior;
for(i=H.n/2;i>0;i--) //对所有非叶子结点筛选
ShiftDown(H,i);
}
//销毁堆H
Status DestroyHeap(Heap &H){
free(H.rcd);
H.rcd=NULL;
H.n=0;
H.size=0;
return OK;
}
//将e插入堆,从堆尾插入
Status InsertHeap(Heap &H,RcdType e){
int curr;
if(H.n>=H.size-1||H.size==0) return ERROR;//堆已满,插入失败
//问题点
if(H.n==0){
H.rcd=(RcdType*)malloc(sizeof(RcdType)*H.size);
H.rcd[1]=e;
}
curr=++H.n;
H.rcd[curr]=e;
int (*prior)(KeyType,KeyType);
if(H.tag==1) prior=greatPrior;
else prior=lessPrior;
while(1!=curr&&prior(H.rcd[curr].key,H.rcd[curr/2].key)){
swapHeapElem(H,curr,curr/2);
curr/=2;
}
return OK;
}
//删除堆H的堆顶结点,并用e将其返回
Status RemoveFirstHeap(Heap &H,RcdType &e){
if(H.n<=0) return ERROR;
e=H.rcd[1];
swapHeapElem(H,1,H.n);
H.n--; //交换堆顶与堆尾结点,堆长度减1
if(H.n>1) ShiftDown(H,1);//从对顶位置向下筛选
return OK;
}
//删除位置pos的结点,用e返回其值
Status RemoveHeap(Heap &H,int pos,RcdType &e){
if(H.n<=0||pos<=0||pos>H.n) return ERROR;
e=H.rcd[pos];
swapHeapElem(H,pos,H.n);
H.n--;
if(H.n>1) ShiftDown(H,pos);
return OK;
}
//堆排序
void HeapSort(RcdSqList &L,int tag){
Heap H;
int i;
RcdType e;
MakeHeap(H,L.rcd,L.length,L.size,tag);
for(i=H.n;i>0;i--){
RemoveFirstHeap(H,e);
}
}
//求深度
int Depth(Heap H){
float depth=log(H.n)/log(2)+1;
return (int)depth;
}
//打印堆的记录
void printHeap(Heap H){
if(H.rcd==NULL){
printf("此堆为空\n");
return;
}
int i,j;
printf("顺序存储结构:\n");
for(i=1;i<=H.n;i++){
printf("%d ",H.rcd[i].key);
}
printf("\n\n树形:\n");
for(i=1,j=1;i<=H.n;i++){
printf("%d ",H.rcd[i].key);
if(i==(int)pow(2,j)-1){
printf("\n");
j++;
}
}
printf("\n深度为%d\n\n",(Depth(H)));
}
int main(){
Heap H; //目标堆
int size=40,i,n;
RcdSqList L; //存用来做堆排序的记录
RcdType E[40]; //创建堆等操作专用
RcdType R[40]; //堆排序专用
printf("\n----------开始----------\n\n\n");
printf("你可以进行下面的操作:\n");
printf("1、初始化一个空堆H\n");
printf("2、创建一个有记录的堆H\n");
printf("3、删除堆H顶结点\n");
printf("4、删除堆H指定的pos位置的结点\n");
printf("5、交换堆H中的第i结点和第j结点\n");
printf("6、销毁堆H\n");
printf("7、将e插入堆H\n");
printf("8、对位置pos结点做筛选\n");
printf("9、打印堆H\n");
printf("10、堆排序\n");
printf("11、测试大顶堆函数\n");
printf("12、测试小顶堆函数\n");
printf("13、退出\n\n");
int choice=0;
int tag,pos,t1,t2;
KeyType x,y;
RcdType a;
while(choice!=13){
printf("请输入你的选择:\n");
scanf("%d",&choice);
switch(choice){
case 1:
//测试初始化函数 Status InitHeap(Heap &H,int size,int tag)
printf("请选择 1、大顶堆 0、小顶堆\n");
scanf("%d",&tag);
if(tag!=0&&tag!=1) {
printf("输入数据有误,请重新选择\n");
break;
}
if(InitHeap(H,size,tag))
printf("初始化成功\n");
else
printf("初始化失败\n");
printf("你创建的堆为:\n\n");
printHeap(H);
break;
case 2:
//建长度为n的堆H,容量为size,当tag为0或1时分别表示小顶堆和大顶堆
printf("请选择 0、小顶堆 1、大顶堆\n");
scanf("%d",&tag);
if(tag!=0&&tag!=1) {
printf("输入数据有误,请重新选择\n");
break;
}
printf("你想创建一个对长度为多少的堆\n");
printf("输入后按回车(小于%d)\n\n",size);
printf("堆长度 n=");
scanf("%d",&n);
if(n>=size||n<0){
printf("数据不合法,请重新选择再输入\n");
break;
}
if(n==0){
InitHeap(H,size,tag);
printf("你创建的堆为:\n\n");
printHeap(H);
break;
}
printf("\n请输入%d个数\n",n);
for(i=1;i<=n;i++){
printf("第%d个记录的关键字:",i);
scanf("%d",&E[i].key);
}
printf("\n----------输入结束----------\n\n");
printf("你创建的堆为:\n\n");
MakeHeap(H,E,n,size,tag);
printHeap(H);
break;
case 3:
//测试删除堆顶结点的函数 Status RemoveFirstHeap(Heap &H,RcdType &e)
if(RemoveFirstHeap(H,a)){
printf("堆顶结点记录为:%d\n",a.key);
printf("删除堆顶后的堆是:\n");
printHeap(H);
}else{
printf("删除失败,此堆为空堆,请重新选择\n");
}
break;
case 4:
//测试删除pos位置结点的函数 Status RemoveHeap(Heap &H,int pos,RcdType &e)
printf("你要删除结点的位置是pos=");
scanf("%d",&pos);
if(RemoveHeap(H,pos,a)){
printf("你删除了%d位置结点记录为:%d\n",pos,a.key);
printf("删除%d位置结点后的堆是:\n",pos);
printHeap(H);
}else{
printf("删除失败,数据有误,请重新选择\n");
}
break;
case 5:
//测试交换函数 Status swapHeapElem(Heap &H,int i,int j)
printf("请输入你想交换的两个结点的位置:(中间用逗号[,]隔开)\n");
scanf("%d,%d",&t1,&t2);
if(swapHeapElem(H,t1,t2)){
printf("交换成功\n");
printHeap(H);
}
else
printf("交换失败,请重新选择\n");
break;
case 6:
//测试销毁函数
DestroyHeap(H);
printf("摧毁成功\n");
break;
case 7:
//测试插入函数 Status InsertHeap(Heap &H,RcdType e)
printf("请输入你要插入的记录关键字RcdType a=");
scanf("%d",&a.key);
if(InsertHeap(H,a)){
printf("插入后的堆\n");
printHeap(H);
}
else printf("插入失败,堆已满或者已摧毁,请重新选择\n");
break;
case 8:
//对堆H中位置为pos的结点做筛选,将以pos为根的子树调整为子堆
printf("请输入你要做筛选的位置pos=");
scanf("%d",&pos);
if(pos<=0||pos>H.n) {
printf("输入数据有误,请重新选择\n");
break;
}
ShiftDown(H,pos);
printf("筛选成功\n");
printHeap(H);
break;
case 9:
printHeap(H);
break;
case 10:
//测试堆排序函数 void HeapSort(RcdSqList &L)
printf("请选择 0、降序 1、升序\n");
scanf("%d",&tag);
if(tag!=0&&tag!=1) {
printf("输入数据有误,请重新选择\n");
break;
}
printf("你要排序的记录有多少个?\n");
scanf("%d",&L.length);
if(L.length>=size){printf("输入数据有误,请重新选择\n");break;}
L.size=size;
for(i=1;i<=L.length;i++){
printf("第%d个记录关键字:",i);
scanf("%d",&R[i].key);
}
L.rcd=R;
HeapSort(L,tag);
printf("堆排序结果\n");
for(i=1;i<=L.length;i++){
printf("%d ",L.rcd[i].key);
}
printf("\n");
break;
case 11:
//测试大顶堆优先函数int greatPrior(KeyType x,KeyType y)
printf("你要测试的两个输请输入\n");
printf("第1个数:");
scanf("%d",&x);
printf("第2个数:");
scanf("%d",&y);
if(greatPrior(x,y)){
printf("%d优先于%d\n",x,y);
}else{
printf("%d优先于%d\n",y,x);
}
break;
case 12:
//测试小顶堆优先函数int lessPrior(KeyType x,KeyType y)
printf("你要测试的两个输请输入\n");
printf("第1个数:");
scanf("%d",&x);
printf("第2个数:");
scanf("%d",&y);
if(lessPrior(x,y)){
printf("%d优先于%d\n",x,y);
}else{
printf("%d优先于%d\n",y,x);
}
break;
case 13:
printf("你选择了退出,感谢使用\n");
break;
case 14:
printf("你可以进行下面的操作:\n");
printf("1、初始化一个空堆H\n");
printf("2、创建一个有记录的堆H\n");
printf("3、删除堆H顶结点\n");
printf("4、删除堆H指定的pos位置的结点\n");
printf("5、交换堆H中的第i结点和第j结点\n");
printf("6、销毁堆H\n");
printf("7、将e插入堆H\n");
printf("8、对位置pos结点做筛选\n");
printf("9、打印堆H\n");
printf("10、堆排序\n");
printf("11、测试大顶堆函数\n");
printf("12、测试小顶堆函数\n");
printf("13、退出\n\n");
break;
default:printf("输入错误,请输入1-14范围内的数字\n");break;
}
printf("----------分割线(输入14可以弹出选项)-----------\n\n");
}
}
5.调试分析
调试过程所遇到的问题和解决的方法:
(1) anyview平台上,不支持结构体的函数指针的引用,所以不能直接采用书本上的堆的类型定义。
解决方法:因为堆中H.tag有记录到堆H是大顶堆还是小顶堆,所以对于有限函数的引用问题,可以根据具体的H.tag的值来决定用greatPrior还是lessPrior函数。
(2) 如需输入的一些操作,输入的数据是有限制的,有时候输错会导致程序运行不下去。
解决方法:在用户输入后加入一些if等判断语句来判断数据是否合法,不合法就重新选择操作。
6.运行测试
提示:要先程序中选择对应的操作才能进行下面的数据测试
(1)基础操作1的测试:
第1组数据:输入1
第2组数据:输入0
(2)基础操作2的测试:
第1组数据:先输入3
第2组数据:先输入1,再输入大于等于size的值(如40)
第3组数据:先输入0,再输入0
第4组数据:先输入1,再输入3,再输入6个数:42,58,68
第5组数据:先输入0,再输入3,再输入6个数:42,58,68
第6组数据:先输入1,再输入6,再输入6个数:42,58,68,98,86,43第7组数据:先输入0,再输入6,再输入6个数:42,58,68,98,86,43
(3)基础操作3的测试:
第1组数据:先使用操作1创建空堆H,再选择3操作
第2组数据:先使用操作2创建堆H,再选择3操作
(4)基础操作4的测试:
第1组数据:先使用操作1创建空堆H,再选择4操作,输入2
第2组数据:先使用操作2创建堆H,其中堆H有6个元素,再选择4操作,输入7
第3组数据:先使用操作2创建堆H,其中堆H有6个元素,再选择4操作,输入5
第4组数据:先使用操作2创建堆H,其中堆H有6个元素,再选择4操作,输入1
(5)基础操作5的测试:
第1组数据:先使用操作1创建空堆H,再选择5操作,输入1,2
第2组数据:先使用操作2创建堆H,其中堆H有6个元素,再选择5操作,输入0,1
第3组数据:先使用操作2创建堆H,其中堆H有6个元素,再选择5操作,输入1,7
第4组数据:先使用操作2创建堆H,其中堆H有6个元素,再选择5操作,输入1,6
第5组数据:先使用操作2创建堆H,其中堆H有6个元素,再选择5操作,输入1,1
(6)基础操作6的测试:
第1组数据:先使用操作1创建空堆H,再选择6操作
第2组数据:先使用操作2创建堆H,再选择6操作
(7)基础操作7的测试:
第1组数据:先使用操作1创建空堆H,再选择7操作,输入22
第2组数据:先使用操作2创建堆H,其中堆H有6个元素,再选择7操作,输入22
第3组数据:先使用操作2创建堆H,其中堆H已满(例如H.n=39),再选择7操作,输入22
第4组数据:先使用操作2创建堆H,其中堆H有6个元素,再选择6操作摧毁,再选择操作7,输入22
(8)基础操作8的测试:
第1组数据:先使用操作1创建空堆H,再选择8操作,输入2
第2组数据:先使用操作1创建空堆H,再选择8操作,输入-1
第3组数据:先使用操作2创建堆H,其中堆H有6个元素,再选择8操作,输入0
第4组数据:先使用操作2创建堆H,其中堆H有6个元素,再选择8操作,输入3
第5组数据:先使用操作2创建堆H,其中堆H有6个元素,再选择8操作,输入7
(9)基础操作9的测试:
第1组数据:先使用操作1创建空堆H,再选择9操作
第2组数据:先使用操作2创建堆H,其中堆H有6个元素,再选择9操作
第3组数据:先使用操作2创建堆H,其中堆H有6个元素,再选择6操作,再选择9操作
(10)基础操作10的测试:
第1组数据:输入2
第2组数据:输入0,输入0
第3组数据:输入0,输入6,再输入6个数:42,58,68,98,86,43
第4组数据:输入1,输入6,再输入6个数:42,58,68,98,86,43
第5组数据:输入0,输入40(大于size)
第6组数据:输入1,输入40(大于size)
(11)基础操作11的测试:
第1组数据:输入3,5
第2组数据:输入40,60
(12)基础操作12的测试:
第1组数据:输入3,5
第2组数据:输入40,60
7. 存储结构的比较
基础操作 | 时间复杂度 |
InitHeap() | O(1) |
MakeHeap() | O(n) |
RemoveFirstHeap() | O(logn) |
RemoveHeap() | O(logn) |
swapHeapElem() | O(1) |
DestroyHeap() | O(1) |
InsertHeap() | O(logn) |
ShiftDown() | O(logn) |
printHeap() | O(n) |
HeapSort() | O(nlogn) |
greatPrior() | O(1) |
lessPrior() | O(1) |
8.思考与小结
(1)有几个操作如打印,插入等都需要判断堆H是否为空再采取相应的操作。
(2)堆排序是利用堆的特性来进行排序的,时间复杂度是O(nlogn),大顶堆排得到的是升序序列,小顶堆排得到的是降序序列。堆排序是不稳定的排序方法。
(3)堆是一种很实用的数据结构。
(4)交互界面要弄得简洁分明一点。