数据结构实验—栈及其应用
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
《算法与数据结构》课程实验报告
一、实验目的
1.熟悉栈的特点(先进后出)及栈的基本操作,如入栈、出栈等,掌握栈
的基本操作在栈的顺序存储结构。
2.实现栈的顺序存储结构,通过实验深入理解栈的操作特点。
二、实验内容及要求
1.实现栈的存储结构及相关操作:进栈、出栈、取栈顶元素等。
2.使用该栈完成对一个字符串的逆序输出。
3.使用该栈完成判断表达式的括号是否匹配。
4.对算术表达式求值。
三、系统分析
(1)数据方面:该栈数据元素类型采用浮点型,在此基础上进行栈的基本操作,并可将栈中数据使用文本文档保存。
在栈的应用中,采用的是存储字符元素类型的栈,并进行对字符的相关操作。
(2)功能方面:能实现栈的一些基本操作,主要包括:
1.进栈操作:若栈不满,则将元素x插入至栈的栈顶,若栈满则进行溢出
处理。
2.出栈操作:若栈不空,则函数返回该栈栈顶的元素,并且栈顶指针退1。
3.获取栈顶元素:若栈不空,则函数返回栈顶元素。
4.判断栈是否为空、判断栈是否满。
5.计算栈中元素个数:直接返回栈中元素个数。
6.清空栈内容:将栈顶指针赋为初始值。
7.保存数据:将栈中元素数据保存至文本文档中。
四、系统设计
(1)设计的主要思路
顺序栈可以采用顺序表作为其存储表示,为此,在顺序栈的声明中用顺序表定义它的存储空间。
存放栈元素的数组的头指针为*elements,该数组最大能允许存放元素个数为maxSize,当前栈顶位置由数组下标指针top知识。
并规定如果栈不空时,elements[0]为栈中第一个元素。
由于实验中还需完成栈的相关应用,故使用两个菜单分别完成栈的基本操作与栈的应用调试。
(2)数据结构的设计
顺序栈定义为只允许在表的末端进行插入和删除的线性表。
允许插入和删除的一端叫做栈顶,而不允许插入和删除的另一端叫做栈底。
当栈中没有任何元素时则成为空战。
即栈又被称为后进先出的线性表,故与线性表的相关操作类似,
在此基础上完成栈的相关操作及应用。
数据结构设计思路
(3)基本操作的设计
栈的基本操作主要包括进栈、出栈、弹出栈顶元素等。
主要难点是在栈相关应用的实现。
第一个应用即是通过栈的操作实现括号匹配。
思路则是依次扫描输入字符串,将左括号存放至栈中,后续扫描遇到右括号则与之相匹配,同时在栈顶删除左括号。
最后将括号匹配信息输出。
在实现栈进行表达式计算过程稍微复杂。
先编写一个Calculator类,该类实现表达式计算的进操作数、退操作数等操作,在主函数文件中编写将中缀表达式转为后缀表达式函数,将转化后的表达式在进行计算即可。
算法主要思路为:顺序扫描表达式每一项,如果是操作数则进行压栈,为操作符则弹出两个操作数,并进行指令运算,将结果再次压入栈中,处理完后,将结果输出即可。
由于为了实现多为操作数的计算故需作出相应改进,用一个字符串保存输入的表达式,在通过字符串操作将操作数和操作符保存至一个字符串数组中,再通过栈的操作将其转为后缀表达式并存放至字符串数组中,在在字符串数组的基础上进行表达式值得计算。
该函数可计算操作数为双精度的表达式。
五、编程环境与实验步骤
(1)编程环境
操作系统:Windows操作系统;编程工具软件:Visual Studio 2017
(2)实验步骤
程序相关文件为SeqStack模板类文件、Calculator类文件、以及主函数调试文件main.cpp。
Calculator类文件在栈应用操作中实现表达式相关计算,SeqStack 模板类文件包括栈的相关操作,例如进栈、出栈等操作。
主函数调试文件则是用于调试栈的基本操作,并且添加了相关栈应用的相关函数,可通过两个菜单的关联,完成栈的基本操作以及相关应用的实现。
(3)编译参数
无编译参数,在Vs2017或其他版本中新建项目然后将三个文件main.cpp、Calculator.h、SeqStack.h添加到解决方案中的头文件中调试即可。
六、实现代码
#include<iostream>
#include"Calculator.h"
#include<string>
using namespace std;
const int maxLength = 100; //最大匹配字符串长度
int isp(char &a);//栈内优先数
int icp(char &a);//栈外优先数
void PrintMatchedPairs(char *expression);//栈的应用——括号匹配
void postfix();//栈的应用——中缀表达式转化为后缀表达式
void inverse(char *c);//栈的应用——字符串逆序输出
void menu1();//栈的基本操作菜单
void menu2();//栈的应用菜单
string str2[1000];//用于存放中缀表达式
int number = 0;//用于表示表达式中数据个数
int main() {
int fini = 0; int choose;
while (!fini) {
cout <<"***************************************"<< endl;
cout <<"-----------1、栈的基本操作-------------"<< endl;
cout <<"-----------2、栈的应用-----------------"<< endl;
cout <<"-----------3、退出---------------------"<< endl;
cout <<"***************************************"<< endl;
cout <<"请输入你的选择[1-3]:"<< endl;
cin >> choose;
switch (choose) {
case 1:
menu1();
break;
case 2:
menu2();
break;
case 3:
fini = 1;
break;
default:
cout <<"输入选择错误,请重新输入!"<< endl;
}
}
return 0;
}
int isp(char &a) {
switch (a) {
case'#':
return 0; break;
case'(':
return 1; break;
case'*':case'%':case'/':
return 5; break;
case'+':case'-':
return 3; break;
case')':
return 6; break;
}
}
int icp(char &a) {
switch (a) {
case'#':
return 0; break;
case'(':
return 6; break;
case'*':case'%':case'/':
return 4; break;
case'+':case'-':
return 2; break;
case')':
return 1; break;
}
}
void PrintMatchedPairs(char *expression) {
SeqStack<int> s(maxLength);
int j, length = strlen(expression);
for (int i = 1; i <=length; i++) {
if (expression[i - 1] == '(')
s.Push(i);
else if (expression[i - 1] == ')') {
if (s.Pop(j) == true)
cout << j <<"与"<< i <<"匹配"<< endl;
else
cout <<"没有与第"<< i <<"个右括号匹配的左括号!"<< endl;
}
}
while (s.IsEmpty() == false) {
s.Pop(j);
cout <<"没有与第"<< j <<"个左括号匹配的右括号!"<< endl;
}
}
void postfix() {
SeqStack<char> s;
char ch = '#';
s.Push(ch);
string str ; //用于存放表达式
string num = "";//用于存放有多位的操作数
cout <<"请输入表达式:"<< endl; cin >> str;
for (int i = 0; i < str.length(); i++)
{
if (str[i] == '0' || str[i] == '1' || str[i] == '2' || str[i] == '3' || str[i] == '4' || str[i] == '5' || str[i] == '6' || str[i] == '7' || str[i] == '8' || str[i] == '9' || str[i] == '.') {
num = num + str[i];
}
else {
char ch1;
str2[number] = num; number++; num ="";
s.getTop(ch1);
if (isp(ch1) < icp(str[i])) {
s.Push(str[i]);
}
else if (isp(ch1) > icp(str[i])) {
char op;
s.Pop(op);
if (op != '(') {
str2[number] = op; number++; i--;
}
}
else {
char ch3;
s.Pop(ch3);
}
}
}
if (num !="") {
str2[number] = num; number++; num ="";
}
while (!s.IsEmpty())
{
char ch4; s.Pop(ch4); str2[number] = ch4; number++;
}
}
void inverse(char *c) {
SeqStack<char> s; int i = 0; char ch;
int l = strlen(c);
for (int i = 0; i < l; i++)
s.Push(c[i]);
for (int i = 0; i < l; i++) {
s.Pop(ch);
cout << ch <<" ";
}
}
void menu1() {
int fini = 0; int choose;SeqStack<double> s;
double x;//压栈元素
double y;//存储出栈元素
double z;//存储栈顶元素
while (!fini) {
cout <<"-----------一、栈的基本操作-------------"<< endl;
cout <<"-----------1:进栈操作-------------------"<< endl;
cout <<"-----------2:出栈操作-------------------"<< endl;
cout <<"-----------3:读取栈顶元素---------------"<< endl;
cout <<"-----------4:判断栈是否空---------------"<< endl;
cout <<"-----------5:判断栈是否满---------------"<< endl;
cout <<"-----------6:计算栈中元素个数-----------"<< endl;
cout <<"-----------7:清空栈的内容---------------"<< endl;
cout <<"-----------8:文件保存栈中元素-----------"<< endl;
cout <<"-----------9:退出:---------------------"<< endl;
cout <<"****************************************"<< endl;
cout <<"请输入你的选择[1-9]:"<< endl;
cin >> choose;
switch (choose) {
case 1:
cout <<"输入进栈数:"<<endl;
cin >> x;
s.Push(x);
break;
case 2:
s.Pop(y);
cout <<"弹出元素是:"<< y << endl;
break;
case 3:
s.getTop(z);
cout <<"此时栈顶元素为:"<< z << endl;
break;
case 4:
if (s.IsEmpty())
cout <<"栈为空!"<< endl;
else
cout <<"栈不为空!"<< endl;
break;
case 5:
if(s.IsFull())
cout <<"栈满!"<< endl;
else
cout <<"栈不满!"<< endl;
break;
case 6:
cout <<"此时栈中有"<< s.getSize() <<"个元素"<< endl;
break;
case 7:
s.MakeEmpty();
cout <<"栈被清空"<< endl;
case 8:
s.SaveFile();
cout <<"保存成功!(数据由顶至底)"<< endl;
cout <<"此时栈被清空!"<< endl;
break;
case 9:
fini = 1;
break;
default:
cout <<"输入选择错误,请重新输入!"<< endl;
}
}
}
void menu2() {
int fini = 0; int choose;
char c[maxLength];//输入字符串
double x;
Calculator ca(stackIncreament);
while (!fini) {
cout <<"-----------二、栈的应用-----------------"<< endl;
cout <<"-----------1:括号匹配-------------------"<< endl;
cout <<"-----------2:表达式的计算---------------"<< endl;
cout <<"-----------3:字符串逆序输出-------------"<< endl;
cout <<"-----------4:退出:---------------------"<< endl;
cout <<"****************************************"<< endl;
cout <<"请输入你的选择[1-4]:"<< endl;
cin >> choose;
switch (choose) {
case 1:
cin.get();
cin.getline(c,maxLength);
PrintMatchedPairs(c);
break;
case 2:
postfix();
ca.Run(x);
cout << x << endl;
break;
case 3:
cin.get();
cin.getline(c, maxLength);
cout <<"字符串逆序输出:"<< endl;
inverse(c);
cout << endl;
break;
case 4:
fini = 1;
break;
default:
cout <<"输入选择错误,请重新输入!"<< endl;
}
}
}
七、测试结果与说明
栈的基本操作:
进栈与出栈:进栈与读取元素:
栈中元素个数:栈空或满:
保存数据:
栈的相关应用:
字符串中括号匹配:
字符串逆序输出:
表达式计算:
八、实验分析
(1)算法的性能分析
栈的括号匹配算法分析。
该应用实现原理是建立一个算法,输入一个字符串,
输出匹配的括号和没有匹配的括号。
从左至右扫描输入字符串,每一个右括号将与最近遇到的那个未匹配的左括号相匹配。
在扫描过程中把遇到的左括号存放到栈中,当后续扫描过程中遇到一个右括号时,就将它与栈顶的左括号(如果存在)相匹配,同时在栈顶删除该左括号。
该算法的时间复杂度O(n),其中n是输入串的长度。
算法流程图如下:
栈的表达式计算算法分析。
在进行表达式计算前需要将表达式翻译成能够正确求值的指令序列。
我们平时使用的是中缀表达式,需要将中缀表达式转化为后缀表达式求表达式的值。
在利用后缀表达式求值时,从左向右顺序地扫描表达式,并使用一个栈暂存扫描到的操作数或进行计算结果。
在后缀表达式的计算顺序中已经隐含了计算操作符的优先次序。
如果扫描项是操作数,则将其压入栈中,如果该项是操作符,则连续从栈中退出两个操作数Y和X,形参运算指令X<op>Y,并将计算结果重新压入栈中,当表达式的所有项都扫描并处理完成后,栈顶存放的就是最后的计算结果。
算法流程图如下:
(2)数据结构的分析
由顺序栈的存储结构以及特点可分析出以下优缺点:
1.优点:
①存取速度块
②空间被高效管理,内存不会变成碎片
③变量创建和释放是自动的
2.缺点:
①受限于栈大小
②变量不能调整大小
③如果对数组大小创建不当,可能会产生栈溢出的情况
根据栈只能在一端进行操作,遵循先进后出或者后进先出的原则。
可分析出栈适用场景主要为字符串逆序输出,语法检查,符号成对出现,数制转化等应用场景。
九、实验总结
经过一个星期的努力,完成了数据结构的栈的实验内容,通过这次实验,能够熟练掌握栈的基本操作并且对于栈的应用有所了解,掌握了栈的特点及其顺序存储结构。
并掌握了对于使用栈处理字符串匹配、字符串逆序输出等的应用。
在
程序的整个分析、设计、实现、测试等环节都有所收获,当然也遇到许多问题,
但都一一克服了。
首先,在理论课上学习了栈的顺序存储方式,并且对于栈的基本操作有所学习,这对在开始阶段编写相关程序有很大的帮助,在程序的分析阶段也轻松了不少。
但是由于需要实现栈的相关应用,所以在程序的分析阶段还是花费了不少时间,在分析使用栈来实现表达式的计算部分代码时,需要将表达式转化为中缀表达式再通过栈的操作来实现,这就需要掌握在表达式转化过程中栈中元素的变化以及求值运算栈中元素的变化,并且思考如何通过代码的实现。
其次,在程序的设计阶段也让我有所收获,由于此次实验难点在于用栈实现相关应用,在理解了栈应用相关代码后在进行程序设计会轻松很多。
在设计该程序时候,考虑到涉及到栈的基本操作与栈的几个相关应用,于是使用两个菜单分别可以进行栈的基本操作和栈的相关应用。
在进行栈的操作后可选择将栈中所有元素保存至文本文档中,而栈的应用则是将结果直接输出到屏幕。
在程序的实现阶段也是遇到了一些小麻烦,主要问题是在实现栈的应用环节。
在实现括号匹配应用和字符串逆序输出这两个应用时都没遇到太大问题,但在实现表达式计算时花费了很多时间,主要是对代码理解的不够彻底,因为涉及到中缀表达式转化为后缀表达式的算法以及转化成功后的如何进行相应计算,这两个问题稍微有点难度所以导致在实现的时候会遇到很多问题,例如在最初完成代码后能够成功实现,但当进行计算的数据为两位数以上后就会出现错误,于是只好重新读代码,再次理解代码含义,重新进行编写相关函数,好在最后通过琢磨并查阅相关资料成功解决了问题。
还有就是在进行字符数组的输入输出以及读取等操作时出现了对字符数组相关函数的遗忘,于是只好重新对相关知识进行巩固,这个过程也花费了一些时间。
在程序的测试阶段基本没有遇到太大得问题,在屡清楚各个菜单之间的关联之后,并且确定每个函数都能完成对应的功能,把菜单中的每个选项进行分别运行就行。
经过几次实验经验的积累,这个阶段基本不会耗费太大时间。
通过此次实验,不仅在编写相关算法的能力有所提高,同时对栈的基本操作理解也相当到位,更能将栈实际应用在不同场景中,但还需要对相关重要代码进行深入理解,方便日后再使用栈时更加熟练。