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

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

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

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

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

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


📚目录

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

📝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)栈有一个重要应用是在程序设计语言中实现递归。递归是程序设计中最为重要的方法之一,递归程序结构清晰,形式简洁。但递归程序在执行时需要系统提供隐式的工作栈来保存调用过程中的参数、局部变量和返回地址,因此递归程序占用内存空间较多,运行效率较低。

            目录
            打赏
            0
            0
            0
            0
            1
            分享
            相关文章
            基于 C++ 语言的迪杰斯特拉算法在局域网计算机管理中的应用剖析
            在局域网计算机管理中,迪杰斯特拉算法用于优化网络路径、分配资源和定位故障节点,确保高效稳定的网络环境。该算法通过计算最短路径,提升数据传输速率与稳定性,实现负载均衡并快速排除故障。C++代码示例展示了其在网络模拟中的应用,为企业信息化建设提供有力支持。
            53 15
            监控局域网其他电脑:Go 语言迪杰斯特拉算法的高效应用
            在信息化时代,监控局域网成为网络管理与安全防护的关键需求。本文探讨了迪杰斯特拉(Dijkstra)算法在监控局域网中的应用,通过计算最短路径优化数据传输和故障检测。文中提供了使用Go语言实现的代码例程,展示了如何高效地进行网络监控,确保局域网的稳定运行和数据安全。迪杰斯特拉算法能减少传输延迟和带宽消耗,及时发现并处理网络故障,适用于复杂网络环境下的管理和维护。
            MapReduce在实现PageRank算法中的应用
            总结来说,在实现PageRank算法时使用MapReduce能够有效地进行大规模并行计算,并且具有良好的容错性和可扩展性。
            120 76
            员工上网行为监控中的Go语言算法:布隆过滤器的应用
            在信息化高速发展的时代,企业上网行为监管至关重要。布隆过滤器作为一种高效、节省空间的概率性数据结构,适用于大规模URL查询与匹配,是实现精准上网行为管理的理想选择。本文探讨了布隆过滤器的原理及其优缺点,并展示了如何使用Go语言实现该算法,以提升企业网络管理效率和安全性。尽管存在误报等局限性,但合理配置下,布隆过滤器为企业提供了经济有效的解决方案。
            110 8
            员工上网行为监控中的Go语言算法:布隆过滤器的应用
            基于 PHP 语言的滑动窗口频率统计算法在公司局域网监控电脑日志分析中的应用研究
            在当代企业网络架构中,公司局域网监控电脑系统需实时处理海量终端设备产生的连接日志。每台设备平均每分钟生成 3 至 5 条网络请求记录,这对监控系统的数据处理能力提出了极高要求。传统关系型数据库在应对这种高频写入场景时,性能往往难以令人满意。故而,引入特定的内存数据结构与优化算法成为必然选择。
            21 3
            从第十批算法备案通过名单中分析算法的属地占比、行业及应用情况
            2025年3月12日,国家网信办公布第十批深度合成算法通过名单,共395款。主要分布在广东、北京、上海、浙江等地,占比超80%,涵盖智能对话、图像生成、文本生成等多行业。典型应用包括医疗、教育、金融等领域,如觅健医疗内容生成算法、匠邦AI智能生成合成算法等。服务角色以面向用户为主,技术趋势为多模态融合与垂直领域专业化。
            JavaScript 中通过Array.sort() 实现多字段排序、排序稳定性、随机排序洗牌算法、优化排序性能,JS中排序算法的使用详解(附实际应用代码)
            Array.sort() 是一个功能强大的方法,通过自定义的比较函数,可以处理各种复杂的排序逻辑。无论是简单的数字排序,还是多字段、嵌套对象、分组排序等高级应用,Array.sort() 都能胜任。同时,通过性能优化技巧(如映射排序)和结合其他数组方法(如 reduce),Array.sort() 可以用来实现高效的数据处理逻辑。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
            通过Milvus内置Sparse-BM25算法进行全文检索并将混合检索应用于RAG系统
            阿里云向量检索服务Milvus 2.5版本在全文检索、关键词匹配以及混合检索(Hybrid Search)方面实现了显著的增强,在多模态检索、RAG等多场景中检索结果能够兼顾召回率与精确性。本文将详细介绍如何利用 Milvus 2.5 版本实现这些功能,并阐述其在RAG 应用的 Retrieve 阶段的最佳实践。
            通过Milvus内置Sparse-BM25算法进行全文检索并将混合检索应用于RAG系统
            企业监控软件中 Go 语言哈希表算法的应用研究与分析
            在数字化时代,企业监控软件对企业的稳定运营至关重要。哈希表(散列表)作为高效的数据结构,广泛应用于企业监控中,如设备状态管理、数据分类和缓存机制。Go 语言中的 map 实现了哈希表,能快速处理海量监控数据,确保实时准确反映设备状态,提升系统性能,助力企业实现智能化管理。
            38 3
            从集思录可转债数据探秘:Python与C++实现的移动平均算法应用
            本文探讨了如何利用移动平均算法分析集思录提供的可转债数据,帮助投资者把握价格趋势。通过Python和C++两种编程语言实现简单移动平均(SMA),展示了数据处理的具体方法。Python代码借助`pandas`库轻松计算5日SMA,而C++代码则通过高效的数据处理展示了SMA的计算过程。集思录平台提供了详尽且及时的可转债数据,助力投资者结合算法与社区讨论,做出更明智的投资决策。掌握这些工具和技术,有助于在复杂多变的金融市场中挖掘更多价值。
            66 12
            AI助理

            你好,我是AI助理

            可以解答问题、推荐解决方案等