【数据结构与算法】第八章:栈与队列相关应用完整版

简介: 前面几章纤细介绍了栈与队列的基本内容及相关操作,本章将通过三个案例对栈与队列作进一步的分析,然后分别利用栈和队列的基本操作给出案例中相关算法的具体实现。

🙊🙊作者主页:🔗求不脱发的博客

📔📔 精选专栏:🔗数据结构与算法

📋📋 精彩摘要:前面几章纤细介绍了栈与队列的基本内容及相关操作,本章将通过三个案例对栈与队列作进一步的分析,然后分别利用栈和队列的基本操作给出案例中相关算法的具体实现。

💞💞觉得文章还不错的话欢迎大家点赞👍➕收藏⭐️➕评论💬支持博主🤞


📚目录

📖【数据结构与算法】第八章:栈与队列相关应用

📝1️⃣数制的转换。

📝2️⃣括号匹配的检验。

📝3️⃣表达式求值

📝4️⃣小 结


📖【数据结构与算法】第八章:栈与队列相关应用


📝1️⃣数制的转换。

【案例描述】

       十进制数N和其他d进制数的转换是计算机实现计算的基本问题,其解决方法很多,其中一个简单算法基于下列原理:

       N = (N div d) × d + N mod d(其中,div为整除运算,mod为求余运算)

