- 实验目的
仅仅认识到栈和队列是两种特殊的线性表是远远不够的,本次实验的目的在于使学生深入了解栈和队列的特征,以便在实际问题背景下灵活运用它们;同时还将巩固这两种结构的构造方法,接触较复杂问题的递归算法设计。
- 实验内容
- 数制转换问题
[问题描述]
将十进制数N和其它d进制数的转换是计算机实现计算的基本问题,其解决方案很多,
其中最简单方法基于下列原理:即除d取余法。例如:(1348)10=(2504)8
N | N div 8 | N mod 8 |
1348 | 168 | 4 |
168 | 21 | 0 |
21 | 2 | 5 |
2 | 0 | 2 |
从表中我们可以看出,最先产生的余数4是转换结果的最低位,这正好符合栈的特性即后进先出的特性。所以可以用顺序栈来模拟这个过程。
[基本要求]
对于键盘输入的任意一个非负的十进制整数,打印输出与其等值的八进制数。由于上述
的计算过程是从低位到高位顺序产生的八进制数的各个数位,而打印输出,一般来说应从高位到低位进行,恰好和计算过程相反。因此可以先将计算过程中得到的八进制数的各位进栈,待相对应的八进制数的各位均产生以后,再使其按顺序出栈,并打印输出。即得到了与输入的十进制数相对应的八进制数。
[测试数据]
由学生依据软件工程的测试技术自己确定。注意测试边界数据。
2) 括号匹配的检验
[问题描述]
假设表达式中允许有两种括号:圆括号和方括号,其嵌套的顺序随意,即(()[ ])或
[([ ] [ ])]等为正确格式,[( ])或(((]均为不正确的格式。检验括号是否匹配的方法可用“期待的紧迫程度”这个概念来描述。例如:考虑下列的括号序列:
[ ( [ ] [ ] ) ]
1 2 3 4 5 6 7 8
当计算机接受了第1个括号以后,它期待着与其匹配的第8个括号的出现,然而等来的却是第2个括号,此时第1个括号“[”只能暂时靠边,而迫切等待与第2个括号相匹配的第7个括号“)”的出现,类似的,因只等来了第3个括号“[”,此时,其期待的紧迫程度较第2个括号更紧迫,则第2个括号只能靠边,让位于第3个括号,显然第3个括号的期待紧迫程度高于第2个括号,而第2个括号的期待紧迫程度高于第1个括号;在接受了第4个括号之后,第3个括号的期待得到了满足,消解之后,第2个括号的期待匹配就成了最急迫的任务了,…… ,依次类推。可见这个处理过程正好和栈的特点相吻合。
[基本要求]
读入圆括号和方括号的任意序列,输出“匹配”或“此串括号匹配不合法”。
[测试数据]
输入([ ]()),结果“匹配”
输入 [(] ),结果“此串括号匹配不合法”
[实现提示]
设置一个栈,每读入一个括号,若是左括号,则作为一个新的更急迫的期待压入栈中;
若是右括号,并且与当前栈顶的左括号相匹配,则将当前栈顶的左括号退出,继续读下一个括号,如果读入的右括号与当前栈顶的左括号不匹配,则属于不合法的情况。在初始和结束时,栈应该是空的。
- 实验环境
Dev C++和Visual Studio 2022
- 实验过程和实验结果
- 实验1:数制转换问题
问题分析与设计方案:
- 建立堆栈数据类型
- 初始化栈,压栈,出栈,取最顶层元素
- 进行转换步骤:代余除法依次将结果存入栈中
- 将栈中数据打印出来即可
调试:
-
-
-
- vs总是抛出新特性让我来修改,对用纯C写程序不太友好。
-
-
-
-
-
- 同样是因为define多写了’;’用dev C++报错给的就很一目了然,但是vs报错就离谱
- 动态内存分配失败:感觉像是地址指针这部分又晕了
- 初始化栈的代码写错了漏了一个非号!导致一直分配失败
- 同样是因为define多写了’;’用dev C++报错给的就很一目了然,但是vs报错就离谱
-
-
改完后:
-
-
-
- 又一次愚蠢的把”==”写成了”=”
-
-
导致if判断一直正确
代码:
- //数制转换问题
- #include<iostream>
- #include<stdlib.h>
- #include<stdio.h>
- using namespace std;
- #define STACK_INIT_SIZE 100
- #define STACKINCREMENT 10
- #define OK 1
- #define ERROR -1
- typedef int Status;//用define和typedef都可以实现重命名
- typedef int SElemType;
- typedef struct {
- SElemType* base;
- SElemType* top;
- int stacksize;
- }SqStack;
- //-----基本操作的函数原型声明-----
- Status InitStack(SqStack& S);
- //构造一个空栈
- //Status DestroyStack(SqStack& S);
- //销毁栈S
- //Status ClearStack(SqStack& S);
- //把S设置为空栈
- //Status StackEmpty(SqStack S);
- //若栈S为空栈,返回TRUE,否则返回FALSE
- //int StackLength(SqStack& S);
- //返回S的元素个数,即栈的长度
- Status GetTop(SqStack& S, SElemType &e);
- //若栈不空,则用e返回S的栈顶元素
- Status Push(SqStack& S, SElemType e);
- //压栈,插入元素e为新的栈顶元素
- Status Pop(SqStack& S, SElemType& e);
- //若栈不空,则删除S的栈顶元素,用e返回其值,并返回OK,否则返回ERROR
- //-----基本操作的算法描述-----
- Status InitStack(SqStack& S) {
- S.base = (SElemType*)malloc((STACK_INIT_SIZE) * sizeof(SElemType));
- if (!S.base) {
- printf("1动态空间分配失败!\n");
- exit(OVERFLOW);
- }
- S.top = S.base;
- S.stacksize = STACK_INIT_SIZE;
- return OK;
- }
- Status GetTop(SqStack S, SElemType& e) {
- //若栈不空,则用e返回S的栈顶元素
- if (S.top == S.base) return ERROR;
- e = *(S.top - 1);
- printf("该栈栈顶元素是:%d", e);
- return OK;
- }
- 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) { printf("2动态存储空间分配失败!\n"); exit(OVERFLOW); }
- S.top = S.base + S.stacksize;
- S.stacksize += STACKINCREMENT;//更新栈的最大容量
- }
- *S.top = e;
- S.top++;
- printf("插入新栈顶元素%d成功!\n",e);
- return OK;
- }
- Status Pop(SqStack& S, SElemType& e) {//这个地方有没有&一会验证一下
- if (S.top == S.base) {
- printf("该栈是空栈!\n");
- return ERROR;
- }
- e = *(S.top - 1);
- S.top--;
- return OK;
- }
- int main() {
- SqStack S;
- InitStack(S);
- int number;
- int b;
- int e=0;
- cout << "请输入待转换的十进制数字(不超过2147483647)" << endl;
- cin >> number;
- while (number / 8) {
- b = number % 8;
- Push(S, b);
- number = number / 8;
- if (number < 8) Push(S, number); //加这么一个判断就解决问题了
- }
- while (Pop(S, e)) {
- cout << e;
- if (S.top == S.base) break;
- }
- return 0;
- }
运行结果
实验2:
问题分析与设计方案:
- 构建栈的数据类型(利用上一个的栈的代码打打补丁做一些小修改)
- 构建基本的参数
- 利用栈存储左括号,并且如果输入右括号在出栈顶层元素比较是否匹配
- 检验健壮性有无非括号的字符
调试:
- If判断里面这个判断写的不对
应该改成这样:
- 最开始本来打算用ASCII码+1的方式一次性完成小括号和中括号的映射,结果发现前后中括号的ASCII码之间差2
- 出现了如下bug:
经调试发现是只要程序顺利走完全程中途没有exit都会执行这一步,追踪S.top和S.base发现判断等号又写成了单等号,于是进行如下修改:
同时也对代码其他部分不匹配的原因补充了标注。
代码:
- #include<stdio.h>
- #include<stdlib.h>
- #include<iostream>
- #include<string.h>
- using namespace std;
- #define STACK_INIT_SIZE 100
- #define STACKINCREMENT 10
- #define OK 1
- #define ERROR -1
- typedef int Status;//用define和typedef都可以实现重命名
- typedef char SElemType;
- typedef struct {
- SElemType* base;
- SElemType* top;
- int stacksize;
- }SqStack;
- //-----基本操作的函数原型声明-----
- Status InitStack(SqStack& S);
- //构造一个空栈
- Status GetTop(SqStack& S, SElemType& e);
- //若栈不空,则用e返回S的栈顶元素
- Status Push(SqStack& S, SElemType e);
- //压栈,插入元素e为新的栈顶元素
- Status Pop(SqStack& S, SElemType& e);
- //若栈不空,则删除S的栈顶元素,用e返回其值,并返回OK,否则返回ERROR
- //-----基本操作的算法描述-----
- Status InitStack(SqStack& S) {
- S.base = (SElemType*)malloc((STACK_INIT_SIZE) * sizeof(SElemType));
- if (!S.base) {
- printf("1动态空间分配失败!\n");
- exit(OVERFLOW);
- }
- S.top = S.base;
- S.stacksize = STACK_INIT_SIZE;
- return OK;
- }
- Status GetTop(SqStack S, SElemType& e) {
- //若栈不空,则用e返回S的栈顶元素
- if (S.top == S.base) return ERROR;
- e = *(S.top - 1);
- printf("该栈栈顶元素是:%c", e);
- return OK;
- }
- 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) { printf("2动态存储空间分配失败!\n"); exit(OVERFLOW); }
- S.top = S.base + S.stacksize;
- S.stacksize += STACKINCREMENT;//更新栈的最大容量
- }
- *S.top = e;
- S.top++;
- //printf("插入新栈顶元素'%c'成功!\n", e);
- return OK;
- }
- Status Pop(SqStack& S, SElemType& e) {//这个地方有没有&一会验证一下
- if (S.top == S.base) {
- printf("括号不匹配(可能原因是右括号数量多余左括号)\n");
- exit(1);
- return ERROR;
- }
- e = *(S.top - 1);
- S.top--;
- //printf("弹出栈顶元素'%c'成功!\n", e);
- return OK;
- }
- int main() {
- cout << "请输入括号串" << endl;
- char data[100];
- cin >> data;
- SqStack S;
- InitStack(S);
- char tmp;
- char e;
- int length = strlen(data);
- int i;
- for (i = 0; i < length; i++) {
- tmp = data[i];
- if ((tmp == '(') || (tmp == '[')) {
- Push(S, tmp);
- }
- else if (tmp == ')') {
- Pop(S, e);
- if (e == '(') {
- //cout << "匹配+1" << endl;
- }
- else { cout << "不匹配(小括号中括号搭配有问题)" << endl; exit(1); }
- }
- else if (tmp == ']') {
- Pop(S, e);
- if (e == '[') {
- //cout << "匹配+1" << endl;
- }
- else { cout << "不匹配(小括号中括号搭配有问题)" << endl; exit(1); }
- }
- else {
- cout << "输入了非法字符,程序关闭" << endl;
- exit(1);
- }
- }
- if (S.top == S.base) {
- cout << "*****匹配成功!*****" << endl;
- }
- else {
- cout << "不匹配(原因可能是左括号数量大于右括号数量)" << endl;
- }
- return 0;
- }
运行结果
- 实验心得
- 巩固了C语言和C++的知识点,对指针的理解更进一步:指针调用结构体内的变量是高效,在初次学习C语言的时候并未理解透彻。
- 一个函数想要搞出多个返回值可以通过地址传参,数组(也是指针的方式),还有全局变量。
- 对于define的一些问题和关于exit函数的使用有了更深的理解:OVERFLOW在vs2022编译器内部已经定义过了,自己再定义就重定义了; 但是Dev C++中没有定义过。(OVERFLOW 3, UNDERFLOW 4)exit()的返回值主要就是这些预定义的宏,用来帮助父程序提供信息使用的,但是本次实验并不涉及到父程序这种结构,因此不需要为exit函数提供返回值。
- 基本掌握了链表的知识点,熟练掌握了链表的增、删、查:但链表的简单八股文基本能够熟练操作了,但是针对每个题目而异的部分还需要反复思考,不能很快写出。
- 对于用户交互界面的设计更加友好:oj风格的输入输出便于机器检查,但不利于自己和他人阅读,在和同学的交流过程中需要增加一些交互式的设计包括一些提示,有助于下次阅读代码时能够快速理解
- 对于边界的调试更加细致:在实际写代码的时候链表中间的步骤本身不是难点,但是在初始化第一位元素和检验边界条件的时候往往有很多的bug。这时可以使用debug对一些函数里的元素进行观测。另一方面也可以通过“打印”的方式来检验。在进行实验构成中,适当使用一些printf函数打印一些汉字提示帮助理解调试过程中遇到的问题。
- 关于不同编译器的调试,并不是越新的编译器越好用,有的时候老一些的编译器因为没那么多新特性所以调试起来反而给出的错误提示更准确。#define a 10 //结束无':',在vs2022种编译给我的报错一直纠结在函数中的宏定义,但是切换Dev C++很直接的告诉我多写了分号。
- 对于编译器本身有了更好的理解:在实验过程中尝试了多种编译器,对包括不同版本dev c++会遇到的因版本不同而产生的bug有了一定的理解,发现了一些vs2022中的新的特性(主要体现在更加严格的纠错机制,尤其是相比于devc)。
- 忘记free导致指针重复写入出现报错
- 注意代码健壮性,把一些由于用户输入不当产生的错误做好提前的预防和解决措施(包括用户输入选项时候输入的大小写同时设置提高使用效果
- 留意检验栈满了的情况(尤其是判断是否增量能够反复增加而不是只启用一次导致栈又满了而不能再次扩容)。