实验一
简单计算器的设计
输入:不含变量的数学表达式的中缀形式,可以接受的操作符包括
+ - * / % ( )
。
输出: 如果表达式正确,则输出表达式的结果,如果表达式非法,则输出错误信息。 同时输出堆栈的状态变化过程。
1.1 需求分析
- 输入的形式和输入值的范围:输入不含变量的数学表达式的中缀形式,可以接受的操作符包括
+ - * / % ( )
。 - 输出的形式:如果表达式正确,则输出表达式的结果,如果表达式非法,则输出错误信息。 同时输出堆栈的状态变化过程。
- 程序所能达到的功能:
A. 输入正确的中缀表达式输出表达式结果
B. 输入不合法的中缀表达式输出“错误”
C. 输出堆栈的状态变化过程
1.2 概要设计
为了实现上述程序功能,需要定义两个栈。
- OPTR栈(Operator stack),用于寄存运算符
- OPND栈(Operand stack),用于寄存操作数或运算结果。
还需要定义栈的抽象数据类型:
ADT Sqstack {
数据对象:D={ai|ai∈IntegerSet,i=0,1,2,…,n,n≥0}
数据关系:R={<ai,ai+1>|ai,ai+1 ∈D}
基本操作:
InitStack (&S) 构造一个空的栈 S.
Push(&S,e)
初始条件:栈 S 已存在
操作结果:将元素 e 压入栈 S
Pop(&S,&e)
初始条件:栈 S 已存在
操作结果:将 S 栈顶元素出栈置入 e 中返回
StackEmpty(S)
初始条件:栈 S 已存在
操作结果:栈空返回true,不空返回false
GetTop(S,&e)
初始条件:栈 S 已存在
操作结果:将 S 栈顶元素的值放入 e 中返回
PrintStack(&S)
初始条件:栈 S 已存在
操作结果:从栈顶到栈底输出栈
}
1.3 基本思想
- 置操作数栈为空栈,表达式起始符
#
为运算符的栈底元素 - 依此读入表达式中的每个字符,若是操作数则入OPND栈,若是运算符则和OPTR栈的栈顶运算符进行比较优先权后进行相应操作,直至整个表达式求值完毕(即OPTR的栈顶元素和当前读入的字符均为
#
)
1.4 函数
本程序包含5个函数:
- 主函数main()
- 优先关系函数Precede()
- 单次计算函数Operate()
- 报错函数Error()
- 输出结果函数outcome()
各函数间关系如下:
1.5 详细设计
//-------------------------------宏定义------------------------------
#define OK 1
#define ERROR 0
#define OVERFLOW -2
#define STACK_INIT_SIZE 100
#define STACKINCREMENT 10
#define MAXSIZE 110
//-------------------------------栈的操作----------------------------
typedef int Status;
typedef int S1elemtype;
typedef char S2elemtype;
typedef struct {
S1elemtype* base;
S1elemtype* top;
int stacksize;
}Sqstack1;
typedef struct {
S2elemtype* base;
S2elemtype* top;
int stacksize;
}Sqstack2;
Status InitStack1(Sqstack1& S) {
S.base = (S1elemtype*)malloc(STACK_INIT_SIZE * sizeof(S1elemtype));
if (!S.base) exit(OVERFLOW);
S.top = S.base;
S.stacksize = STACK_INIT_SIZE;
return OK;
}
Status InitStack2(Sqstack2& S) {
S.base = (S2elemtype*)malloc(STACK_INIT_SIZE * sizeof(S2elemtype));
if (!S.base) exit(OVERFLOW);
S.top = S.base;
S.stacksize = STACK_INIT_SIZE;
return OK;
}
Status StackEmpty1(Sqstack1 S) {
if (S.top == S.base) return 1;
else return 0;
}
Status StackEmpty2(Sqstack2 S) {
if (S.top == S.base) return 1;
else return 0;
}
Status GetTop1(Sqstack1 S, S1elemtype& e) {
if (S.top == S.base)
return ERROR;
e = *(S.top - 1);
return OK;
}
Status GetTop2(Sqstack2 S, S2elemtype& e) {
if (S.top == S.base)
return ERROR;
e = *(S.top - 1);
return OK;
}
Status Push1(Sqstack1& S, S1elemtype e) {
if (S.top - S.base >= S.stacksize) {
S1elemtype* tmp = (S1elemtype*)realloc(S.base, (S.stacksize + STACKINCREMENT) * sizeof(S1elemtype));
if (!tmp) exit(OVERFLOW);
S.base = tmp;
S.top = S.base + S.stacksize;
S.stacksize += STACKINCREMENT;
}
*S.top = e;
S.top++;
return OK;
}
Status Push2(Sqstack2& S, S2elemtype e) {
if (S.top - S.base >= S.stacksize) {
S2elemtype* tmp = (S2elemtype*)realloc(S.base, (S.stacksize + STACKINCREMENT) * sizeof(S2elemtype));
if (!tmp) exit(OVERFLOW);
S.base = tmp;
S.top = S.base + S.stacksize;
S.stacksize += STACKINCREMENT;
}
*S.top = e;
S.top++;
return OK;
}
Status Pop1(Sqstack1& S, S1elemtype& e) {
if (S.top == S.base) return ERROR;
S.top--;
e = *(S.top);
return OK;
}
Status Pop2(Sqstack2& S, S2elemtype& e) {
if (S.top == S.base) return ERROR;
S.top--;
e = *(S.top);
return OK;
}
void PrintStack1(Sqstack1 S) {
S1elemtype* t;
t = S.base;
while (t != S.top && t + 1 != S.top) {
printf("%d ", *t);
t++;
}
printf("%d\n", *t);
}
void PrintStack2(Sqstack2 S) {
S2elemtype* t;
t = S.base;
while (t + 1 != S.top) {
printf("%c ", *t);
t++;
}
printf("%c", *t);
}
//---------------------------------主程序----------------------------
char priority[7][7] = {
{'>','>','<','<','<','>','>'},
{'>','>','<','<','<','>','>'},
{'>','>','>','>','<','>','>'},
{'>','>','>','>','<','>','>'},
{'<','<','<','<','<','=','0'}, // 此行"("=")"表示左右括号相遇,括号内运算已完成
{'>','>','>','>','0','>','>'}, // "=" 表示整个表达式求值完毕
{'<','<','<','<','<','0','='} // "0"表示不可能出现这种情况 ( 语法错误 )
};
//判断运算符栈的栈顶运算符 a1 与读入运算符 a2 之间的优先关系
char Procede(char a, char b) {
// 建立 pre[][] 到 运算符间的映射关系
int i = 0, j = 0;
switch (a) {
case'+':i = 0; break;
case'-':i = 1; break;
case'*':i = 2; break;
case'/':i = 3; break;
case'(':i = 4; break;
case')':i = 5; break;
case'#':i = 6; break; // # 是表达式的结束符
}
switch (b) {
case'+':j = 0; break;
case'-':j = 1; break;
case'*':j = 2; break;
case'/':j = 3; break;
case'(':j = 4; break;
case')':j = 5; break;
case'#':j = 6; break;
}
return priority[i][j];
}
//判断错误
void error() {
printf("\n错误\n");
exit(-1);
}
//单步计算
int Operate(int m, int n, char x) {
if (x == '+')
return m + n;
else if (x == '-')
return n - m;
else if (x == '*')
return m * n;
else if (x == '/')
return n / m;
else {
error();
return -1;
}
}
//计算表达式
void outcome() {
Sqstack1 OPND; // Operand stack
Sqstack2 OPTR; // Operator stack
InitStack1(OPND);
InitStack2(OPTR);
//预处理表达式
char ss[2] = "#";//尾部有\0
char s[MAXSIZE];
printf("请输入中缀表达式\n\n");
scanf_s("%s", s, MAXSIZE);
printf("\n");
strcat_s(s, sizeof(s), ss);// 运算式尾部加 "#"——结束运算符
Push2(OPTR, '#');
//依次处理字符
char c = s[0], topchar, tmp;
int k = 1, topnum1, topnum2;
GetTop2(OPTR, topchar);
while (c != '#' || topchar != '#') { //表达式未读完或者运算未完
//处理数字
if (c >= '0' && c <= '9') {
int y = 0;
while (c >= '0' && c <= '9') { // 读入连续的数字
y = y * 10 + (c - '0');
c = s[k++];
if (c == '\0') error();
}
Push1(OPND,y); // 把读进的数字入数字栈
}
//处理字符
else {
switch (Procede(topchar, c)) {
//栈顶元素优先权低——入栈
case'<':
Push2(OPTR,c);
c = s[k++];
if (c == '\0') error();
break;
//括号——脱括号并读入下一个字符
case'=':
Pop2(OPTR,tmp);
c = s[k++];
if (c == '\0') error();
break;
//栈顶元素优先权高——退栈并将运算结果入栈
case'>':
{
if (topchar == '#') error();
Pop2(OPTR,tmp);
if (StackEmpty1(OPND)) error();
Pop1(OPND, topnum1);
if (StackEmpty1(OPND)) error();
Pop1(OPND, topnum2);
Push1(OPND,Operate(topnum1, topnum2, tmp));
break;
}
default:
error();
}
}
GetTop2(OPTR, topchar);
printf("操作符栈:");
PrintStack2(OPTR);
printf("\t数字栈:");
PrintStack1(OPND);
}
int e;
GetTop1(OPND, e);
printf("\n最终结果为:%d\n", e);
}
int main()
{
outcome();
return 0;
}
1.6 使用说明
程序名为 计数器.exe,运行环境为 Windows。程序执行后显示请输入中缀表达式
,在光标后输入中缀表达式,回车即可输出表达式栈的变化及表达式结果/错误。
1.7 心得体会
这个实验用到了堆的数据结构,主要利用了两个栈,一个数字栈,一个字符栈,将中缀表达式的每一个字符依次入栈后依据优先关系进行计算。
其中重要的一点是算法的健壮性,对于不符合要求的表达式要进行判错处理,输出错误。其次是利用了一个二维数组进行字符优先级的判别,将字符转化成数字利用二维数组进行判别>、<、=、与脱括号处理。
实验二
应用等价类生成随机迷宫并寻找迷宫路径
构造一个 N × N N×N N×N的从左上角到右下角只有一条路径的随机迷宫,然后在这一迷宫上寻找迷宫路径。该设计共包含如下四个部分:
① 等价类数据结构的设计和实现
② 构建随机迷宫
③ 寻找迷宫路径
④ 将迷宫和路径用图形方式画出
用图形方式将上述算法获得的随机迷宫及其上的最短路径画出。用线段来表示迷宫中的墙,用在每个方格中心的点来表示路径。
2.1 需求分析
本程序用 VS 编写,生成随机迷宫并寻找迷宫路径。
- 输入的形式和输入值的范围:无输入
- 输出的形式:输出生成的随机迷宫及路径
- 程序所能达到的功能:
A. 构建随机迷宫
B. 寻找迷宫路径
C. 以图形的方式画出路径
2.2 概要设计
定义栈的抽象数据类型:
ADT Sqstack {
数据对象:D={ai|ai∈IntegerSet,i=0,1,2,…,n,n≥0}
数据关系:R={<ai,ai+1>|ai,ai+1 ∈D}
基本操作:
InitStack (&S)
操作结果:构造一个空的栈 S.
Push(&S,e)
初始条件:栈 S 已存在
操作结果:将元素 e 压入栈 S
Pop(&S,&e)
初始条件:栈 S 已存在
操作结果:将 S 栈顶元素出栈置入 e 中返回
StackEmpty(S)
初始条件:栈 S 已存在
操作结果:栈空返回true,不空返回false
GetTop(S,&e)
初始条件:栈 S 已存在
操作结果:将 S 栈顶元素的值放入 e 中返回
PrintStack(&S)
初始条件:栈 S 已存在
操作结果:从栈顶到栈底输出栈
}
2.3 基本思想
- 等价类基本思想: N × N N×N N×N 迷宫的 N 2 N^2 N2 个方格 0 , 1 , … , N n 2 − 1 0,1,…,N_{n^2-1} 0,1,…,Nn2−1 初始时每个方格自己成为一个等价类,即 0 , 1 , … , N n 2 − 1 {0},{1},…,N_{n^2-1} 0,1,…,Nn2−1。
- 生成随机迷宫的方法是随机选择一个内部墙(连接两个相邻方格的墙),如果该内部墙关联的两个相邻的方格属于不同的等价类就将该墙除去,在除去该墙的同时将这两个等价类合并。直到出口与入口的方格都在一个等价类中,就完成了随机迷宫的生成。
- 寻找迷宫路径基本思想:
① 入口区域入栈
② 判断栈顶区域是不是出口
③ 如果是则输出栈内全部元素,这就是一个从入口到出口的逆序路线
④ 如果不是则探索栈顶区域的四个方向
⑤ 如果这四个方向均不能走(相邻的区域是墙、探索过不能同行、)已经在路径中,则把这块区域从路经栈中删除,然后执行步骤2
⑥ 如果这块区域的相邻区域存在可通行的区域,则将第一个可通行的区域入栈,然后执行步骤2
2.4 函数
本程序包含四个部分,结构如下:
2.5 详细设计
//----------------------------------宏定义----------------------------
#define INITSIZE 100
#define STACKINCREMENT 10
#define OVERFLOW -2
#define OK 1
#define NO 0
#define TRUE 0
#define FALSE 0
#define MAXLEN 100
typedef int Status;
//坐标(x,y)
typedef struct {
int x;
int y;
}PosType;
//栈中元素类型
typedef struct {
int ord;
PosType seat;
int dir;
}StackType;
//栈
typedef struct {
StackType* base;
StackType* top;
int stacksize;
}Stack;
//---------------------------------栈的数据结构--------------------------
Status InitStack(Stack& s) {
s.base = (StackType*)malloc(sizeof(StackType) * INITSIZE);
if (!s.base) exit(OVERFLOW);
s.top = s.base;
s.stacksize = INITSIZE;
return OK;
}
Status StackEmpty(Stack& s) {
if (s.top == s.base) return 1;
else return 0;
}
Status GetTop(Stack s, StackType& e) {
if (StackEmpty(s))
return 0;
e = *(s.top - 1);
return OK;
}
Status Push(Stack& s, StackType e) {
if (s.top - s.base >= s.stacksize) {
StackType* tmp = (StackType*)realloc(s.base, (s.stacksize + STACKINCREMENT) * sizeof(StackType));
if (!tmp) exit(OVERFLOW);
s.base = tmp;
s.top = s.base + s.stacksize;
s.stacksize += STACKINCREMENT;
}
*s.top = e;
s.top++;
return OK;
}
Status Pop(Stack& s, StackType& e) {
if (!StackEmpty(s)) {
e = *s.top;
s.top--;
return OK;
}
else return NO;
}
Status DestroyStack(Stack& s) {
free(s.base);
return OK;
}
void OverturnStack(Stack S, Stack& T) {
StackType e;
do {
Pop(S, e); Push(T, e);
} while (S.base != S.top);
return;
}
//----------------------------利用等价类实现随机迷宫----------------
//迷宫(r行c列的矩阵)
typedef struct {
int r;
int c;
char addr[MAXLEN][MAXLEN];
}MazeType;
//初始化迷宫(全为空格)
void InitMaze(MazeType& maze) {
maze.r = 0;
maze.c = 0;
for (int i = 0; i < MAXLEN; i++) {
for (int j = 0; j < MAXLEN; j++) {
maze.addr[i][j] = ' ';
}
}
}
//等价类定义
int eq[MAXLEN * MAXLEN];//用等价类记录祖先,初始化为-1
int Find(int x) {
if (eq[x] > 0) return Find(eq[x]);
else return x;
}
void Union(int x,int y) {
eq[y] = x;
}
//生成随机迷宫(不可通为#可通为空格)
Status CreatMaze(MazeType& maze, PosType& start, PosType& end) {
int i, j;
int m = 0, n = 0;
srand((unsigned)time(NULL));
//1.随机确定行列数,迷宫的行和列的范围为[3,25]
printf("随机生成迷宫的行数和列数(包含外墙):");
maze.r = rand() % 23 + 3; maze.c = rand() % 23 + 3;
printf("行数:%d,列数:%d\n", maze.r, maze.c);
//2.画出围墙
for (i = 0; i < maze.c; i++) {
maze.addr[0][i] = '#'; //给出上下围墙
maze.addr[maze.c - 1][i] = '#';
}
for (i = 1; i < maze.r - 1; i++) {
maze.addr[i][0] = '#'; //给出左右围墙
maze.addr[i][maze.r - 1] = '#';
}
//3.随机生成的迷宫入口:入口的r只能在[1,(maze.r-2)]之间,c只能为0
printf("\n随机生成的迷宫入口:");
start.x = rand() % (maze.r - 2) + 1;
start.y = 0;
printf("(%d,%d)\n", start.x+1, start.y+1);
maze.addr[start.x][start.y] = ' ';
//4.随机生成的迷宫出口:出口的r只能在[1,(maze.r-2)]之间,c只能为maze.c-1
printf_s("\n随机生成的迷宫出口:");
end.x = rand() % (maze.r - 2) + 1;;
end.y = maze.c - 1;
printf("(%d,%d)\n", end.x+1, end.y+1);
maze.addr[end.x][end.y] = ' ';
//5.用等价类实现迷宫路径
int num = 1;
for (int i = 0; i < maze.r; i++) {
for (int j = 0; j < maze.c; j++) {
num = i * maze.r + j + 1;
eq[num] = -1;
}
}
int numstart = (start.x) * maze.c + start.y + 1;//注意坐标与序号之间的关系
int numend = (end.x) * maze.c + end.y + 1;
while (Find(numstart) != Find(numend)) {
int num1 = 1, num2 = 1, dir;
while (num1 <= maze.c * 2 || num1 > (maze.r - 2) * maze.c || num1 % maze.c <= 2 || num1 % maze.c == maze.c - 1) {
num1 = rand() % (maze.r * maze.c) + 1;
}
dir = rand() % 4;
switch (dir) {
case 0:
num2 = num1 + 1;
break;
case 1:
num2 = num1 - maze.c;
break;
case 2:
num2 = num1 - 1;
break;
case 3:
num2 = num1 + maze.c;
break;
}
if (Find(num1) != Find(num2)) Union(num1,num2);
}
//遍历整个迷宫,与start、end祖先相同的为' ' 不相同为'#'
for (int i = 0; i < maze.r; i++) {
for (int j = 0; j < maze.c; j++) {
int num = i * maze.r + j + 1;
if (Find(1) == Find(num)) maze.addr[i][j] = ' ';
}
}
return OK;
}
//---------------------------寻找迷宫路径----------------------------
//判断当前位置可否通过,可通过返回true
Status WhetherPass(MazeType& maze, PosType curpos) {
if (maze.addr[curpos.x][curpos.y] == ' ') return TRUE;
else return FALSE;
}
//若该通道块走过并且可通,就记录下来
void FootPrint(MazeType& maze, PosType curpos) {
maze.addr[curpos.x][curpos.y] = 'o';
return;
}
//指示并返回下一位置的坐标
PosType NextPos(PosType& curpos, int i) {
switch (i) { //向各个方向前进一个单位,并返回位置值
case 1:curpos.y += 1; break; //向东前进一个单位
case 2:curpos.x += 1; break; //向南前进一个单位
case 3:curpos.y -= 1; break; //向西前进一个单位
case 4:curpos.x -= 1; break; //向北前进一个单位
default:exit(-1);
}
return curpos;
}
//曾经走过,但不是通路,就标记
void MarkPrint(MazeType& maze, PosType curpos) {
maze.addr[curpos.x][curpos.y] = '@';
return;
}
//若迷宫存在一条通路,则求出一条通路存放到栈中
Status MazePath(MazeType& maze, PosType start, PosType end, Stack& S) {
PosType curpos; //记录当前元素位置
int curstep = 1, flag = 0; //记录能通行的通道块在栈中的路径顺序
StackType e; //记录可压住栈中的当前通道块的信息
curpos = start;
do {
if (WhetherPass(maze, curpos)) { //如果该路径块可通行
FootPrint(maze, curpos); //记录下通行过的痕迹
e.ord = curstep; //将栈中的顺序给e的ord属性
e.seat.y = curpos.y; //将该通道块的位置给e的curpos属性
e.seat.x = curpos.x;
e.dir = 1; //将初始方向设为向东(1)
Push(S, e); //将该可通的通道块压入栈
if (curpos.x == end.x && curpos.y == end.y) {
//如果遇到了出口则退出
return TRUE;
}
else { //如果没有遇到出口
curpos = NextPos(curpos, 1); //那么寻找下一个位置
curstep++; //计数器加1
}
}
else { //如果该路径块不通
if (!StackEmpty(S)) { //如果栈不为空
Pop(S, e);
//将栈顶元素出栈也就是退回上一个元素,将e返回出来判断是否再次进行方向的改变,也就是将栈顶元素进行悬挂操作
while (e.dir == 4 && !StackEmpty(S)) {
//当四个方向都不通并且栈b不为空时
MarkPrint(maze, e.seat); //记录该通道块不通
Pop(S, e);
//将栈顶元素出栈也就是退回上一个位置
}
if (e.dir < 4) { //当四个方向没有尝试完时
e.dir++; //换一个方向
Push(S, e); //将换方向之后的位置压栈
curpos = NextPos(e.seat, e.dir); //当前位置下移
}
}
}
} while (!StackEmpty(S));
return false;
}
//-------------------将迷宫和路径用图形方式画出------------------------
//若迷宫存在一条通路,则求出一条通路存放到栈中
void PrintStack(Stack S) {
int flag = 0;
printf("迷宫中的一条通路如下:\n");
do {
S.top--; //将栈顶指针指向想要输出的栈顶元素
if (flag % 5 == 0) printf("\n");
switch (S.top->dir) {
case 1:
printf("(%d,%d)向东->", S.top->seat.x, S.top->seat.y);
flag++; break;
case 2:
printf("(%d,%d)向南->", S.top->seat.x, S.top->seat.y);
flag++; break;
case 3:
printf("(%d,%d)向西->", S.top->seat.x, S.top->seat.y);
flag++; break;
case 4:
printf("(%d,%d)向北->", S.top->seat.x, S.top->seat.y);
flag++; break;
}
} while (S.base != S.top);
printf("结束\n");
return;
}
//将标记路径信息输出到终端显示屏
void PrintMaze(MazeType& maze, PosType start, PosType end) {
int i, j;
printf(" "); //10个空格
for (i = 0; i < maze.c; i++) printf("%2d", i);
printf("\n");
for (i = 0; i < maze.r; i++) {
if (i == start.x - 1) printf("entrance%2d", start.x - 1);
else if (i == start.x) printf(" ->%2d", start.x);
else printf("%10d", i);
for (j = 0; j < maze.c; j++) {
printf("%2c", maze.addr[i][j]);
if (i == end.x - 1 && j == end.y) printf(" exit");
else if (i == end.x && j == end.y) printf("<-");
}
printf("\n");
}
return;
}
int main()
{
MazeType maze;
PosType start, end;
start.x = start.y = end.x = end.y = 0;
Stack S,T;
InitStack(S);
InitStack(T);
InitMaze(maze);
printf("----------------创建迷宫----------------\n");
if (!CreatMaze(maze, start, end)) {
printf("初始化失败!!!\n");
exit(OVERFLOW);
}
printf("\n");
printf("----------------原始迷宫模型图----------------\n");
PrintMaze(maze, start, end);
printf("\n");
printf("----------------走出迷宫的路径----------------\n");
OverturnStack(S, T); PrintStack(T); DestroyStack(S);
printf("--------------迷宫路径模型图(o代表一条通路)--------------\n");
PrintMaze(maze, start, end);
return 0;
}
2.6 使用说明
程序名为 迷宫.exe,运行环境为 Windows。程序执行后显示
----初始化的迷宫----
----原始迷宫模型图----
----走出迷宫的路径----
----迷宫路径模型图----
2.7 心得体会
本次实验主要学习了如何使用等价类生成一个迷宫,等价类的思想与图的数据结构中Flord算法求最短路径思想相同,首先初始化所有点的祖先为-1表示自己为根节点,之后不断地找两个相邻的点进行union操作,使得两个相邻点的祖先相同,表示两点可通。寻找祖先使用find函数进行递归调用直至等价类为-1表示该点的祖先。
其次是应用了栈的数据结构实现了迷宫路线的寻找,通过不断地压栈出栈直至从入口到出口,是栈数据结构的典型应用。