栈之经典应用

来源:互联网 发布:java 字符串gbk编码 编辑:程序博客网 时间:2024/05/19 10:35
栈的应用

算术表达式的求值
                  栈的应用
                  由于栈结构具有的后进先出的固有特性,致使栈成为程序设计中常用的工具。以下是几个栈应用的例子。

一、算术表达式的中缀表示
                  把运算符放在参与运算的两个操作数中间的算术表达式称为中缀表达式。例如:2+3*4 – 6/9
算术表达式中包含了算术运算符和算术量(常量、变量、函数),而运算符之间又存在着优先级,不能简单地进行从左到右运算,编译程序在求值时,不能简单从左到右运算,必须先算运算级别高的,再算运算级别低的,同一级运算才从左到右。在计算机中进行中缀表达式求值较麻烦。而后缀表达式求值较方便(无须考虑运算符的优先级及圆括号)。                   
                  二、算术表达式的后缀表示
                  把运算符放在参与运算的两个操作数后面的算术表达式称为后缀表达式。
                  例如,对于下列各中缀表达式:
                   (1)3/5+8
                  (2)18-9*(4+3)
对应的后缀表达式为:
                  (1)3 5 / 8 +
                  (2)18 9 4 3 + * -
转换规则:把每个运算符都移到它的两个操作数的后面,然后删除掉所有的括号即可.
例如,将中缀表达式a+b*c-d(e*f)转换为后缀表达式.
三、后缀表达式的求值
                  将中缀表达式转换成等价的后缀表达式后,求值时,不需要再考虑运算符的优先级,只需从左到右扫描一遍后缀表达式即可。具体求值步骤为:从左到右扫描后缀表达式,遇到运算符就把表达式中该运算符前面两个操作数取出并运算,然后把结果带回后缀表达式;继续扫描直到后缀表达式最后一个表达式。                   
                  例如,后缀表达式(abc*+def*/-)的求值
                  四、后缀表达式的求值的算法
                  设置一个栈,开始时,栈为空,然后从左到右扫描后缀表达式,若遇操作数,则进栈;若遇运算符,则从栈中退出两个元素,先退出的放到运算符的右边,后退出的放到运算符左边,运算后的结果再进栈,直到后缀表达式扫描完毕。此时,栈中仅有一个元素,即为运算的结果。

                  例,求后缀表达式:1 2 + 8 2 - 7 4 - / *的值,
                  栈的变化情如下:
步骤栈中元素说明111进栈2122进栈3 遇+号退栈2和1431+2=3的结果3进栈5388进栈63822进栈73遇-号退栈2和88368-2=6的结果6进栈93677进栈1036744进栈1136遇-号退栈4和712367-4=3的结果3进栈133遇/号退栈3和614326/3=2的结果2进栈15 遇*号退栈2和31663*2=6进栈176扫描完毕,运算结束

从上可知,最后求得的后缀表达式之值为6,与用中缀表达式求得的结果一致,但后缀式求值要简单得多。
                  五、中缀表达式变成等价的后缀表达式的算法
                  将中缀表达式变成等价的后缀表达式,表达式中操作数次序不变,运算符次序发生变化,同时去掉了圆括号。转换规则是:设立一个栈,存放运算符,首先栈为空,编译程序从左到右扫描中缀表达式,若遇到操作数,直接输出,并输出一个空格作为两个操作数的分隔符;若遇到运算符,则必须与栈顶比较,运算符级别比栈顶级别高则进栈,否则退出栈顶元素并输出,然后输出一个空格作分隔符;若遇到左括号,进栈;若遇到右括号,则一直退栈输出,直到退到左括号止。当栈变成空时,输出的结果即为后缀表达式。将中缀表达式(1+2)*((8-2)/(7-4))变成等价的后缀表达式。
                  现在用栈来实现该运算,栈的变化及输出结果如下:
