栈的典型应用
递归嵌套
栈还适合解决一类问题,这类问题的特点是:具有自相似的(某一局部往往和整体具有某种共性)问题多可嵌套的递归描述,但因分支位置和嵌套深度并不固定,其递归算法不易控制。
栈结构天然的具有递归嵌套性,故可高效地解决这类问题。
括号匹配
括号匹配就是一种递归嵌套的问题,其任务是,在任一程序块,判断其中的括号是否在嵌套的意义下完全匹配。对于这各任务,我们只需要将push、pop操作分别与左括号和右括号相对应即可:
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 fasle; break;
case ']' : if((S.empty()) || (']' != S.pop())) return false; break;
case '}' : if((S.empty()) || ('}' != S.pop())) return false; break;
default: break;// 非括号字符一律忽略
}
return S.empty();//整个表达式扫描过后,栈中若仍残留(左括号),则不匹配;否则(栈空)匹配
}
这种算法的流程比较简单,而且便于推广至多类括号并存的场景。它自左向右的逐个考察各字符,忽略所有的非括号字符,反遇到左括号,无论哪一类都压入栈中,反遇到右括号,者弹出栈顶的左括号并与之对比。若二者同类,则继续检查下一字符;否则,则可断定表达式不匹配。当然,栈S提前变空或者表达式扫描完之后栈S非空,也意味着不匹配。
栈混洗
另一个递归嵌套的问题就是栈混洗问题:考察三个栈A、B和S,其中B和S初始问空,A含有n个元素,自顶而下构成输入序列:A = < a1, a2, a3。。。an],这里用尖括号表示栈顶,方括号表示栈底。以下,只允许进行S.push(A.pop())或B.push(S.pop())操作,将A中的顶元素压入栈S中,或弹出S的顶元素压入B中,则在经过这两列操作各n次之后,栈A和S有可能均为空。原A中的元素均以转入栈B中。S就相当于一个中转站。若将B中元素自底而上构成的序列记作:B = [ak1, ak2, ak3, …akn> ,则该序列称作原输入序列的一个栈混洗。
比如A = < 1, 2, 3, 4], 则[3, 2, 4, 1>、[1, 2, 3, 4>、[4, 3, 2, 1>等等都是原输入的栈混洗。只是因为每次中转栈push和pop操作的顺序不同,所以会产生不同的栈混洗。现在分析一下:[3, 1, 2, 4>是不是原输入序列的一个栈混洗。这个栈混洗的第一个元素就是3,所以在从中转栈中pop出3时,中转栈中一定也有1和2,且2一定在1的上方,所以在栈混洗中,2一定是先于1被压入栈中。所以这个栈混洗一定是不存在的。
所以可以得出结论当i,j,k都是栈中元素的位置时,i<j<k,则栈中若以[…k, …i,…j…>排列,那么这个栈混洗一定不存在。
当原输入序列的元素个数为n时,它的栈混洗个数为 (2n)!/(n+1)!/n! 。