1逆序输出
在栈所擅长解决的典型问题中,有一类具有以下特征:首先,虽有明确的算法,但其解答却以线性序列的形式给出;其次无论递归还是迭代实现,该序列都是依逆序计算输出的。最后,输入和输出规模的不确定,难以实现确定盛放输出数据的容器大小。因其特有的“后进先出”特性及其在容量方面的自适应性,可使用栈来解决此类问题。
进制转换:
任给十进制整数n,将其转换为λ进制的表示形式。
void convert(stack<char>& S,_int64 n,int base){//十进制正整数n到base进制的转换(递归版) static char digit[]={'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};//新进制下的数位符号 if(0<n){ S.puch(digit[n%base]); //逆向记录当前最低位,再通过递归得到所有最低位 convert(S,n/base,base); } }//新进制下由高到低的各数位,自顶而下保存于栈S中
在计算结束后只需通过反复的出栈操作即可由高到低的将其顺序输出。
void convert(Stack<char>& S,_int64 n,int base){//十进制n到base进制的转换(迭代版) static char digit[]={'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'}; while(n>0){//由高到低,助益计算出新进制下的各数位 int remainder=(int)(n%base); S.puch(digit[remainder]);//余数入栈 n/=base;//n更新为其对base的除商 } }
2.括号匹配
对表达式括号匹配的检查则又是语法检查中必须的一个环节。其任务是,对任一程序块,判断其中的括号是否在嵌套的意义下完全匹配。
递归实现:
不妨先只考虑圆括号。用‘+’表示表达式的串接。不难理解,一般的,若表达式S可分解为如下形式:
S=S0+"("+S1+")"+S2+S3
其中S0和S3不含括号,且S1中左右括号数目相等,则S匹配当且仅当S1和S2均匹配。
按照这一理解,可采用分治策略设计算法如下:将表达式划分为自表达式S0、S1和S2,分别递归地判断S1和S2是否匹配。
void trim(const char exp[],int& lo,int& hi){//删除exp[lo,hi]不含括号的最长前缀、后缀 while((lo<=hi)&&(exp[lo]!='(')&&(exp[lo]!=')')) lo++;//查找第一个和最后一个括号 while((lo<hi)&&(exp[hi]!='(')&&exp[hi]!=')')) hi--; } int divide(const char exp[],int lo,int hi){//切分exp[lo,hi],使exp匹配仅当子表达死匹配 int mi=lo; int crc=1;//crc为[lo,mi]范围内左右括号数目之差 while((0<crc)&&(++mi<hi)){//逐个检查各字符,直到左右括号数目相等,或者越界 if(exp[mi]==')')crc--;//左右括号分别计数 if(exp[mi]=='(')crc++; } return mi;//若mi<=hi,则为合法切分点;否则,意味着局部不可能匹配 } bool paren(const char exp[],int lo,int hi){//检查表达式exp[lo,hi]是否括号匹配(递归版) trim(exp,lo,hi);//清楚不含括号的前缀、后缀 if(lo>hi)return true; if(exp[lo]!='(')return false;//首字符非左括号,则必不匹配 if(exp[hi]!=')')return false;//首字符非右括号,则必不匹配 int mi=divide(exp,lo,hi);//确定适当的切分点 if(mi>hi)return false;//切分点不合法,意味着局部以至整体不匹配 return paren(exp,lo+1,mi-1)&&paren(exp,mi+1,hi);//分别检查左右子表达式 }
其中,trim()函数用于截除表达式中不含括号的头部和尾部,即前缀S0和后缀S3。divide()函数对表达式做线性扫描,并动态地记录已经扫描的左、右括号数目之差。如此,当已扫过同样多的左、右括号时,即确定了一个合适的切分点mi,并得到子表达式S1=exp(lo,mi)和S2=exp(mi,hi]。以下,经递归的检查S1和S2,即可判断原表达式是否匹配。
在最坏的情况下divide()需要线性时间,且递归深度为σ(n),故以上算法共需σ(n2)时间。此外,该方法也难以处理含有多种括号的表达式,故有必要进一步优化。
迭代实现:
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();//整个表达式扫描过后,栈中若残留(左)括号,则不匹配;否则(栈空)匹配 }
新算法可推广至多类括号并存的场合。它自左向右逐个考察各字符,忽略所有非括号字符。凡遇到左括号,无论属于哪类均统一压入栈中。若遇到右括号,则弹出栈顶的左括号并与之比对。若二者属于同类,则继续检查下一字符;否则,即可断定表达式不匹配。当然,栈S提前变空或者表达式扫描过后栈S非空,也意味着不匹配。