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

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

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

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

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

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


📚目录

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

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

            相关文章
            |
            2月前
            |
            存储 算法
            非递归实现后序遍历时,如何避免栈溢出?
            后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
            44 1
            |
            12天前
            |
            存储 C语言 C++
            【C++数据结构——栈与队列】顺序栈的基本运算(头歌实践教学平台习题)【合集】
            本关任务:编写一个程序实现顺序栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 1.初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储
            128 75
            |
            12天前
            |
            存储 C++ 索引
            【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
            【数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】初始化队列、销毁队列、判断队列是否为空、进队列、出队列等。本关任务:编写一个程序实现环形队列的基本运算。(6)出队列序列:yzopq2*(5)依次进队列元素:opq2*(6)出队列序列:bcdef。(2)依次进队列元素:abc。(5)依次进队列元素:def。(2)依次进队列元素:xyz。开始你的任务吧,祝你成功!(4)出队一个元素a。(4)出队一个元素x。
            34 13
            【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
            |
            12天前
            |
            存储 C语言 C++
            【C++数据结构——栈与队列】链栈的基本运算(头歌实践教学平台习题)【合集】
            本关任务:编写一个程序实现链栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储整数,最大
            35 9
            |
            12天前
            |
            C++
            【C++数据结构——栈和队列】括号配对(头歌实践教学平台习题)【合集】
            【数据结构——栈和队列】括号配对(头歌实践教学平台习题)【合集】(1)遇到左括号:进栈Push()(2)遇到右括号:若栈顶元素为左括号,则出栈Pop();否则返回false。(3)当遍历表达式结束,且栈为空时,则返回true,否则返回false。本关任务:编写一个程序利用栈判断左、右圆括号是否配对。为了完成本关任务,你需要掌握:栈对括号的处理。(1)遇到左括号:进栈Push()开始你的任务吧,祝你成功!测试输入:(()))
            29 7
            |
            2月前
            |
            存储 缓存 算法
            在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
            在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
            87 5
            |
            2月前
            |
            存储 算法 Java
            数据结构的栈
            栈作为一种简单而高效的数据结构,在计算机科学和软件开发中有着广泛的应用。通过合理地使用栈,可以有效地解决许多与数据存储和操作相关的问题。
            103 21
            |
            2月前
            |
            并行计算 算法 测试技术
            C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
            C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
            81 1
            |
            2月前
            |
            缓存 NoSQL PHP
            Redis作为PHP缓存解决方案的优势、实现方式及注意事项。Redis凭借其高性能、丰富的数据结构、数据持久化和分布式支持等特点,在提升应用响应速度和处理能力方面表现突出
            本文深入探讨了Redis作为PHP缓存解决方案的优势、实现方式及注意事项。Redis凭借其高性能、丰富的数据结构、数据持久化和分布式支持等特点,在提升应用响应速度和处理能力方面表现突出。文章还介绍了Redis在页面缓存、数据缓存和会话缓存等应用场景中的使用,并强调了缓存数据一致性、过期时间设置、容量控制和安全问题的重要性。
            55 5
            |
            2月前
            |
            存储 JavaScript 前端开发
            执行上下文和执行栈
            执行上下文是JavaScript运行代码时的环境,每个执行上下文都有自己的变量对象、作用域链和this值。执行栈用于管理函数调用,每当调用一个函数,就会在栈中添加一个新的执行上下文。

            热门文章

            最新文章