假设现要编制一个满足下列要求的程序:

    • 对于输入的任意一个非负十进制整数,输出与其等值的八进制数。

           上述计算过程是从低位到高位顺序产生八进制数的各个数位;而输出过程应从高位到低位进行,恰好和计算过程相反,因而我们可以使用栈来解决这个问题。在计算过程中依次将得到的余数压入栈中,计算完毕后,再依次弹出栈中的余数就是数制转换的结果。

    【案例分析】以十进制转化为八进制为例

           当将一个十进制整数N转换为八进制数时,在计算过程中,把N与8求余得到的八进制数的各位依次进栈,计算完毕后将栈中 的八进制数依次出栈输出,输出结果就是待求得的八进制数。

    【算法步骤】

    ① 初始化一个空栈S。

    ② 当十进制数N非零时,循环执行以下操作:

      • 把N与8求余得到的八进制数压入栈S;
      • N更新为N与8的商。

      ③ 当栈S非空时,循环执行以下操作:

        • 弹出栈顶元素e;
        • 输出e。

        【算法描述】

        void conversion(int N)
        {
            //对于任意一个非负十进制数,打印输出与其等值的八进制数
            InitStack(S); //初始化空栈S
            while(N) //当N非零时,循环
            {
                Push(S,N%8); //把N与8求余得到的八进制数压入栈S
                N=N/8; //N更新为N与8的商
            }
            while(!StackEmpty(S)) //当栈S非空时,循环
            {
                Pop(S,e); //弹出栈顶元素e
                cout<<e; //输出e
            }
        }

        image.gif

        📝2️⃣括号匹配的检验。

        【案例描述】

               假设表达式中允许包含两种括号:圆括号和方括号,其嵌套的顺序随意,即([]())或[([][])]等为正确的格式,[(]或([())或(()])均为不正确的格式。检验括号是否匹配的方法可用“期待的急迫程度”这个概念来描述。

               当计算机接受了第一个括号后,它期待着与其匹配的第八个括号的出现,然而等来的却是第二个括号,显然第二个括号的期待急迫性高于第一个括号,此时第一个括号“[”只能暂时靠边,而迫切等待与第二个括号相匹配的、第七个括号“)”的出现。类似地,因等来的是第三个括号“[”,其期待匹配的程度较第二个括号更急迫,则第二个括号也只能靠边,让位于第三个括号。在接受了第四个括号之后,第三个括号的期待得到满足,消解之后,第二个括号的期待匹配就成为当前最急迫的任务了,……,依次类推。可见,这个处理过程恰与栈的特点相吻合。每读入一个括号,若是右括号,则或者使置于栈顶的最急迫的期待得以消解,或者是不合法的情况;若是左括号,则作为一个新的更急迫的期待压入栈中,自然使原有的在栈中的所有未消解的期待的急迫性都降了一级。

        【案例分析】

               检验算法借助一个栈,每当读入一个左括号,则直接入栈,等待相匹配的同类右括号;每当读入一个右括号,若与当前栈顶的左括号类型相同,则二者匹配,将栈顶的左括号出栈,直到表达式扫描完毕。

               在处理过程中,还要考虑括号不匹配出错的情况。例如,当出现(( )[ ]))这种情况时,由于前面入栈的左括号均已和后面出现的右括号相匹配,栈已空,因此最后扫描的右括号不能得到匹配;出现[([ ])这种错误,当表达式扫描结束时,栈中还有一个左括号没有匹配;出现(( )]这种错误显然是栈顶的左括号和最后的右括号不匹配。

        【算法步骤】

        ① 初始化一个空栈S。

        ② 设置一标记性变量flag,用来标记匹配结果以控制循环及返回结果,1表示正确匹配,0表示错误匹配,flag初值为1。

        ③ 扫描表达式,依次读入字符ch,如果表达式没有扫描完毕或flag非零,则循环执行以下操作:

          • 若ch是左括号“[”或“(”,则将其压入栈;
          • 若ch是右括号“)”,则根据当前栈顶元素的值分情况考虑:若栈非空且栈顶元素是“(”,则正确匹配,否则错误匹配,flag置为0;
          • 若ch是右括号“]”,则根据当前栈顶元素的值分情况考虑:若栈非空且栈顶元素是“[”,则正确匹配,否则错误匹配,flag置为0。

          ④ 退出循环后,如果栈空且flag值为1,则匹配成功,返回true,否则返回false。

          【算法描述】

          Status Matching()
          {
              //检验表达式中所含括号是否正确匹配,如果匹配,则返回true,否则返回false
              //表达式以“# 结束
              InitStack(S); //初始化空栈
              flag=1; //标记匹配结果以控制循环及返回结果
              cin>>ch; //读入第一个字符
              while(ch!='#'&&flag) //假设表达式以“#”结尾
              {
                  switch(ch)
                  {
                      case '['||'(': //若是左括号,则将其压入栈
                          Push(S,ch);
                          break;
                      case ')': //若是“)”,则根据当前栈顶元素的值分情况考虑
                          if(!StackEmpty(S)&&GetTop(S)=='(')
                              Pop(S,x); //若栈非空且栈顶元素是“(”,则正确匹配
                          else flag=0; //若栈空或栈顶元素不是“(”,则错误失败
                          break;
                      case ']': //若是“]”,则根据当前栈顶元素的值分情况考虑
                          if(!StackEmpty(S)&&GetTop(S)=='[')
                              Pop(S,x); //若栈非空且栈顶元素是“[”,则正确匹配
                          else flag=0; //若栈空或栈顶元素不是“[”,则错误匹配
                          break;
                  } //switch
                  cin>>ch; //继续读入下一个字符
              } //while
              if(StackEmpty(S)&&flag)
                  return true; //匹配成功
              else return false; //匹配失败
          }

          image.gif


          📝3️⃣表达式求值

          【案例描述】

                 表达式求值是程序设计语言编译中的一个最基本问题,其实现是栈应用的又一个典型例子。“算符优先法”,是一种简单直观、广为使用的表达式求值算法。

          要把一个表达式翻译成正确求值的一个机器指令序列,或者直接对表达式求值,首先要能够正确解释表达式。算符优先法就是根据算术四则运算规则确定的运算优先关系,实现对表达式的编译或解释执行的。

                 在表达式计算中先出现的运算符不一定先运算,具体运算顺序是需要通过运算符优先关系的比较,确定合适的运算时机,而运算时机的确定是可以借助栈来完成的。将扫描到的不能进行运算的运算数和运算符先分别压入运算数栈和运算符栈中,在条件满足时再分别从栈中弹出进行运算。

          【案例分析】

          image.gif编辑

          任何一个表达式都是由操作数(operand)、运算符(operator)和界限符(delimiter)组成的,统称它们为单词。对于运算符以下给出运算符的优先级顺序。

          f1 \ f2 + - * / ( ) #
          + > > < < < > >
          - > > < < < > >
          * > > > > < > >
          / > > > > < > >
          ( < < < < < = x
          ) > > > > x > >

                 表中的“(”=“)”表示当左右括号相遇时,括号内的运算已经完成。为了便于实现,假设每个表达式均以“#”开始,以“#”结束。所以“#”=“#”表示整个表达式求值完毕。“)”与“(”、“#”与“)”以及“(”与“#”之间无优先关系,这是因为表达式中不允许它们相继出现,一旦遇到这种情况,则可以认为出现了语法错误。在下面的讨论中,我们暂假定所输入的表达式不会出现语法错误。

          【算法步骤】

          ① 初始化OPTR栈和OPND栈,将表达式起始符“#”压入OPTR栈。

          ② 扫描表达式,读入第一个字符ch,如果表达式没有扫描完毕至“#”或OPTR的栈顶元素不为“#”时,则循环执行以下操作:

            • 若ch不是运算符,则压入OPND栈,读入下一字符ch;
            • 若ch是运算符,则根据OPTR的栈顶元素和ch的优先级比较结果,做不同的处理:
            • 若是小于,则ch压入OPTR栈,读入下一字符ch;
            • 若是大于,则弹出OPTR栈顶的运算符,从OPND栈弹出两个数,进行相应运算,结果压入OPND栈;
            • 若是等于,则OPTR的栈顶元素是“(”且ch是“)”,这时弹出OPTR栈顶的“(”,相当于括号匹配成功,然后读入下一字符ch。

            ③ OPND栈顶元素即为表达式求值结果,返回此元素。

            【算法描述】

            char EvaluateExpression()
            {
                //算术表达式求值的算符优先算法,设OPTR和OPND分别为运算符栈和操作数栈
                InitStack(OPND); //初始化OPND栈
                InitStack(OPTR); //初始化OPTR栈
                Push(OPTR,'#'); //将表达式起始符“#”压入OPTR栈
                cin>>ch;
                while(ch!='#'||GetTop(OPTR)!='#') //表达式没有扫描完毕或OPTR的栈顶元素不为“#”
                {
                    if(!In(ch))
                    {
                        Push(OPND,ch);
                        cin>>ch;
                    } //ch不是运算符则进OPND栈
                    else
                    switch(Precede(GetTop(OPTR),ch)) //比较OPTR的栈顶元素和ch的优先级
                    {
                        case '<':
                            Push(OPTR,ch);cin>>ch; //当前字符ch压入OPTR栈,读入下一字符ch
                            break;
                        case '>':
                            Pop(OPTR,theta); //弹出OPTR栈顶的运算符
                            Pop(OPND,b);Pop(OPND,a); //弹出OPND栈顶的两个运算数
                            Push(OPND,Operate(a,theta,b)); //将运算结果压入OPND栈
                            break;
                        case '=': //OPTR的栈顶元素是“(”且ch是“)”
                            Pop(OPTR,x);cin>>ch; //弹出OPTR栈顶的“(”,读入下一字符ch
                            break;
                    } //switch
                } //while
                return GetTop(OPND); //OPND栈顶元素即为表达式求值结果
            }

            image.gif


            📝4️⃣小 结

            到这里栈和队列的基本内容就全部介绍完了,主要内容如下。

            (1)栈是限定仅在表尾进行插入或删除的线性表,又称为后进先出的线性表。栈有两种存储表示,顺序表示(顺序栈)和链式表示(链栈)。栈的主要操作是进栈和出栈,对于顺序栈的进栈和出栈操作要注意判断栈满或栈空。

            (2)队列是一种先进先出的线性表。它只允许在表的一端进行插入,而在另一端删除元素。队列也有两种存储表示,顺序表示(循环队列)和链式表示(链队)。队列的主要操作是进队和出队,对于顺序的循环队列的进队和出队操作要注意判断队满或队空。凡是涉及队头或队尾指针的修改都要将其对MAXQSIZE求模。

            (3)栈和队列是在程序设计中被广泛使用的两种数据结构,其具体的应用场景都是与其表示方法和运算规则相互联系的。表3.3分别从逻辑结构、存储结构和运算规则3方面对二者进行了比较。(4)栈有一个重要应用是在程序设计语言中实现递归。递归是程序设计中最为重要的方法之一,递归程序结构清晰,形式简洁。但递归程序在执行时需要系统提供隐式的工作栈来保存调用过程中的参数、局部变量和返回地址,因此递归程序占用内存空间较多,运行效率较低。

            相关文章
            |
            1月前
            |
            存储 算法
            非递归实现后序遍历时,如何避免栈溢出?
            后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
            37 1
            |
            1天前
            |
            算法
            【算法】栈
            栈相关算法题,供参考,附有链接地址及板书
            |
            28天前
            |
            存储 缓存 算法
            在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
            在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
            53 5
            |
            1月前
            |
            存储 算法 Java
            数据结构的栈
            栈作为一种简单而高效的数据结构,在计算机科学和软件开发中有着广泛的应用。通过合理地使用栈,可以有效地解决许多与数据存储和操作相关的问题。
            |
            1月前
            |
            存储 JavaScript 前端开发
            执行上下文和执行栈
            执行上下文是JavaScript运行代码时的环境,每个执行上下文都有自己的变量对象、作用域链和this值。执行栈用于管理函数调用,每当调用一个函数,就会在栈中添加一个新的执行上下文。
            |
            1月前
            |
            算法
            数据结构之购物车系统(链表和栈)
            本文介绍了基于链表和栈的购物车系统的设计与实现。该系统通过命令行界面提供商品管理、购物车查看、结算等功能,支持用户便捷地管理购物清单。核心代码定义了商品、购物车商品节点和购物车的数据结构,并实现了添加、删除商品、查看购物车内容及结算等操作。算法分析显示,系统在处理小规模购物车时表现良好,但在大规模购物车操作下可能存在性能瓶颈。
            49 0
            |
            2天前
            |
            机器学习/深度学习 算法
            基于改进遗传优化的BP神经网络金融序列预测算法matlab仿真
            本项目基于改进遗传优化的BP神经网络进行金融序列预测,使用MATLAB2022A实现。通过对比BP神经网络、遗传优化BP神经网络及改进遗传优化BP神经网络,展示了三者的误差和预测曲线差异。核心程序结合遗传算法(GA)与BP神经网络,利用GA优化BP网络的初始权重和阈值,提高预测精度。GA通过选择、交叉、变异操作迭代优化,防止局部收敛,增强模型对金融市场复杂性和不确定性的适应能力。
            105 80
            |
            21天前
            |
            算法
            基于WOA算法的SVDD参数寻优matlab仿真
            该程序利用鲸鱼优化算法(WOA)对支持向量数据描述(SVDD)模型的参数进行优化,以提高数据分类的准确性。通过MATLAB2022A实现,展示了不同信噪比(SNR)下模型的分类误差。WOA通过模拟鲸鱼捕食行为,动态调整SVDD参数,如惩罚因子C和核函数参数γ,以寻找最优参数组合,增强模型的鲁棒性和泛化能力。
            |
            7天前
            |
            供应链 算法 调度
            排队算法的matlab仿真,带GUI界面
            该程序使用MATLAB 2022A版本实现排队算法的仿真,并带有GUI界面。程序支持单队列单服务台、单队列多服务台和多队列多服务台三种排队方式。核心函数`func_mms2`通过模拟到达时间和服务时间,计算阻塞率和利用率。排队论研究系统中顾客和服务台的交互行为,广泛应用于通信网络、生产调度和服务行业等领域,旨在优化系统性能,减少等待时间,提高资源利用率。
            |
            15天前
            |
            存储 算法
            基于HMM隐马尔可夫模型的金融数据预测算法matlab仿真
            本项目基于HMM模型实现金融数据预测,包括模型训练与预测两部分。在MATLAB2022A上运行,通过计算状态转移和观测概率预测未来值,并绘制了预测值、真实值及预测误差的对比图。HMM模型适用于金融市场的时间序列分析,能够有效捕捉隐藏状态及其转换规律,为金融预测提供有力工具。