数据结构系列--栈的实践
原创 慧子和她的3D 慧子和她的3D 昨天
“ 用栈来解决问题。”C语言中符号的匹配+将中缀表达式转换成后缀表达式
01
—
C语言中符号的匹配
在C语言中一些符号都是成对匹配出现
#include <stdio.h>
int main()
{
int a[5][5];
int (*p)[4];
p = a[0];
printf("%d\n",&p[3][3] - &a[3][3]);
return 0;
}
几乎所有的编译器都具有检测括号是否匹配的能力,如何实现编译器中的符号成对检测?
算法思路从这里开始;
从第一个字符开始扫描
当遇见普通字符时忽略,当遇见左括号时压入栈中
当遇见右括号时从栈中弹出栈顶符号
进行匹配
匹配成功:继续读入下一个字符
匹配失败:立即停止,并报错.
结束:
成功:所有字符扫描完毕,且栈为空
失败:匹配失败或所有字符扫描完毕但栈为空
算法框架
scanner(code)
{创建栈;
i = 0;
while(code[i] != '\0')
{
if(code[i] 为左括号)
{
Push(S,code[i]);
}
if(code[i] 为右括号)
{
c = (Pop(S);// c是栈顶元素
if(c 与code[i]不匹配)
{
报错,停止循环;
}
}
i++;
}
if((Size(S) == 0) && (code[i]=='\0'))
{
匹配成功;
}
else
{
匹配失败,报错;
}
}
#include <stdio.h>
#include <stdlib.h>
#include "LinkStack.h"
int isLeft(char c)
{
int ret = 0;
switch(c)
{
case '<':
case '(':
case '[':
case '{':
case '\'':// 单引号和双引号需要转译
case '\"':
ret = 1;
break;
defult;// swich case都要加defult形成好习惯,处理意外情况
ret = 0;
break;
}
return ret;
}
int isRight(char c)
{
int ret = 0;
switch(c)
{
case '>':
case ')':
case ']':
case '}':
case '\'':
case '\"':
ret = 1;
break;
defult;
ret = 0;
break;
}
return ret;
}
int match(char left,char right)
{
int ret = 0;
switch(left)
{
case '<':
ret = (reght == '>');
case '(':
ret = (reght == ')');
case '[':
ret = (reght == ']');
case '{':
ret = (reght == '}');
case '\'':
ret = (reght == '\'');
case '\"':
ret = (reght == '\"');
ret = 1;
break;
defult;
ret = 0;
break;
}
}
int scanner(const char* code)
{// 返回0代码编译不过,有匹配问题
LinkStack* stack = LinkStack_Create();
int ret = 0;
int i;
while(code[i] != '\0')// 逐个字符扫描
{
if(isLeft(code[i]))// 判断是左符号就压入栈
{
LinkStack_Push(stack,(void*)(code + i)); // 压地址进去
}
if(isRight(code[i]))// 判断是左符号就压入栈
{
char *c= (char*)LinkStack_Pop(stack)); // 压地址进zhan
if((c= NULL) || !match(*c,code[i]))
{
printf("%c does not match!\n",code[i]);
break;
}
}
i++;
}
if((LinkStack_Size(stack) == 0) && (code[i] == '\0'))
{
printf("succeed!\n");
}
else
{
printf("invalued code!\n");
ret = 0;
}
LinkStack_Destory(stack);// 一定记得创建了就立马写销毁,避免后期忘记导致内存泄漏
return ret;
}
int main (int argc,char *argv[])
{
const char* code = "void f(int a[]) {int(*p)[5];p = NULL;}";
scanner(code);
return 0;
}
总结:
当需要检测成对出现但又不相互相邻的事物时,可以使用栈的“”先进后出“”特性,
栈非常适合于需要“”就近匹配“”的场合
02
—
如何将中缀表达式转换成后缀表达式?
解决办法
遍历中缀表达式中的数字和符号
对于数字:直接输出,按照先后顺序成为后缀表达式的一部分
对于符号:
左括号:进栈
符号: 与栈顶元素进行优先级比较
栈顶符号优先级低:进栈
栈顶符号优先级不低:将栈顶符号弹出并输出,之后进栈
右括号:将栈顶符号弹出并输出,直到匹配左括号
遍历结束:将栈中的所有符号弹出并输出
算法框架
transform(exp)
{
创建栈S;
i=0;
while(exp[i] !='\0'){
if(exp[i]为数字)
output(exp[i]);
}
else if(exp[i]为符号){
while(exp[i]优先级<= 栈顶符号优先级){
output(栈顶符号);
Pop(S);
}
Push(S,exp[i]);
} else if(exp[i]为左括号){
Push(S,exp[i]);
} else if(exp[i]为右括号){
while(栈顶符号不为左括号){
output(栈顶符号);
Pop(S);
}
从S中弹出左括号;
}else{
报错,停止循环;
}
while(Size(S) > 0) &&(exp[i] == '\0')){// 遍历结束的标志
output(栈顶符号);
Pop(S);
}
}
#include <stdio.h>
#include "LinkStack.h"
int isNumber(char c)
{
return ('0' <= c) && (c <='9');
}
int isOperator(char c)
{
return (c == '+') || (c == '-') || (c == '*') || (c == '/');
}
int isLeft(char c)
{
return (c == '(');
}
int isRight(char c)
{
return (c == ')');
}
int priority(char c)// 定义符号优先级
{
int ret = 0;
if((c == '+')||(c == '-'))
{
ret = 1;
}
if((c == '*')||(c == '/'))
{
ret = 2;
}
return ret;
}
void output(char c)
{
if(c !='\0')
{
printf("%c",c);
}
}
void transform(const char* exp)
{
LinkStack* stack = LinkStack_Create();
int i = 0;
while(exp[i] != '\0')// 逐个读入
{
if(isNumber(exp[i]))
{
output(exp[i]);
}
else if ( isOperator(exp[i]) )
{
while(priority(exp[i])) <= priority((char)(int)LinkStack_Top(stack)) )
{// 比栈顶元素优先级比较
output((char)(int)LinkStack_Pop(stack));
}
LinkStack_Push(stack,(void*)(int)exp[i];
}
else if(isLeft(exp[i]))
{
LinkStack_Push(stack,(void*)(int)exp[i]);
}
else if(isRight(exp[i]))
{
char c = '\0';
while(!isleft((char)(int)LinkStack_Top(stack))// 栈顶元素不是左括号输出栈顶元素
{
output((char)(int)LinkStack_Pop(stack));
}
LinkStack_Pop(stack);
}
else
{
printf("Invalid expression!");
break;
}
i++;
}
while((LinkStack_Size(stack)>0) && (exp[i] == '\0'))// 遍历结束输出栈中元素
{
output((char)(int)LinkStack_Pop(stack));
}
LinkStack_Destory(stack);// 销毁栈否则会内存泄漏
}
int main()
{
transform("9+(3-1)*5+8/2");
printf("\n");return 0;
}
计算机是如何基于后缀表达式计算的?基于栈
解决方法:
遍历后缀表达式中的符号和数字
对于数字:进栈
对于符号:
从栈中弹出右操作数
从栈中弹出左操作数
根据符号进行运算
将运算结果压入栈中
遍历结束:栈中的唯一数字为计算结果
算法框架:
compute(exp){
创建栈S;
i= 0;
while(exp[i] != '\0')
{
if(exp[i]为数字)
{
Push(S,exp[i]);
}
else if(exp[i]为符号)
{
1.从栈中弹出右操作数
2.从栈中弹出左操作数
3.根据符号进行运算;
4.Push(stack,结果);
}
else
{
报错,停止循环;
}
i++;
}
if((Size(S) == 1) && (exp[i] == '\0'))
{
栈中唯一的数字为运算结果;
}
返回结果;
}
#include <stdio.h>
#include "LinkStack.h"
int isNumder(char c)
{
return('0' <= c) && (c <= '9');
}
int Operator(char c)
{
return (c == '+') || (c == '-') || (c == '+') || (c== '/');
}
int value(char c)
{
return (c - '0');// 字符转成数字
}
int express(int left,int right,char op)
{
int ret = 0;
switch(op)
{
case '+':
ret = left + right;
break;
case '-':
ret = left - right;
break;
case '*':
ret = left * right;
break;
case '/':
ret = left / right;
break;
defult:
break;
}
return ret;
}
int compute(const char* exp)
{
LinkStack* stack = LinkStack_Create();
int ret = 0;
int i= 0;
while(exp[i] != '\0')
{
if(isNumder(exp[i]))
{
LinkStack_Push(stack,(void*)value(exp[i]));
}
else if(isOperator(exp[i]))
{
int right = (int)LinkStack_Pop(stack);
int left = (int)LinkStack_Pop(stack);
int result = express(left,right,exp[i]);
LinkStack_Push(stack,(void*)result);
}
else
{
printf("Invalid expression!");
break;
}
i++;
}
if((LinkStack_Size(stack) == 1) && (exp[i] == !='\0'))
{
ret = (int)LinkStack_Pop(stack);
}
else// 多了符号,少些数字同样不合法
{
printf("Invalid expression!");
break;
}
LinkStack_Destory(stack);
return ret;
}
int main()
{
printf("9+(3-1)*5+8/2 = %d\n",compute("931-5*82/+"));
return 0;
}
中缀表达式是人习惯的表达方式
后缀表达式是计算机喜欢的表达方式
通过栈可以方便的将中缀形式变换成后缀表达式
中缀表达式的计算过程类似于程序编译运行的过程
留有疑问: