实验题目:栈和队列的关键算法实现及性能分析
一、实验目的
1. 能够运用高级程序设计技术实现栈和队列及其关键算法(实验1、2)
2. 能够通过对比分析的方法分析的关键算法性能的影响规律(实验1、2)
3. 具体任务参加各个实验的任务书
二、实验原理
1.栈的基本原理
(1)栈它是一种运算受限的线性表。限定仅在表尾进行插入和删除操作的线性表。这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。
(2)基本算法:
进栈(PUSH)算法:
①若TOP≥n时,则给出溢出信息,作出错处理(进栈前首先检查栈是否已满,满则溢出;不满则作②);
②置TOP=TOP+1(栈指针加1,指向进栈地址);
③S(TOP)=X,结束(X为新进栈的元素);
退栈(POP)算法:
①若TOP≤0,则给出下溢信息,作出错处理(退栈前先检查是否已为空栈, 空则下溢;不空则作②);
②X=S(TOP),(退栈后的元素赋给X):
③TOP=TOP-1,结束(栈指针减1,指向栈顶)
(3)定义stack的简单代码:
stack<int> sta;
入栈:sta.push(x);
出栈:sta.pop();
判断栈的大小: sta.size();
判断栈是否为空:sta.empty();
(4)队列的基本原理
队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。队列中没有元素时,称为空队列。队列的数据元素又称为队列元素。在队列中插入一个队列元素称为入队,从队列中删除一个队列元素称为出队。因为队列只允许在一端插入,在另一端删除,所以只有最早进入队列的元素才能最先从队列中删除。
(5)队列基本算法
初始化队列:Init_Queue(q) ,初始条件:队q 不存在。操作结果:构造了一个空队;
入队操作: In_Queue(q,x),初始条件: 队q 存在。操作结果: 对已存在的队列q,插入一个元素x 到队尾,队发生变化;
出队操作: Out_Queue(q,x),初始条件: 队q 存在且非空,操作结果: 删除队首元素,并返回其值,队发生变化;
读队头元素:Front_Queue(q,x),初始条件: 队q 存在且非空,操作结果: 读队头元素,并返回其值,队不变;
判队空操作:Empty_Queue(q),初始条件: 队q 存在,操作结果: 若q 为空队则返回为1,否则返回为0。
2.关键算法设计原理及性能影响因素分析
①顺序栈是静态分配的,而链栈则是动态分配的,二者存储结构不同。链栈可以将很多零碎的空间利用起来,容量可变,节省空间,顺序栈则固定内存空间,容量不变。
顺序栈查询速度快,链栈添加删除数据更快。
尽管顺序栈简单、存储密度高。但是即使栈的长度很长,也还是可能发生上溢。当栈的容量不固定时,必须设置栈的长度以使其可以容纳更多的元素,容易浪费空间。
当栈的容量不固定时,必须设置栈,使其容纳最多的数据元素,这样会浪费很对存储空间,也可能产生上溢出现象,采用链式存储结构就不会产生类似的问题。
因此两种栈的适用范围受很多因素的影响。
②队列遵循先进先出的原则,这是和栈不同的地方。整个顺序队列在数据不断地进队出队过程中,在顺序表中的位置不断后移。这将导致顺序队列之前的数组存储空间将无法再被使用,造成了空间浪费;而且如果顺序表申请的空间不足够大,则直接造成程序中数组 a 溢出,产生溢出错误。
三、实验方案设计
1.存储方案设计
分析:(1)栈:向一个栈插入新元素(即入栈)时,就是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素(即出栈)时,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。这两个操作都是在栈顶元素上进行操作,也就是“先进先出”。首先要定义栈顶最大容量,如果元素较多时要定义一个容量较大的栈,用于储存数量规模未知的元素,并防止元素溢出。所以在十进制转二进制的实验中,设计一个顺序栈来进行后续实验。
- (2)队列:队列中数据的进出要遵循 "先进先出" 的原则,即最先进队列的数据元素,同样要最先出队列。所以在考虑排队问题时优先选择队列。而队列的特点就能满足舞伴的选择问题。先定义两个较大的队列长度,分别用于存储男性和女性的信息。再进行入队操作,让先进入队列的元素(即舞者)优先和另一队列排在前面的进行两两配对。从而达到目的。
3.算法的设计和实现
栈:
(1)、栈的初始化: Status InitStack(SqStack &S){
S.base = (SElemType *)malloc(STACK_INIT_SIZE * sizeof(SElemType))
if(!S.base) exit(OVERFLOW); //存储分配失败
S.top = S.base;
S.stacksize = STACK_INIT_SIZE;
return OK;
}
(2)、栈顶元素插入: Status Push(SqStack &S, SElemType &e){
if(S.top - S.base >= S.stacksize){ //栈满,追加存储空间
S.base = (SElemType *)realloc(S.base, (S.stacksize + STACKINCREMENT) * sizeof(SElemType));
if(!S.base) exit(OVERFLOW); //存储分配失败
S.top = S.base + S.stacksize;
S.stacksize += STACKINCREMENT;
}
* S.top++ = e;
return OK;
}
(3)、栈顶元素获取:Status GetTop(SqStack &S, SElemType &e){
if(S.top == S.base) return ERROR;
e = *(S.top - 1);
return OK;
}
(4)、栈的销毁: Status DestroyStack(SqStack &S){
free(S.base);
S.base = NULL;
S.top = NULL;
S.stacksize = 0;
return OK;
}
(5)判断s是 否为空栈
Empty(s)
{
if(s.top==-1)
return true;
return false;
}
队列:
空队列
- .创建空队列
LinkQueue InitQueue( )
{
LinkQueue Q;
Q. front=Q. rear= (QueuePtr) malloc(LENG);
Q. front->next=NULL;
return Q;
}
(2)入队列
EnQueue(q, e)//进入队列
{
if(Q->rear>=MAXQSIZE)
return ERROR;
Q->data[Q->rear]=e;
Q->rear++;
return OK;
}
(3)出队列
DeQueue(q,e)
{
if(q->rear==q->front)
return ERROR;
e=q->data[q->front];
q->front++ ;
return OK;
}
(4)判断q是否为空队列
QueueEmpty(Q)
{
if(Q->rear==Q->front)
return OK;
return ERROR;
}
(5)销毁队列
DestroyQueue (I inkQueue &Q)
{
while(Q.front)
{
Q.rear=Q.front-> >next;
free(Q.front);
Q.front=Q.rear;
}
return OK;
}
3.测试方案
(1)栈的相关测试
①入栈操作(先对栈进行初始化)
测试数据:99 88 77 66 55 44 33 22 11
预期结果:栈内存储了99 88 77 66 55 44 33 22 11共9个元素。
②判断栈是否为空。
测试数据:&S
预期结果:false
③在确定栈非空之后,判断栈的长度( 调用StackLength( SqStack S ) )
测试数据:&S
预期结果:9
④选择元素进行入栈操作。并重新判断栈的长度。
测试数据:123 789
预期结果:栈内元素为99 88 77 66 55 44 33 22 11 123 789
且对于栈长度判断返回11
⑤进行出栈操作,出栈4个元素,并重新判断栈的长度。
测试数据:&S
预期结果:789 123 11 22
此时返回栈的长度为7
(2)队列的测试数据
①通过入对操作进行数据的输入(创建一个新的队列后)
测试数据:12 23 34 45 56 67 78 89 90
预期结果:队列内存储了12 23 34 45 56 67 78 89 90共9个元素
②判断栈是否为空。(通过QueueEmpty(Q)函数进行判断)
预期结果:ERROR
③在进行出队操作后进行取队首元素操作,结果应该只有一个出队
测试数据:&Q
预期结果:12 23 对中所剩元素个数为8
实验报告
- 概述
本次内容是与栈和队列相关的三个实验题。包括对顺序栈和队列的操作,比如判断栈是否为空、入栈、删除栈顶元素、加入队列、出队列等内容。在本次实验中,首先建立一个容量较大的空栈,然后输入数据存入栈中,通过不同的元素的输入,实现入栈、出栈等操作。入栈和出栈的操作类似,在进行完这两个操作时,都是在栈顶元素上进行操作的,即后进入的元素,同时记录数据元素的长度的标示符也要跟着改变。队列和栈存储和释放元素的方式是不同的,队列所遵循的是先进先出原则。队列是只允许在一端进行插入操作,在另一端进行删除操作的线性表。允许插入的一端为队尾,允许删除的一端为队头,先进先出,相邻元素具有前驱与后继关系。考虑到十进制转二进制的要求以及特点,选择用顺序栈来实现,而舞伴分配问题类似于排队问题,选择用队列来实现。顺序栈连续存储,有固定的内存空间,空间利用率高,但正是由于是一段连续的存储空间,所以不方便数据的增删。只允许在表的前端进行删除操作,而在表的后端进行插入操作。当队列为空时,有front=rear,而当所有队列空间全占满时,也有front=rear。为了区别这两种情况,规定循环队列最多只能有MaxSize-1个队列元素,当循环队列中只剩下一个空存储单元时,队列就已经满了。在实际的物理空间中,顺序队列的数据集中存储,链队列的数据分散存储。
二、实验过程
1.调试分析
(1).PTA代码里由于缺少缺少头文件,导致printf函数无法使用。
(2).由于在输入代码时误将赋值操作符写成了等于操作符,导致运行结果错误。
(3)返回的大小写错误,在c语言中,大小写分别是两个不同的变量,大小写的不同使其蕴含的意思也完全不相同,于是在该函数的返回中返回来“ok”这个不存在的值,所以会进行报错,解决方法为将“ok”改为“OK”。
2.测试过程
对于不同的实验应该有不同的输入,所以将实验分为三个大部分,一个实验对应一个部分,并且每一部分应有不同的测试数据。
实验一:十进制转二进制(顺序栈设计和应用)
该实验主要考察对顺序栈的操作部分,由于只涉及到进制的转换,所以可进行多组数据进行测试。
第一个测试数据:12
运行结果如下图:
第二个测试数据:26
运行结果如下图:
由以上两个图可以得到,运行结果和预期结果完全相符,所以该程序测试完毕且无误。
实验二:jmu-ds-舞伴问题
舞伴问题主要是利用入队列、出队列的操作,通过创造一个空的队列,再让男士和女士分别进入队列,再进行出队列,从而达到最终目的。但由于情境的要求,故而需要大量的数据完成。
第一个测试数据:
共7个元素,分别为 赵F 钱M 孙M 李M 周F 吴M 郑F
运行结果如下图:
第二个测试数据:
共4个元素,分别为张F 蒋F 沈M 韩M
运行结果如下图:
三、评价分析
1.实验结果分析
实验过程中,对程序的调试基本达到了预期的效果,对于栈的实验中,由于初始定义的栈的空间较小,导致了第一次运行时发生了“溢出”。后期增大的栈的空间,在保证不发生“溢出”的前提下,成功的完成了入栈操作。
调试舞伴问题的实验时,成功的实现了预期的目标。只是选择用于测试的数据不够多样,程序还有改进的空间。
2.算法性能评价
顺序栈的算法较为单一,所以重点在于入栈和出栈的算法进行优化和改善,与队列不同的是,入栈和出栈不必改变和移动其他的元素。但是由于顺序栈的连续空间大小已定,所以可以用链栈来解决空间不连续的问题。
- 总结与体会
对于栈和队列都属于线性表问题,但是栈的特点是“后进先出”,在栈顶操作。队列是“先进先出”,两者各有所长。顺序栈的结构比较简单,逻辑上相邻的元素在物理上也是相邻的。而且在不使用指针的前提下节省了很多的储存空间。但是需要考虑到元素入栈时是否溢出的问题。队列和生活中的排队很像,两者类比可以更快地理解。有了线性表的实验原理基础,再经过这次实验,对于栈和队列的相关代码已基本熟悉,算法知识得到了复习与巩固。在写代码的过程与调试中,在解决问题过程中,丰富了个人编程的经历和经验,提高了个人解决问题的能力。但是在编写程序的过程中关于c语言的指针方面的内容还不能得心应手,这也影响了对数据结构这门课的理解和掌握。所以还要多练习,达到熟能生巧。