栈实现表达式求值——逆波兰法
正儿八经地学习数据结构有一个多月了,基本的线性表和vector、list、deque等等都浅浅地接触了一下。实验做了学生信息管理的顺序表实现和链表实现,二者各有利弊。但是通过学习数据结构和一些算法,我对那些厉害人物以及他们写的神奇的算法非常崇拜,胸中有一片火热尽管现实摧残得我不敢轻举妄动。
这不刚刚接触数据结构中的受限表——栈。栈是先进后出(FILO)的,这一特性赋予了该数据类型独特的个性。他只能从一端进行push压入和pop弹出操作。这恰好符号了我做表达式求值的要求。当然我想,既然学了,那就往深处挖挖,看看更深的地方人们所做的建树与探索。
按常理来说,计算机处理一个表达式,比如 a + b * c + (d* e + f)*g.需要按照运算符的优先级来先后计算子式。故采用 中缀转后缀法()转换成无二义性且可以顺序处理的表达式。源代码:
- //2011-10-10 by bibodeng
- //对输入的表达式求值
- //主要思路:先将中缀表达式转换成后缀表达式
- //再对后缀表达式进行求值
- #include <iostream>
- #include <stack>
- #include <vector>
- #include <string>
- #include <sstream>
- using namespace std;
- typedef vector<string> STRVECTOR;
- void show_vector( STRVECTOR &v);
- double str2num(string s);
- string num2str(double i);
- void main()
- {
- //声明一个栈,中缀到后缀时转换的符号储存在该栈中
- stack <string,vector<string>> st1 ;
- vector<string> input;//存放首次输入的中缀式子vector数组
- vector<string> temp;//用于存放后缀式子
- char ans;
- string a;
- //********************************************
- do
- {
- system("cls");
- STRVECTOR::iterator ite;
- cout<<"请输入式子的长度"<<endl;
- int APPY_SIZE;
- cin>>APPY_SIZE;
- cout<<"请输入要进行运算的式子,式子的每个元素以空格隔开:"<<endl;
- for( int i=0;i<APPY_SIZE;i++)
- {
- cin>>a;
- input.push_back(a);
- }
- cout<<"您输入的表达式是:"<<endl;
- show_vector(input);
- //标示是否正在处理闭合括号 0 为不是,1为是, 2 为两重,依次类推
- int flag=0;
- //*******************进行中缀到后缀的转换*******************
- //***********************开始循环**************************
- for(unsigned int i=0;i<input.size();i++)
- {
- //对于input,若是操作符视情况压入栈中,若是操作数,直接送到temp
- //低优先级的+ 和 -
- //遇到低优先级的+ -
- if("+"==input[i] || "-"==input[i])
- {
- //没有比这个优先级更低的了,弹出直至空或者碰到(
- while(!st1.empty())
- {
- if(st1.top()=="(")
- break; //若碰到( 需要停止,因为只有碰到),(才被弹出
- temp.push_back(st1.top());
- st1.pop();
- //弹出一个
- }
- //所有都弹出后,再把+ 或 - 压入
- st1.push(input[i]);
- }
- //************************遇到乘除***************************
- //只需考虑前面是否有+ - ,若有则入栈
- else if("*"==input[i] || "/"==input[i])
- {
- if(!st1.empty()) //非空
- {
- //若遇到等级低的直接压入
- if(st1.top()=="+"||st1.top()=="-")
- st1.push(input[i]);
- //若等级相同,则弹出后压入
- else if(st1.top()=="*"||st1.top()=="/")
- {
- while(!st1.empty()&&st1.top()!="(")
- {
- temp.push_back(st1.top());
- st1.pop();//弹出
- }
- }
- else if(st1.top()=="(")
- st1.push(input[i]);
- }
- //碰到正在处理括号,则把符号压入
- if (st1.empty())
- st1.push(input[i]);
- }
- //************************遇到括号****************************
- //优先级最高,直接压入
- else if(input[i]== "(")
- {
- st1.push(input[i]);
- flag++; //提起标志,正在处理括号中
- }
- //碰到右括号,与左括号相匹配,将中间的都弹出来
- else if(input[i]== ")")
- {
- //弹出所有非(的运算符
- if(!st1.empty())
- {
- while(st1.top()!="(")//非空且不为括号
- {
- temp.push_back(st1.top());
- st1.pop();
- }
- if(st1.top()=="(")
- {
- st1.pop();//弹出且改变flag
- flag--;
- }
- }
- }
- //*********************************数值直接输出到temp************************
- else if(input[i]!="+"&& input[i]!="-"&&input[i]!="*"&&input[i]!="/"&&input[i]!="("&&input[i]!=")")
- temp.push_back(input[i]);
- //************************转换循环结束*************************
- }
- //将所有符号弹空送到temp中
- while(!st1.empty())
- {
- temp.push_back(st1.top());
- st1.pop();
- }
- //输出后缀式子
- show_vector(temp);
- //**********************************************************
- //用于最终计算的栈,可以弹出与压入
- stack <string,vector<string>> final;
- double x=0,y=0; //x,y 用于存储从栈中取出的数字
- double result=0; //result 用于存储产生的结果
- //进行式子的计算
- for(unsigned int i=0;i<temp.size();i++)
- {
- //如果碰到是数字
- if(temp[i]!="+"&& temp[i]!="-"&&temp[i]!="*"&&temp[i]!="/"&&temp[i]!="("&&temp[i]!=")")
- final.push(temp[i]);//将数字压入栈中
- else //若是遇到运算符,则将两个数弹出来然后进行计算,将结果压入栈中
- {
- if(!final.empty())
- {
- x=str2num(final.top());
- final.pop();
- y=str2num(final.top());
- final.pop();
- }
- if(temp[i]== "+")result=x+y;
- else if(temp[i]== "-")result=y-x;
- else if(temp[i]== "*")result=x*y;
- else if (temp[i]=="/")result=y/x;
- string temp1;
- temp1=num2str(result);
- final.push(temp1);
- }
- }
- cout<<result<<endl;
- //********************************************************************
- cout<<"是否继续运算(Y/n) "<<endl;
- cin>>ans;
- input.clear();
- temp.clear();
- }while(ans=='y'||ans=='Y');
- //********************************************
- }
- //输出vector内容
- void show_vector( STRVECTOR &v)
- {
- STRVECTOR::iterator ite;
- for(ite=v.begin();ite!=v.end();ite++)
- cout<<*ite;
- cout<<endl;
- }
- //字符串转数字
- double str2num(string s)
- {
- double num;
- stringstream ss(s);
- ss>>num;
- return num;
- }
- //数字转字符串
- string num2str(double i)
- {
- stringstream ss;
- ss<<i;
- return ss.str();
- }
最关键的一步是中缀转后缀,分四种情况进行若是+ - ,优先级最低,只管将st1栈中的运算符弹出,直到遇到(或者st1为空为止。而遇到* / 等优先级较高的,又分若干种情况。不过基本上有三种:
1、遇到优先级低的,直接压入
2、遇到同等优先级的弹出后压入
3、碰到(直接压入。
最后两种情况(“(”和“)”)比较好处理,遇到(优先级最高,直接压入。遇到“)”则将与左括号“(”之间的运算符弹出送到结果当中,最后将“(”弹出,不送到结果当中。
编码的时候遇到了相当多的问题,最后是用监视将各类bugs进行清扫。计算机里面的一些细微逻辑的确不容忽视,每一个语句都要经得起考验。
据了解,这个表达式求值居然是编译器工程的一个重要组成部分,这里涉及到许多符号平衡,以及众多匹配问题(如简单的if else)。编译器必须将语法正确的算法语言的语义正确地翻译成机器语言。然而这当中又有如此多常人难以考虑周全的细节,虽然作为一个编译器使用者根本不需要操心这类事情。但是从我们使用的算法语言的灵活性来看,这的确够让那些写编译器的人喝一壶了。因此,我由衷地崇拜那些能够写出庞大的编译器的人。像stallman这样的天才型***,到时候一定要看看他写的gcc里面的一些巧夺天工的代码……扯远了 :P
最近觉得学了数据结构就能对现实中的很多东西能够刻画成模型并实现之,而且有些还非常的有趣。比如用栈走迷宫,用循环链表来解约瑟夫环。这些问题都非常地有趣,而且这其中不乏有好的算法,像大名鼎鼎的knuth写的约瑟夫环算法,相当地简练,效率也高,而一般人写出的算法一大串且效率低下,很不幸我自己也是一般人。在现实社会中,好的算法往往能够让人为之一振,而且让效率成倍增长。特别是在这个信息迅猛发展的时代,算法更是举足轻重。想想如此大的运算量,假如算法好,那么一次处理节省了多少时间与空间。眼下之时,正式学好算法的时代。
2011-10-11 by bibodeng