栈及其应用

代码来源:《数据结构(c++语言版)(第三版)》,邓俊辉编著,ISBN: 978-7-302-33064-6


栈是典型的后进先出的结构,且只有一段(栈顶)可以操作数据。


主要操作有:

栈支持的操作接口
操作接口功能
size()报告栈的规模
empty()判断是否为空
push(e)将e插至栈顶
pop()删除栈顶对象
top()引用栈顶对象








1、进制转换


在利用短除法进行进制转换时,所得余数是从上向下排列,而输出是由下向上。这时可以使用栈结构。
void convert ( Stack<char>& S, __int64 n, int base ) { //十进制正整数n到base进制的转换(递归版)
   static char digit[] //0 < n, 1 < base <= 16,新进制下的数位符号,可视base取值范围适当扩充
   = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
   if ( 0 < n ) { //在尚有余数之前,反复地
      S.push ( digit[n % base] ); //逆向记录当前最低位,再
      convert ( S, n / base, base ); //通过递归得到所有更高位
   }
} //新进制下由高到低的各数位,自顶而下保存于栈S中


2、括号匹配


括号匹配即是找到一串括号,看左右括号是否能够一一对应。对于这种情况,无论是把它分成两部分、还是不断消减,都无法很好的达到效果。

这时考虑栈。当遇到左括号时,入栈,遇到右括号时,出栈。

bool paren ( const char exp[], int lo, int hi ) { //表达式括号匹配检查,可兼顾三种括号
   Stack<char> S; //使用栈记录已发现但尚未匹配的左括号
   for ( int i = lo; i <= hi; i++ ) /* 逐一检查当前字符 */
      switch ( exp[i] ) { //左括号直接进栈;右括号若与栈顶失配,则表达式必不匹配
         case '(': case '[': case '{': S.push ( exp[i] ); break;
         case ')': if ( ( S.empty() ) || ( '(' != S.pop() ) ) return false; break;
         case ']': if ( ( S.empty() ) || ( '[' != S.pop() ) ) return false; break;
         case '}': if ( ( S.empty() ) || ( '{' != S.pop() ) ) return false; break;
         default: break; //非括号字符一律忽略
   }
   return S.empty(); //整个表达式扫描过后,栈中若仍残留(左)括号,则不匹配;否则(栈空)匹配
}

3、栈混洗

栈混洗就是将栈A中的元素按某种顺序,压入S中,再由S弹出压入B完成某种排列。

其中,栈混洗的总共排列个数,就是卡特兰数(Catalan number)( 2n )! / ( (n + 1)! * n! )

考虑只含三个元素1 2 3 的栈,易知,它的栈混洗不会是3 1 2,推而广之,在栈中,有三个元素i j k,如果新栈里有k i j的形式,那么它一定不是栈混洗。换而言是,k j i的出现是栈混洗的必要条件。

Knuth证明,这也是其充要条件。


4、中缀表达式求值

对于求解一个复杂的计算式,没办法简单地从左到右计算,借助栈,将待执行的元素堆入栈,遇到优先级低的符号就计算上一个表达式,最后计算全部。

float evaluate ( char* S, char*& RPN ) { //对(已剔除白空格的)表达式S求值,并转换为逆波兰式RPN
   Stack<float> opnd; Stack<char> optr; //运算数栈、运算符栈
   optr.push ( '\0' ); //尾哨兵'\0'也作为头哨兵首先入栈
   while ( !optr.empty() ) { //在运算符栈非空之前,逐个处理表达式中各字符
      if ( isdigit ( *S ) ) { //若当前字符为操作数,则
         readNumber ( S, opnd ); append ( RPN, opnd.top() ); //读入操作数,并将其接至RPN末尾
      } else //若当前字符为运算符,则
         switch ( orderBetween ( optr.top(), *S ) ) { //视其与栈顶运算符之间优先级高低分别处理
            case '<': //栈顶运算符优先级更低时
               optr.push ( *S ); S++; //计算推迟,当前运算符进栈
               break;
            case '=': //优先级相等(当前运算符为右括号或者尾部哨兵'\0')时
               optr.pop(); S++; //脱括号并接收下一个字符
               break;
            case '>': { //栈顶运算符优先级更高时,可实施相应的计算,并将结果重新入栈
               char op = optr.pop(); append ( RPN, op ); //栈顶运算符出栈并续接至RPN末尾
               if ( '!' == op ) { //若属于一元运算符
                  float pOpnd = opnd.pop(); //只需取出一个操作数,并
                  opnd.push ( calcu ( op, pOpnd ) ); //实施一元计算,结果入栈
               } else { //对于其它(二元)运算符
                  float pOpnd2 = opnd.pop(), pOpnd1 = opnd.pop(); //取出后、前操作数
                  opnd.push ( calcu ( pOpnd1, op, pOpnd2 ) ); //实施二元计算,结果入栈
               }
               break;
            }
            default : exit ( -1 ); //逢语法错误,不做处理直接退出
         }//switch
   }//while
   return opnd.pop(); //弹出并返回最后的计算结果
}

这里设置了两个栈,分别存储数字和字符,而字符通过优先级判断。如果现在的运算符比栈顶运算符高,不执行栈顶运算符,继续压入栈。


5、逆波兰表达式

逆波兰表达式可以方便的计算中缀表达式的值。对于其计算,只需要用括号把所有运算分别包括,然后把运算符移至紧邻的括号右边,最后删去括号即可。


( 0 ! + 1 ) * 2 ^ ( 3 ! + 4 ) - ( 5 ! - 67 - ( 8 + 9 ) )

其逆波兰表达式为

0 ! 1 + 2 3 ! 4 + ^ 5 ! 67 - 8 9 + - - 


6、队列

队列和栈是完全相反的操作,为先进先出规律。只能从一端输入数据,另一端删除数据。


队列ADT支持的操作接口
操作功能
size()报告队列的规模(元素总数)
empty()判断队列是否为空
enqueue(e)将e插入队尾
dequeue()删除队首元素
front()引用队首元素

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值