栈的应用——括号匹配、表达式求值、递归

文章详细介绍了栈在括号匹配中的应用,通过逻辑分析和代码实现展示了如何检查括号序列的正确性。接着讨论了栈在表达式求值中的作用,特别是将中缀表达式转换为后缀表达式并计算结果。最后,阐述了栈在递归算法中的应用,以斐波那契数列为例子,同时提到了递归的优缺点以及如何用栈转换为非递归算法。
摘要由CSDN通过智能技术生成

一、栈在括号匹配中的应用

括号匹配,顾名思义。若括号按照正确的格式嵌套,则可正确匹配,例如([]),反之若括号按照不正确的格式嵌套,则不可正确匹配,例如([))。

逻辑实现

考虑下列括号序列:
括号匹配题目
分析该括号序列是否可正确匹配的流程如下:
1、接收第1个括号"[“后,期待与之匹配的第8个括号”]“出现
2、接收第2个括号”(“,此时第1个括号”[“暂时放在一边,期待与之匹配的第7个括号”)“出现
3、接收第3个括号”[“,此时第2个括号”(“暂时放在一边,期待与之匹配的第4个括号”]“出现
4、接收第4个括号”]",第3个括号的期待得到满足等待第2个括号的期待出现
5、以此类推

代码实现

算法思想
1、初始时设置一个空栈,顺序读入等待判断的括号序列
2、若读入的是右括号,有两种情况:第一种情况是使置于栈顶的最急迫的期待得以满足,第二种情况为不合法(括号序列不匹配,退出程序)
3、若读入的是左括号,则作为一个急迫的期待压入栈中(后压入的期待比先压入的期待更加急迫)
4、若算法结束时栈为空,则匹配成功,否则,匹配失败

代码如下

int Parentheses_match(char Arr,int n){//要判断的括号序列存储在数组Arr[n]中
	SqStack S;
	InitStack(&S);//初始化栈
	int i=0;
	for(i=0;i<n;i++){//读取括号序列
		if(Arr[i]=='('||Arr[i]=='[') Push(&S,Arr[i]);//若读入的是左括号,入栈
		else if(Arr[i]==')'||Arr[i]==']'){//若读入的是右括号
			char x;
			GetTop(S,&x);//读栈顶元素,并用x返回栈顶元素
			
			//判断右括号是否和栈顶的左括号匹配
			if((x=='('&&Arr[i]==')')||(x=='['&&Arr[i]==']')){
				Pop(&S,&x);//弹出栈顶元素,等待下个期待被满足
			};//满足左括号的期待
			else return 0;//右括号未和最近的左括号配对,序列不合法
		}
	}
	if(StackEmpty(S)) return 1;//读取完括号序列后栈为空,序列合法
	else return 0;
}

其中用到的栈的基本操作可见文章操作受限的线性表——栈

二、栈在表达式求值中的应用

我们平时见到的表达式都是从左往后理解的,例如a+b=c、a/b=c等,我们可以称这种阅读方式为中缀表达式(即运算符在两个运算数中间)。相对应的是后缀表达式(即运算符在两个运算数之后)

手算实现

例如,中缀表达式 A + B ∗ ( C − D ) − E / F A+B*(C-D)-E/F A+B(CD)E/F对应的后缀表达式为 A B C D − ∗ + E F / − ABCD-*+EF/- ABCD+EF/中缀表达式转换为后缀表达式的过程如下图所示(按照运算符的运算顺序进行转换):
中缀表达式转后缀表达式
需要注意的是,中缀表达式转化为后缀表达式的结果不唯一,例如下图也是一种转化方式。这种不唯一性来源于某些运算的优先级相同,例如’+‘和’-‘,’*‘和’/'。我们在进行中缀表达式和后缀表达式的转换时,往往采用上图的转换原则——优先级相同的运算符从左到右进行编号
中缀表达式转后缀表达式

代码实现

算法思想:
1、顺序扫描表达式的每一项,根据类型做如下操作
2、若该项是操作数,则将其压入栈中
3、若该项是操作符(简写为op),则连续从栈中退出两个操作数Y和X,形成运算指令XY,并将得到的结果重新压入栈中
4、当表达式的所有项都扫描处理完后,栈顶存放的就是最后的计算结果

代码如下

int Expression_evaluation(char Arr,int n){//表达式存储在Arr数组中
	SqStack S;
	InitStack(&S);//初始化栈
	int i=0;
	for(i=0;i<n;i++){//读取表达式
		if(Arr[i]>=48&&Arr[i]<=57) Push(&S,Arr[i]);//读取的是操作数,入栈
		else{//读取的是操作符,出栈两个操作数进行运算
			int x;
			int y;
			if(Arr[i]==42){//如果是乘法运算
				Pop(&S,&x);
				Pop(&S,&y);
				x=x*y;
				Push(&S,x);
			}
			else if(Arr[i]==47){//如果是除法运算
				Pop(&S,&x);
				Pop(&S,&y);
				x=x/y;
				Push(&S,x);
			}
			else if(Arr[i]==43){//如果是加法运算
				Pop(&S,&x);
				Pop(&S,&y);
				x=x+y;
				Push(&S,x);
			}
			else if(Arr[i]==45){//如果是减法运算
				Pop(&S,&x);
				Pop(&S,&y);
				x=x-y;
				Push(&S,x);
			}
			else return -10000;//返回-10000表示表达式格式不正确
		}
	}
	int answer=GetTop(S,&x);//栈顶元素为计算结果
	return answer;
}

三、栈在递归中的应用

递归可以理解为一个函数/过程/数据结构的定义中又应用了它自身,则这个函数/过程/数据结构是递归定义的,简称递归。

递归模型必须满足两个条件,第一通过循环体(递归表达式)实现递归,第二递归要有出口(结束条件)

一个典型的递归的例子是斐波那契数列
斐波那契数列
代码实现如下:

int Fib(int n){
	if(n==0) return 0;
	else if(n==1) return 1;
	else return Fib(n-1)+Fib(n-2);//递归表达式
}

在递归调用的过程中,系统为每一层的返回点、局部变量、传入实参等开辟了递归工作栈来进行数据存储(函数调用时,需要用一个栈存储调用的返回地址、实参和局部变量等)。若递归的次数过多,容易造成栈溢出。

递归的有点事代码简单且容易理解,但效率不高,因为其包含很多重复的计算,例如在计算Fib(5)时,要先计算Fib(4)和Fib(3),在计算Fib(4)时又要计算Fib(3)和Fib(2)… …一共需要计算2次Fib(3)、3次Fib(2),5次Fib(1)和3次Fib(0)。

逻辑实现

如果想把递归算法转换为非递归算法,就需要借助栈来实现。例如斐波那契数列,在每一次计算时,都从栈顶读取(注意使读值不是出栈)两个元素相加再入栈,即可得到每一个斐波那契数列。

代码实现

int Fib_stack(int n){//计算Fib(n)
	if(n=0) return 0;//Fib(0)
	else if(n=1) return 1;//Fib(1)
	else{//Fib(n)	n>1
		SqStack S;
		InitStack(&S);//初始化栈
		int F0=0;
		int F1=1;
		
		Push(&S,F0);//Fib(0)入栈
		Push(&S,F1);//Fib(1)入栈
		int i=0;
		int Fi;
		int Fj;
		int Fk;
		for(i=2;i<=n;i++){//非递归的计算Fib(i)
			GetTop(S,&Fj);
			GetSecond(S,&Fk);
			Fi=Fj+Fk;
			Push(&S,Fi);
		}
	}
	int Fn;
	GetTop(S,&Fn);
	if(i==n) return Fn;//返回Fib(n)
}

另有文章队列的应用——层次遍历、计算机系统见链接队列的应用——层次遍历、计算机系统

本文内容为个人学习总结所得,如有问题欢迎评论区提出。

  • 5
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

HePingxxb

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值