4.2 栈应用

2020-07-14  本文已影响0人  月影追猎者

逆序输出 conversion
输出次序与处理过程相反,递归深度与输出长度不易预知
递归嵌套 stack permutation + parenthesis
具有自相似性的问题可递归描述,但分支位置与嵌套深度不固定
延迟缓冲 evaluation
线性扫描算法模式中,在预读足够长度后,方能确定可处理的前缀
栈式计算 RPN
基于栈结构的特定计算模式

进制转换

void convert(Stack<char> & S, __int64 n, int base) {
    static char digit[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
    while (n > 0) { // 由低到高,逐一计算目标进制的各数位
        S.push(digit[n % base]); // 余数(对应数位)入栈
        n /= base; // n更新为其对base的除商
    }
}
main() {
    Stack<char> S;
    convert(S, n, base); // 使用栈记录转换所得各数位
    while (!S.empty())
        printf("%c", S.pop()); // 逆序输出
}

括号匹配

bool paren(const char exp[], int lo, int hi) { // exp[lo, hi)
    Stack<char> S; // 使用栈记录已发现但尚未匹配的左括号
    for (int i = lo; i < hi; i++) // 逐一检查当前字符
        if ('(' == exp[i])
            S.push(exp[i]); // 遇左括号,入栈
        else if (!S.empty())
            S.pop(); // 遇右括号,若栈非空,则弹出左括号
        else
            return false; // 否则必不匹配
    return S.empty(); // 最终当且仅当栈空时匹配
}

栈混洗
栈A = <a1, a2, ..., an]、B = S = Ø,只允许将A的顶元素弹出并压入S,或将S的顶元素弹出并压入B,若经一系列上述操作后,A中元素全部转入B中,B = [ak1, ak2, ..., akn>,则称为A的一个栈混洗(stack permutation)
同一输入序列,可有多种栈混洗
对于长度为n的序列,可能的栈混洗数
SP(n)=\sum_{k=1}^{n} SP(k-1)SP(n-k)=\frac{(2n)!}{n!(n+1)!}=catalan(n)
任意3个元素能否按某相对次序出现于混洗中,与其它元素无关
对于任意1 <= i < j < k <= n,[..., k, ..., i, ..., j, ...>必非栈混洗,称为禁形
O(n)判定算法:直接借助栈A、B与S,模拟混洗过程,每次S.pop()前检测S是否已空,或需弹出元素在S中却非顶元素
每一栈混洗,对应于栈S的n次push与n次pop操作构成的序列

中缀表达式

float evaluate(char* S, char* & RPN) { // 中缀表达式求值
    Stack<float> opnd; // 运算数栈
    Stack<char> optr; // 运算符栈
    optr.push('\0'); // 尾哨兵'\0'作为头哨兵首先入栈
    while (!optr.empty()) { // 逐个处理字符,直至运算符栈空
        if (isdigit(*S)) // 若当前字符为操作数
            readNumber(S, opnd); // 则读入(可能多位)操作数
        else // 若当前字符为运算符,则视其与栈顶运算符间优先级高低
            switch(orderBetween(optr.top(), *S)) { /* 分别处理 */ }
    }
    return opnd.pop(); // 弹出并返回计算结果
}
const char pri[N_OPTR][N_OPTR] = { // 运算符优先级[栈顶][当前]
//       +    -    *    /    ^    !    (    )    \0
/* + */ '>', '>', '<', '<', '<', '<', '<', '>', '>',
/* - */ '>', '>', '<', '<', '<', '<', '<', '>', '>',
/* * */ '>', '>', '>', '>', '<', '<', '<', '>', '>',
/* / */ '>', '>', '>', '>', '<', '<', '<', '>', '>',
/* ^ */ '>', '>', '>', '>', '>', '>', '<', '>', '>',
/* ! */ '>', '>', '>', '>', '>', '>', ' ', '>', '>',
/* ( */ '<', '<', '<', '<', '<', '<', '<', '=', ' ',
/* ) */ ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
/* \0 */'<', '<', '<', '<', '<', '<', '<', ' ', '=',
};
switch(orderBetween(optr.top(), *S)){ // 不同优先级处理方法
    case '<': // 栈顶运算符优先级更低
        optr.push(*S);
        S++;
        break; // 计算推迟,当前运算符进栈
    case '=': // 优先级相等(当前运算符为右括号或尾部哨兵'\0')
        optr.pop();
        S++;
        break; // 脱括号并接收下一个字符
    case '>': // 栈顶运算符优先级更高,实施相应计算,结果入栈
        char op = optr.pop(); // 栈顶运算符出栈,执行对应运算
        if ('!' == op)
            opnd.push(calcu(op, opnd.pop())); // 一元运算符
        else {
            float pOpnd2 = opnd.pop();
            float pOpnd1 = opnd.pop(); // 二元运算符
            opnd.push(calcu(pOpnd1, op, pOpnd2)); // 实施计算,结果入栈
        }
        break;
}

逆波兰表达式(Reverse Polish Notation, RPN)
在由运算符(operator)与操作数(operand)组成的表达式中不使用括号(parenthesis-free),即可表示带优先级的运算关系

float evaluate(char* S, char* & RPN) { // RPN转换
    while (!optr.empty()) { // 逐个处理字符,直至运算符栈空
        if (isdigit(*S)) { // 若当前字符为操作数
            readNumber(S, opnd);
            append(RPN, opnd.top()); // 则直接将其接入RPN
        } else { // 若当前字符为运算符
            switch(orderBetween(optr.top(), *S)) {
                case '>': // 且可立即执行
                    char op = optr.pop();
                    append(RPN, op); // 则在执行相应计算的同时将其接入RPN
            }
        }
    }
}
上一篇下一篇

猜你喜欢

热点阅读