数据结构:栈的应用——迷宫求解、表达式求值(C++实现)

这里是闲话:上一篇文章(详见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类型计算如下:

显然,第一种存储方法在一定范围内的整数运算中可行,但面对小数和超过一定范围的整数运算中失效;

第二种存储方法可以计算任意整数间的表达式求值。

经检验,实际结果符合理论。

  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值