这里是闲话:上一篇文章(详见https://blog.csdn.net/jokerCe/article/details/114141469),已经实现了栈的三种简单应用,接下来,本文将会实现另外两种复杂一点点的应用——迷宫求解、表达式求值,栈的LIFO特性仍然是我们应该注意的点,以下内容仅供参考~
这里是目录:
一、迷宫求解
1、算法思想:
①关键:深度优先遍历算法
寻找迷宫路径问题是栈的一个经典应用,由于计算机解迷宫时用的是“穷举求解”的方法,即从入口开始,寻找可通的路径并前进,若当前路径不可通则退回,并重复寻找,直至找到出口。寻找可通的路径并前进,这与压栈操作对应;退回不可通的路径,这与弹栈操作对应,故我们可以使用栈的数据结构来解决这个问题。这个思想应用到深度优先遍历算法(DFS),以下是DFS的基本思路:
①访问顶点v;
②依次从v的未被访问的邻接点出发,对图进行深度优先遍历;直至图中和v有路径相通的顶点都被访问;
③若此时图中尚有顶点未被访问,则从一个未被访问的顶点出发,重新进行深度优先遍历,直到图中所有顶点均被访问过为止。
因为迷宫问题仅要求我们找到出口,所以我们无需遍历所有点,只需要在遍历过程中找到点即可跳出。
②判断路径是否可通的算法
在DFS算法中我们需要判断路径是否可通,而该题的迷宫障碍用数字8表示,通道用数字0表示,启发我们可以用别的数字表示其他的状态。我在DFS算法中规定遍历的顺序为东→南→西→北,用1表示东可通,2表示南可通,3表示西可通,4表示北可通,5表示已被走过但不可通,判断是否可通的条件是当前位置是否小于4(若等于4,证明已经向北走过且回溯至此)且下个位置是否为0(若不为0,则问题非简单路径问题)
2、代码实现:
①迷宫求解算法(本质为深度优先遍历算法)
//若迷宫中存在从入口start到出口end的通道,则求得一条存放在栈中,并返回OK,否则返回ERROR
void MazePath(PosType start, PosType end) {
SqStack S;
InitStack(S);
PosType curpos = start; //设置“入口位置”为“当前位置”
PosType nextpos, temppos;
do {//深度优先遍历
if (GetNext(curpos, nextpos)) { //若当前位置可通,设为走过的通道块
Push(S, curpos);
if (ComparePos(nextpos, end)) { //若到达终点,跳出循环
Push(S, nextpos); maze[nextpos.x][nextpos.y]=1; break;//终点的位置设为1~4皆可,此处设为1
}
curpos = nextpos;//更新当前位置
}
else //若当前位置不可通
Pop(S, curpos); //退回前一个位置,即出栈
} while (!StackEmpty(S));
if(StackEmpty(S))
cout << "走不出这个迷宫了555" << endl;
else PrintPath (S);
}
②判断路径是否可通的算法
//判断下个位置是否可通(前提是:走过的通道块视为不可通,否则所求路径不是简单路径)
bool GetNext(PosType &curpos, PosType &nextpos) {
if (maze[curpos.x][curpos.y] == 0 && maze[curpos.x][curpos.y + 1] == 0) {
nextpos.x = curpos.x; nextpos.y = curpos.y + 1; maze[curpos.x][curpos.y] = 1;//东可通,则原位置赋值为1
}
else if (maze[curpos.x][curpos.y] <= 1 && maze[curpos.x + 1][curpos.y] == 0) {
nextpos.x = curpos.x + 1; nextpos.y = curpos.y; maze[curpos.x][curpos.y] = 2;//南可通,则原位置赋值为2
}
else if (maze[curpos.x][curpos.y] <= 2 && maze[curpos.x][curpos.y - 1] == 0) {
nextpos.x = curpos.x; nextpos.y = curpos.y - 1; maze[curpos.x][curpos.y] = 3;//西可通,则原位置赋值为3
}
else if (maze[curpos.x][curpos.y] <= 3 && maze[curpos.x - 1][curpos.y] == 0) {
nextpos.x = curpos.x - 1; nextpos.y = curpos.y; maze[curpos.x][curpos.y] = 4;//北可通,则原位置赋值为4
}
else {
maze[curpos.x][curpos.y] = 5;//已走过但不可通,则原位置赋值为5
return false;
}
return true;
}
③辅助功能的代码实现(比较位置+打印迷宫通道结果)
//比较两者位置,相同则返回真
bool ComparePos(PosType a, PosType b) {
if (a.x == b.x && a.y == b.y) return true;
else return false;
}
//打印迷宫通道
Status PrintPath(SqStack S) {
if (StackEmpty(S)) return ERROR;
cout << "出迷宫的路线为:\n" << S << endl;
cout << "\n具体路线如下(.表示路径):" << endl;
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 10; j++) {
switch (maze[i][j]) {
case 1:
case 3:
case 2:
case 4:cout << "."; break;
case 5:
case 0:cout << " "; break;
default:cout << maze[i][j]; break;
}
}
cout << endl;
}
return OK;
}
3、运行结果及检验:
①检验问题:输入迷宫为
{8,8,8,8,8,8,8,8,8,8}
{8,0,0,8,0,0,0,8,0,8}
{8,0,0,8,0,0,0,8,0,8}
{8,0,0,0,0,8,8,0,0,8}
{8,0,8,8,8,0,0,0,0,8}
{8,0,0,0,8,0,0,0,0,8}
{8,0,8,0,0,0,8,0,0,8}
{8,0,8,8,8,0,8,8,8,8}
{8,8,0,0,0,0,0,0,0,8}
{8,8,8,8,8,8,8,8,8,8}
②理论结果:
出迷宫路线为: (1,1) -> (1,2) -> (2,2) -> (3,2) -> (3,1) -> (4,1) -> (5,1) -> (5,2) -> (5,3) -> (6,3) -> (6,4) -> (6,5) -> (7,5) -> (8,5) -> (8,6) -> (8,7) -> (8,8)
③实际结果:(用.表示走的路径)
经检验,实际结果符合理论。
二、表达式求值
1、算法思想:
1)算符优先算法:
表达式求值遵循四则计算的三个原则:
①先乘除,后加减;
②从左算到右;
③先括号内,再括号外。
因此,我们可以根据运算优先关系规定“算符优先法”来解决这个问题。我们可以将算符(运算符+界限符)和操作数分别存入算符栈和操作数栈,算符栈OPER负责寄存算符,操作数栈OPTR负责寄存操作数或运算结果。算法的基本思想是:
①首先设立一个表示优先级的数组,表示算符之间的优先级;
②置操作数栈为空栈,表达式起始符“#”为OPER栈底元素;
③依次读入表达式中每个字符,若是操作数则进OPND栈,若是运算符则和OPER栈栈顶算符比较优先级后做相应求值,然后将对应运算符消除(即将前一个运算符从OPER出栈)+对应结果读入(即将运算结果压入OPTR栈),直至整个表达式求值完毕。(栈顶为#且栈底为#)
2、代码实现:
1)仅使用char类型作为运算符栈和操作数栈的数据类型时
写在前头:因为运算符栈中存储的char类型仅支持1个字节(8位)的存储空间,所以这种方法存储的数据仅支持整数且范围在-176~79(键盘输入的‘0’对应ASCLL码为48,所以范围在-128-48~127-48),超出则计算异常如下:
①算符优先算法的代码实现:
//算术表达式求值的算符优先算法
int EvaluateExpression() {
SqStack OPTR, OPND;//OPTR为运算符栈,OPND为运算数栈
InitStack(OPTR);
InitStack(OPND);
Push(OPTR, '#');
char c = cin.get();
while (!StackEmpty(OPTR)) {
if (!In(c, OP)) {//如果输入的c不是运算符
if (c >= '0' && c <= '9') {//如果c是操作数,则进运算数栈
//此处可读入连续整数数字,但单个数字或结果范围仅限在[-176, 79]
char next = cin.get();//输入下一个字符
int y = c - '0';//将字符化为对应整数进行计算
while (next >= '0' && next <= '9') {//若下一个字符也是数字,继续加
y = y * 10 + (next - '0');
c = next;
next = cin.get();
}
c = y + '0';//将结果储存到当前字符
Push(OPND, c);
c = next;//保存下一个字符(已输入)
}
else {//如果c既不是运算符也不是操作数,提示错误并重新输入
cout << "Wrong Input!" << endl;
exit(-1);
}
}
else{
switch (Precede(Top(OPTR), c)) {
case '<'://栈顶元素优先权更低
Push(OPTR, c);
c = cin.get();
break;
case '>'://栈顶元素优先权更高,退栈并将运算结果入栈
OperandType theta;//当前运算所用运算符theta
OperatorType a, b;//当前运算所用操作数a, b
Pop(OPTR, theta);
Pop(OPND, b);
Pop(OPND, a);
Push(OPND, Operate(a, theta, b));//此处a与b的位置不能调换
break;
case '='://脱去括号并接受下一字符
SElemType temp;
Pop(OPTR, temp);
c = cin.get();
break;
}
}
}
cout << Top(OPND)-'0' << endl;//输出结果
return Top(OPND) - '0';
}
②辅助功能的代码实现:
//判断符号是否为运算符或界限符
bool In(OperandType s, char OP[]) {
for (int i = 0; i < 7; i++) {
if (s == OP[i]) return true;
}
return false;
}
//判断运算符间的优先级
char Precede(OperatorType a, OperatorType b) {
char Priority[7][7] = {
{'>','>','<','<','<','>','>'},
{'>','>','<','<','<','>','>'},
{'>','>','>','>','<','>','>'},
{'>','>','>','>','<','>','>'},
{'<','<','<','<','<','=',' '},
{'>','>','>','>',' ','>','>'},
{'<','<','<','<','<',' ','='},
};
int i, j, flag=0;
for (i = 0; i < 7; i++)
if (a == OP[i]) break;
for (j = 0; j < 7; j++)
if (b == OP[j]) break;
return Priority[i][j];
}
//得到表达式的结果
SElemType Operate(OperandType a, OperatorType theta, OperandType b) {
int a1, b1, result, i=0;
a1 = a - '0';
b1 = b - '0';
switch (theta)
{
case '+':
result = a1 + b1;
break;
case'-':
result = a1 - b1;
break;
case'*':
result = a1 * b1;
break;
case'/':
result = a1 / b1;
break;
default:
break;
}
return result+'0';
}
2)改进后,使用结构体模板作为栈的结构体,char类型作为运算符栈的数据类型、double类型作为操作数栈的数据类型
相比1)中的算法,结构体模板可以让栈存储不同类型的数据元素,计算的范围拓展到任意整数,但小数的输入较繁琐,此处先不实现。
①算符优先算法的代码实现:
//算术表达式求值的算符优先算法
void EvaluateExpression() {
SqStack<OperatorType> OPTR;//OPTR为运算符栈
SqStack<OperandType> OPND;//OPND为操作数栈
InitStack(OPTR);
InitStack(OPND);
Push(OPTR, '#');
char c = cin.get();
while (!StackEmpty(OPTR)) {
if (!In(c, OP)) {//如果输入的c不是运算符
if (c >= '0' && c <= '9') {//如果c是操作数,则进运算数栈
//此处可读入连续整数数字,但单个数字或结果范围仅限在[-176, 79]
char next = cin.get();//输入下一个字符
OperandType y = c - 48;//将字符化为对应整数进行计算
while (next >= '0' && next <= '9') {//若下一个字符也是数字,继续加
y = y * 10 + (next - 48);
next = cin.get();
}
Push(OPND, y);
c = next;//保存下一个字符(已输入)
}
else {//如果c既不是运算符也不是操作数,提示错误并重新输入
cout << "Wrong Input!" << endl;
while (c != '\n') c = cin.get();//清空缓存区字符
ClearStack(OPND);//清空操作数栈
ClearStack(OPTR);//清空运算符栈
return;
}
}
else {
switch (Precede(Top(OPTR), c)) {
case '<'://栈顶元素优先权更低
Push(OPTR, c);
c = cin.get();
break;
case '>'://栈顶元素优先权更高,退栈并将运算结果入栈
OperandType a, b;//当前运算所用运算符theta
OperatorType theta;//当前运算所用操作数a, b
Pop(OPTR, theta);
Pop(OPND, b);
Pop(OPND, a);
Push(OPND, Operate(a, theta, b));//此处a与b的位置不能调换
break;
case '='://脱去括号并接受下一字符
OperatorType temp;
Pop(OPTR, temp);
c = cin.get();
break;
}
}
}
cout << Top(OPND)<< endl;//输出结果
ClearStack(OPND);//清空操作数栈
ClearStack(OPTR);//清空运算符栈
}
②辅助功能的代码实现:此处代码可复用2)②部分
3、运行结果及检验:
①检验问题:10+(2*5)、8/(3+8)*20+7
②理论结果:20、21.5454...
③实际结果:
操作数存储为char类型计算如下:
操作数存储为double类型计算如下:
显然,第一种存储方法在一定范围内的整数运算中可行,但面对小数和超过一定范围的整数运算中失效;
第二种存储方法可以计算任意整数间的表达式求值。
经检验,实际结果符合理论。