[数据结构笔记] 栈Stack、队列Queue

目录

括号匹配问题

栈混洗问题

中缀表达式求值与中缀表达式转换为RPN

利用栈判断回文串

一向量多栈

倒置栈

直方图中最大矩形问题

队列

双栈当队

双队当栈


LIFO结构,函数递归调用的底层实现

括号匹配问题

栈的简单应用,过程中与结束时检查是否为空栈。

考虑文法

S \rightarrow S(S)S \\ S \rightarrow \epsilon

括号匹配问题为其LR(0)文法分析过程。

若引入多种括号,左括号进栈,右括号与栈顶匹配则栈顶出栈,继续识别;与栈顶不匹配(在当前左括号还未匹配时识别到其他类别的括号)即得匹配失败。

栈混洗问题

有三个栈A、B、C,A中有元素,B、C皆为空。只考虑下面两种操作:

  1. A出栈,B压栈
  2. B出栈,C压栈

把A、B中元素全部出栈,最终在C中得到的元素序列称为原序列的一个栈混洗。

栈混洗禁形:不失一般性,设A中元素为1,2,\dots,na_1,a_2,\dots,a_n为一个排列,易见上述排列是一个栈混洗当且仅当不存在1\le i < j < k \le n, \dots ,k , \dots,i, \dots ,j,\dots在上述排列中。

视操作1为括号匹配问题中识别到左括号的动作,操作2为括号匹配问题中识别到右括号的动作,那么

  • 一个括号匹配的序列意味着在任何位置操作1次数不少于操作2次数,而总体操作1次数与操作2次数相同,这给出了一个栈混洗序列
  • 一个栈混洗序列的产生意味着在任何位置操作1次数不少于操作2次数,而总体操作1次数与操作2次数相同,对应了一个括号匹配序列

这表明A的栈混洗序列与n对括号的匹配序列构成双射,A的栈混洗数量与n对括号的匹配序列数量相同。

n对括号的匹配序列数量为C_n,其中\displaystyle C_n =\frac{1}{n}\binom{2n}{n}为第n个Catalan数

对于任何n对括号匹配的表达式Sn,有拆解Sn=Sk(Sn-k-1),0<=k<n,从而记Sn有Pn种可能,有递推关系\displaystyle P(n) = \sum_{k=0}^{n}P(k) + P(n-k)和边界条件P(0)=0,P(1)=1,对照Catalan数递推关系即得。

中缀表达式求值与中缀表达式转换为RPN

按运算符优先级计算中缀表达式。计算过程中按计算顺序追加RPN。

通常计算规则下的语法制导翻译

#define N_OPTR 9 //运算符总数
typedef enum { ADD, SUB, MUL, DIV, POW, FAC, L_P, R_P, EOE } Operator; //运算符集合
//加、减、乘、除、乘方、阶乘、左括号、右括号、起始符与终止符

const char pri[N_OPTR][N_OPTR] = { //运算符优先等级 [栈顶] [当前]
/* |-------------------- 当 前 运 算 符 --------------------| */
/* + - * / ^ ! ( ) \0 */
/* -- + */ '>', '>', '<', '<', '<', '<', '<', '>', '>',
/* | - */ '>', '>', '<', '<', '<', '<', '<', '>', '>',
/* 栈 * */ '>', '>', '>', '>', '<', '<', '<', '>', '>',
/* 顶 / */ '>', '>', '>', '>', '<', '<', '<', '>', '>',
/* 运 ^ */ '>', '>', '>', '>', '>', '<', '<', '>', '>',
/* 算 ! */ '>', '>', '>', '>', '>', '>', ' ', '>', '>',
/* 符 ( */ '<', '<', '<', '<', '<', '<', '<', '=', ' ', //当栈顶运算符是左括号但已经扫描到终结符时,括号不匹配
/* | ) */ ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', //当栈顶运算符是右括号时,括号不匹配
/* -- \0 */ '<', '<', '<', '<', '<', '<', '<', ' ', '='
};
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(); //弹出并返回最后的计算结果
}

开头增加开始符哨兵,优先级低于常规计算符和左括号。栈顶为开始符时识别到常规计算符和左括号,操作符进栈。

相同等级的运算符表项均记为>,出现此种情况即栈顶出栈计算。

evaluate()函数对语法错误的表达式可能亦能给出结果,例如(12)3+!4*+5。

(2018-912)非法表达式(12)3+!4*+5执行evaluate算法后的结果为______。

输入缓冲操作数栈操作符栈动作
(12)3+!4*+5$$识别到开始符
12)3+!4*+5$$ (识别到(,栈顶为$,栈顶<当前,(入栈
)3+!4*+5$12$12入栈(readNumber函数识别最长操作数字符串)
3+!4*+5$12$识别到),栈顶为(,脱括号,推进一个字符
+!4*+5$12 3$3入栈
!4*+5$12 3$ ++入栈
4*+5$12 3$ + !识别到!,栈顶为+,栈顶<当前,!入栈
*+5$12 3 4$ + !4入栈
*+5$12 3 24$ +识别到*,栈顶为!,栈顶>当前,执行!
+5$12 3 24$ + *识别到*,栈顶为+,栈顶<当前,*入栈
+5$12 72$ +识别到+,栈顶为*,栈顶>当前,执行*
+5$84$识别到+,栈顶为+,栈顶>当前,执行+
5$84$ ++入栈
$84 5$ +5入栈
$89$识别到$,栈顶为+,栈顶>当前,执行+
$89识别到$,栈顶为$,栈顶=当前,$出栈
$89操作符栈为空,while循环退出,得89

RPN求值从左向右执行即可

infix为语法解析树的中序遍历,postfix为语法解析树的后序遍历

  • 中缀表达式需要加括号以规定计算顺序,但无需元字符分隔相邻操作数
  • RPN不需要加括号,但需要元字符分隔相邻操作数(11 2 +和1 12+)

利用栈判断回文串

找到中间位置即可

一向量多栈

设置每个栈的sp,类比内存栈管理

倒置栈

利用递归机制存数据,时间复杂度O(n^2),空间复杂度为递归深度O(n)

直方图中最大矩形问题

按照从左到右的顺序处理元素,并且维护一个包含已经开始但尚未结束的子直方图信息的栈。

不妨认为相邻直方高度不同。

  • 如果当前高度大于栈顶元素,这表明栈顶元素高度的直方可以延伸,入栈
  • 如果当前高度小于栈顶元素,这表明栈顶元素高度的直方到此结束了
    • 计算此时面积
      • 矩形的高是栈顶元素
      • 矩形的底是index(当前)-index(栈顶)
    • 更新最大值并出栈,继续检查
  • 结束时可增加哨兵,高度为0

分摊时间复杂度为O(n),空间复杂度为O(n)(维护的栈)。

队列

FIFO结构,出队端为队头,进队端为队尾

双栈当队

一个进一个出,用完一个就把另一个pop-push

双队当栈

确保至少一个队列始终为空

  • push:哪个不空进哪个
  • pop:把前面所有的移入另一空队列中,最后一个出队(现在这个队列是空的了)

参考:

1. 邓俊辉. 数据结构(C++语言版)

2. Narasimha Karumanchi. Data Structures and Algorithms Made Easy: Data Structures and Algorithmic Puzzles.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值