步骤栈中元素输出结果说明1( (进栈2(1输出13(+1+进栈4(+1 2输出25 1 2 ++退栈输出,退栈到(止6*1 2 +*进栈7*(1 2 +(进栈8*((1 2 +(进栈9*((1 2 + 8输出810*((-1 2 + 8输出211*((-1 2 + 8 2- 进栈12*(1 2 + 8 2 --退栈输出,退栈到(止13*(/1 2 + 8 2 -/ 进栈14*(/(1 2 + 8 2 -( 进栈15*(/(1 2 + 8 2 - 7输出716*(/(-1 2 + 8 2 - 7-进栈17*(/(-1 2 + 8 2 - 7 4输出418*(-1 2 + 8 2 - 7 4 --退栈输出,退栈到(止19*1 2 + 8 2 - 7 4 - //退栈输出,退栈到(止20 1 2 + 8 2 - 7 4 - / **退栈并输出


                  1、 数制转换

     将一个非负的十进制整数N转换为另一个等价的基为B的B进制数的问题,很容易通过"除B取余法"来解决。
                    【例】将十进制数13转化为二进制数。
     解答:按除2取余法,得到的余数依次是1、0、1、1,则十进制数转化为二进制数为1101。
     分析:由于最先得到的余数是转化结果的最低位,最后得到的余数是转化结果的最高位,因此很容易用栈来解决。

     转换算法如下:

          typedef                   int DataType;//应将顺序栈的DataType定义改为整型
          void                   MultiBaseOutput (int N,int B)
                            {//假设N是非负的十进制整数,输出等值的B进制数
                                 int i;
                                                  SeqStack S;
                                                  InitStack(&S);
                                                  while(N){  //从右向左产生B进制的各位数字,并将其进栈
                                       push(&S,N%B); //将bi进栈0<=i<=j
                                                        N=N/B;
                                                  }
                                                  while(!StackEmpty(&S)){  //栈非空时退栈输出
                                        i=Pop(&S);
                                                         printf("%d",i);
                                                  }
                                                }

以上部分原文地址:http://sjjp.tjuci.edu.cn/sjjg/DataStructure/DS/web/zhanhuoduilie/zhanhuoduilie3.3.1.htm

 

 

栈是计算机术语中比较重要的概念,实质上栈就是一段内存区域,但是栈满足一定的特性,那就是只有一个口,具有先入后出的特性,这种特性在计算机中有很广泛的运用。其实在程序员无时无刻不在运用栈,函数的调用是我们间接使用栈的最好例子,因此可以说栈的一个最重要的运用就是函数的调用。但是栈的运用还不止这些,还有很多,其中几个典型的运行如下:判断平衡符号,实现表达式的求值(也就是中缀表达式转后缀表达式的问题以及后缀表达式求值问题),在路劲探索中实现路劲的保存也可以说是栈的经典运用之一。具体的问题具体分析,只要满足先入后出特性的问题都能找到现成的数据结构---栈。

    本文采用C++中的vector实现栈结构,然后利用栈实现判断平衡符号,实现中缀表达式到后缀表达式的转换,并依据栈实现简单的整数加减乘除运算。
   
首先栈的实现,由于在C++中采用了vector来代替原始的数组操作,这种比较方便的容器能较好的实现数组的功能,当然栈也可以采用链表实现,但是我认为链表没有数组直观,而且在实际的计算机里也是采用连续的存储空间作为栈空间的,因此选择Vector。主要实现三个操作,push、pop以及为空判断。基本的形式如下:
  1. #ifndef __MYSTACK_H_H_
  2. #define __MYSTACK_H_H_

  3. #include "myvector.h"

  4. namespace myspace
  5. {
  6.         template<typename Object>
  7.         class Stack
  8.         {
  9.         public:
  10.                 Stack(){}
  11.                 void push(const Object&x)
  12.                 {
  13.                         objects.push_back(x);
  14.                 }

  15.                 const Object &pop()
  16.                 {
  17.                         int len;
  18.                         if(!isempty())
  19.                         {
  20.                                 objects.pop_back();
  21.                                 len = objects.size();
  22.                                 return objects[len];
  23.                         }
  24.                 }

  25.                 bool isempty()const
  26.                 {
  27.                         return (objects.size()== 0);
  28.                 }

  29.                 int size()
  30.                 {
  31.                         return objects.size();
  32.                 }
  33.         private:
  34.                 Vector<Object> objects;
  35.         };
  36. }

  37. #endif
实现了简单的栈类,接下来采用栈实现一些简单的运用。
 
符号的平衡问题
在语言中往往需要判断一些符号是否是成对出现的,比如<>,{},[],(),通常在C++中也只有这几种对称问题,如何让判断符号的对称也是很多代码判断的首要任务。当然实现的方式是多种多样的,采用栈的实现会相对更加简单。基本的实现思路如下:
假设在读入一串字符串以后,如果遇到对称符号的左边部分,则将其压入栈中,当遇到对称符号的右边部分,则弹出栈中的一个对象,实现比对,如果是对称的,则说明当前的符号是平衡的,如果不对称,则说明当前字符串是不平衡的,当字符串读完以后,如果所有的符号都是平衡的,栈中此时应该就是为空,通过判断栈中是否为空,说明字符串是否是符号平衡的。
依据上面实现的栈类,实现符号平衡判断的过程比较简单,如下所示:
  1. bool isbalance(conststring&str)
  2. {
  3.         string::size_typelen= str.size();

  4.         Stack<char> stack;

  5.         for(string::size_type i= 0; i < len ; ++ i)
  6.         {
  7.                 /*first selection*/
  8.                 if(str[i]=='['|| str[i]=='{'||
  9.                    str[i]=='('|| str[i]=='<')
  10.                 {
  11.                         stack.push(str[i]);
  12.                 }
  13.                 /*如果是对称的符号,则从栈中弹出*/
  14.                 if(str[i]==']'|| str[i]=='}'||
  15.                    str[i]==')'|| str[i]=='>')
  16.                 {
  17.                         /*如果栈中没有对象,则说明不平衡*/
  18.                         if(stack.isempty())
  19.                         {
  20.                                 cout <<"the string is Unblanced"<< endl;
  21.                                 return false;
  22.                         }
  23.                         /*采用switch-case语句判断*/
  24.                         switch(str[i])
  25.                         {
  26.                                 case ']':
  27.                                 {
  28.                                         /*判断是否是匹配的*/
  29.                                         if('['!= stack.pop())
  30.                                         {
  31.                                                 cout << "Can not blanced with ]"<< endl;
  32.                                                 return false;
  33.                                         }
  34.                                         break;
  35.                                 }
  36.                                 case ')':
  37.                                 {
  38.                                         if('('!= stack.pop())
  39.                                         {
  40.                                                 cout << "Can not blanced with )"<< endl;
  41.                                                 return false;
  42.                                         }
  43.                                         break;
  44.                                 }
  45.                                 case '}':
  46.                                 {
  47.                                         if('{'!= stack.pop())
  48.                                         {
  49.                                                 cout << "Can not blanced with }"<< endl;
  50.                                                 return false;
  51.                                         }
  52.                                         break;
  53.                                 }
  54.                                 case '>':
  55.                                 {
  56.                                         if('<'!= stack.pop())
  57.                                         {
  58.                                                 cout << "Can not blanced with >"<< endl;
  59.                                                 return false;
  60.                                         }
  61.                                         break;
  62.                                 }
  63.                                 /*一般的非对称字符*/
  64.                                 default:
  65.                                         break;
  66.                         }
  67.                 }
  68.         }
  69.         /************************************************
  70.         *当所有对象遍历完成以后,需要判断栈中是否存在对象
  71.         *栈中为空说明是平衡的,非空则说明是非空的对象
  72.         ************************************************/
  73.         if(stack.isempty())
  74.         {
  75.                 cout <<"string is blanced!!"<< endl;
  76.                 return true;
  77.         }
  78.         else/*没有正确的匹配,并输出具体不匹配的符号*/
  79.         {
  80.                 cout << stack.pop()<<" " <<"Unblance string!!"<< endl;
  81.                 return false;
  82.         }
  83. }
采用上面的代码能够符号是否平衡的判断。
 
接下来说明一下表达式的求值问题,表达式的求值问题主要设计到操作符的优先级问题,比如()、*/、+-这几种符号的优先级是不一样的,其中括号的优先级最好,乘除其次,加减最低,我们通常看到的表达式都是中缀表达式,也就是在操作符的两边都有对象,当然括号除外啦,这种中缀表达式是不便于在程序中处理的,因为存在很多的优先级差别,很难把握从那个位置先计算。
 
如果在表达式中没有了优先级的问题,求值问题也就变得相对来说更加简单了,后缀表达式是一种非优先级的表达式表示方式,但是如何实现中缀表达式到后缀表达式的切换也是很难实现的。
 
中缀表达式到后缀表达式的实现如下:
  1. /*****************************************
  2.  * 实现表达式中缀到表达式后缀的转换
  3.  *****************************************/
  4. bool midtolast(string&src,string&dst)
  5. {
  6.         string::size_typelen= src.size();

  7.         /*用来保存栈中弹出的对象*/
  8.         char temp = '\0';

  9.         Stack<char> stack;

  10.         for(string::size_type i= 0; i != len;++ i)
  11.         {
  12.                 switch(src[i])
  13.                 {
  14.                         /*如果是'(',则将左括号压入栈中*/
  15.                         case '(':
  16.                         {
  17.                                 stack.push(src[i]);
  18.                                 break;
  19.                         }
  20.                         /*如果是')',则将栈中左括号之前的对象弹出*/
  21.                         case ')':
  22.                         {
  23.                                 /*如果为空,则说明表达式不准确*/
  24.                                 if(stack.isempty())
  25.                                 {
  26.                                         return false;
  27.                                 }
  28.                                 /*非空,弹出对象*/
  29.                                 temp = stack.pop();
  30.                                 /*只要不是左括号,则全部弹出*/
  31.                                 while('('!= temp )
  32.                                 {
  33.                                         /*并输出到后缀表达式中*/
  34.                                         dst += temp;
  35.                                         /*保证栈为非空*/
  36.                                         if(stack.isempty())
  37.                                                 break;
  38.                                         /*弹出新的对象*/
  39.                                         temp = stack.pop();
  40.                                 }
  41.                                 /*如果弹出的是左括号,则不用输出到后缀表达式*/
  42.                                 break;
  43.                         }

  44.                         /***************************************
  45.                         乘除法操作实质上是一致的
  46.                         在压入栈中之前,需要弹出非左括号的同优先级对象
  47.                         遇到左括号或者低优先级的对象就停止,直接入栈
  48.                         ****************************************/
  49.                         case '*':
  50.                         case '/':
  51.                         {
  52.                                 /*判断非空的栈,为空则直接入栈即可*/
  53.                                 if(!stack.isempty())
  54.                                 {
  55.                                         temp = stack.pop();
  56.                                         while(temp!='+'&& temp !='-'&& temp!='(')
  57.                                         {
  58.                                                 dst += temp;
  59.                                                 if(stack.isempty())
  60.                                                 {
  61.                                                         /*保证temp不影响后面的压栈操作*/
  62.                                                         temp = '\0';
  63.                                                         break;
  64.                                                 }
  65.                                                 temp = stack.pop();
  66.                                         }
  67.                                 }
  68.                                 /*如果当前的temp是+-(,则返回到栈中*/
  69.                                 if(temp=='+'|| temp =='-'|| temp=='(')
  70.                                 {
  71.                                         stack.push(temp);
  72.                                 }
  73.                                 /*将当前操作符压栈*/
  74.                                 stack.push(src[i]);

  75.                                 break;
  76.                         }
  77.                         case '-':
  78.                         case '+':
  79.                         {
  80.                                 /*判断非空*/
  81.                                 if(!stack.isempty())
  82.                                 {
  83.                                         /*加减操作的优先级最低,因此需要弹出所有非左括号的操作符*/
  84.                                         temp = stack.pop();
  85.                                         while(temp!='(')
  86.                                         {
  87.                                                 /*将操作符输出到后缀表达式中*/
  88.                                                 dst += temp;
  89.                                                 if(stack.isempty())
  90.                                                 {
  91.                                                         temp = '\0';
  92.                                                         break;
  93.                                                 }
  94.                                                 temp = stack.pop();
  95.                                         }
  96.                                 }
  97.                                 /*如果当前弹出的对象是’(,则重新压入栈中*/
  98.                                 if(temp=='(')
  99.                                 {
  100.                                         stack.push(temp);
  101.                                 }
  102.                                 /*将操作符本身压入栈中*/
  103.                                 stack.push(src[i]);
  104.                                 break;
  105.                         }
  106.                         default:
  107.                         {
  108.                                 /*针对字符而言直接输出到后缀表达式*/
  109.                                 dst += src[i];
  110.                                 break;
  111.                         }
  112.                 }
  113.         }
  114.         /*将栈中非空的操作符输出到后缀表达式中*/
  115.         while(!stack.isempty())
  116.         {
  117.                 temp = stack.pop();
  118.                 dst += temp;
  119.         }
  120.         return true;
  121. }

后缀表达式求值的问题,实现了后缀表达式的转换,实现表达式的求值问题也就比较简单了,实现的基本思路如下:

遍历后缀表达式,遇到非操作符的字符则直接压入栈中,如果遇到操作符则从栈中弹出两个对象,进行对应的操作,然后将计算的结果又压入栈中。继续遍历,直到表达式被遍历完成,此时栈中保存的值也就是当前表达式的值,需要注意除法和减法的操作数顺序问题以及除数不能为0的。为了说明该方法的准确性,我采用0-9以内的数作为操作数,进行4则运算,目前我还只能实现这些计算,对于复杂的多位数还需要另外的处理。实现的代码如下:
  1. /*后缀表达式求值问题*/
  2. int retVal(conststring&src)
  3. {
  4.         Stack<int> stack;
  5.         int temp = 0, s= 0;

  6.         int len= src.size();
  7.         for(string::size_type i= 0; i != len;++ i)
  8.         {
  9.                 /*本程序只能处理整形的加减乘除操作*/
  10.                 switch(src[i])
  11.                 {
  12.                         /*遇到数值直接压入栈中*/
  13.                         case '0':case'1':case'2':case'3':case'4':
  14.                         case '5':case'6':case'7':case'8':case'9':
  15.                         {
  16.                                 stack.push(myatoi(src[i]));
  17.                                 break;
  18.                         }
  19.                         /*********************************************
  20.                         * 遇到操作符弹出两个数值进行操作
  21.                         * 需要注意减法和除法的压栈顺序
  22.                         * 操作完成以后将得到的结果压入到栈中
  23.                         * 最后栈中的值就是计算的结果
  24.                         **********************************************/
  25.                         case '+':
  26.                         {
  27.                                 temp = stack.pop()+ stack.pop();
  28.                                 stack.push(temp);
  29.                                 temp = 0;
  30.                                 break;
  31.                         }
  32.                         case '-':
  33.                         {
  34.                                 s = stack.pop();
  35.                                 temp = stack.pop()- s;
  36.                                 stack.push(temp);
  37.                                 s = 0;
  38.                                 temp = 0;
  39.                                 break;
  40.                         }
  41.                         case '*':
  42.                         {
  43.                                 temp = stack.pop()* stack.pop();
  44.                                 stack.push(temp);
  45.                                 temp = 0;
  46.                                 break;
  47.                         }
  48.                         case '/':
  49.                         {
  50.                                 s = stack.pop();
  51.                                 if(s!= 0)
  52.                                 {
  53.                                         temp = stack.pop()/ s;
  54.                                 }
  55.                                 stack.push(temp);
  56.                                 s = 0;
  57.                                 temp = 0;
  58.                                 break;
  59.                         }
  60.                 }
  61.         }
  62.         /*获得最后的值*/
  63.         return stack.pop();
  64. }

为了分析上面的代码准确性,写了如下的测试代码:

  1. int main()
  2. {
  3.         string s1;
  4.         cout <<"Input a string to test the balance :"<< endl;
  5.         cin >> s1;
  6.         isbalance(s1);
  7. #if 10
  8.         string src;
  9.         string dst;
  10.         cout <<"Input a expression: "<< endl;
  11.         cin >> src;
  12.         midtolast(src,dst);
  13.         cout <<"After the convertion: "<< endl;
  14.         cout << dst<< endl;
  15.         cout <<"The result of expression: "<< endl;
  16.         cout << retVal(dst)<< endl;
  17. #endif
  18.         return 0;
  19. }

测试结果:

  1. [gong@Gong-Computer data_struct]$ vi testblance.cpp
  2. [gong@Gong-Computer data_struct]$ g++-g testblance.cpp-o testblance
  3. [gong@Gong-Computer data_struct]$./testblance
  4. Input a string to test the balance:
  5. This(is)[a]{te}<st>.
  6. string is
  7. Input a expression:
  8. 3*3-(5-2+3*6)*(7-3*1)
  9. After the convertion:
  10. 33*52-36*+731*-*-
  11. The result of expression:
  12. -75
从上面的测试结果基本上实现符号平衡测试、表达式的求值问题,但是当前的表达式只能计算0-9为操作数的四则运算,复杂的多位数还不能进行测试。
 
以上的三个例子是栈的经典运用,对栈的特性先入后出有更深层次的理解才能更好的运用。
 
在函数调用中,如果不保存调用程序的状态,在被调用程序中就会修改程序的状态,这时候也就不能还原到之前的状态,只有将当前的状态保存起来,压入栈中,当被调用程序执行完成以后,将当前程序栈中的内容弹出就能实现任务状态的还原,当前函数执行完成以后,调用该函数的状态也需要还原。这时候就实现了先调用的函数最后执行,所以说函数调用是典型的先入后出问题。这也说明为什么栈如此的重要。

 

 

以上部分的原文地址是:http://blog.chinaunix.net/uid-20937170-id-3322943.html

原创粉丝点击