数据结构—栈的应用举例【算术表达式求值转换、后缀表达式求值计算】

简介: 数据结构—栈的应用举例【算术表达式求值转换、后缀表达式求值计算】

栈的应用

     带你走进Java后缀表达式的世界!

e424395976104fa4b72237fa20dd0628.png

           表达式求值问题:编程实现算术表达式求值。


一、什么是算术表达式?表达式又分为那3种表示形式?

                     是由操作数、算术运算符和分隔符所组成的式子。


                     表达式一般有中缀表达式,后缀表达式和前缀表达式共3种表示形式。


                              中缀表达式:是将运算符放在两个操作数的中间。日常生活中编写的加减乘除都是中序表达式。


                             后缀表达式:也称逆波兰表达式,是将运算符放在两个操作数之后。


                             前缀表达式:是将运算符放在两个操作数之前。


------------------------------------------------------------------------


          二、请举例说明一下表达式的三种形式?

例:


中缀表达式:A+(B-C/D) * E


后缀表达式:ABCD/-E*+


前缀表达式:+A *- B/CDE


        三、计算表达式的值采用哪种表达形式好呢?


                                        由于运算符又优先级,所以中缀表达式在描述时,对计算非常不方便,特别是待括号的更麻烦。


                                        而后缀表达式中既无运算符的优先级又无括号的约束问题。在后缀表达式中运算符出现的顺序正是计算的顺序。故而使用后缀表达式更好计数。


------------------------------------------------------------------------


             四、求算术表达式值的步骤分那几步?


                                  求算术表达式的值可分为两步来进行:


                                                       第一步:将原算术表达式换成后缀表达式。


                                                       第二步:再对后缀表达式进行求值。


                       第一步:将原算术表达式转换成后缀表达式:

                                       后缀表达式中的操作数和原算术表达式的先后次序一样,只是运算符的次序不一样;则只需将转换的重点放在运算符的处理上即可。


                                       运算符的优先级表:


937b8676fd504d9e9d259a324749822d.png


注:优先级别从高到低依次用0~3的数字来表示,数字越大,表示其运算符的优先级越高。


------------------------------------------------------------------------


         五、运算符栈


                             使用一个栈来记录保留还未送往后缀表达式的运算符,称为运算符栈。


                                         入栈:新入栈的运算符比栈里的运算符优先级要高。如果是‘(’进行入栈操作。


                                         出栈:新入栈的运算符没有栈里的运算符优先级高,栈里的运算符需要出栈。如果是‘)’出栈直到‘(’


例:已知中序表达式为 8-(3+2*6)/ 5 + 2 ^ 2 ; 求后缀表达式?


解析:


12557c9214dd425eae454490a6eba3c8.png


------------------------------------------------------------------------


     六、实现转换的基本思路和规则!


                       (1) 初始化一个运算符栈


                       (2) 从算术表达式输入的字符串,从左到右读取一个字符。


                       (3) 若当前字符是操作数,则直接送往后缀表达式。


                       (4) 若当前字符是左括号“ ( ” 时,将其进行压栈到运算符栈


                        (5) 若当前字符为运算符时,则:


                                       1.当运算符栈空时,进行压栈。


                                       2.当此运算符的优先级高于栈顶运算符时,进行压栈。反之,弹出栈顶运算符送往后缀式,并将当前运算符压栈,重复步骤(5)


                        (6) 若当前字符为右括号“ )”时,反复将栈顶符号弹出,并送往后缀表达式,直到栈顶符为左括号“( ” 为止,在将左括号出栈并丢弃。


                       (7) 若还未读取完毕,则跳转到(2)


                       (8) 读取完毕,将栈这剩余的所有运算符弹出并送往后缀表达式。


ea0bb2ef720d4f37b41563e11469355b.png


      七、计算后缀表达式值的算法的基本思路!


                 第二步:计算后缀表达式的值。


                                步骤:先找到运算符——》在找前面最后出现的两个操作数——》构成一个最小算术表达式——》 一个栈来保留后缀表达式中还未参与运算的操作数【操作数栈】


                       基本思路:


                                (1) 初始化一个操作数栈


                               (2) 从左到右依次读入后缀表达式中的字符。


                                               1. 若当前字符是操作数,则压栈。


                                                2. 若当前字符是运算符,则从栈顶弹出两个操作数并分别作为第2个操作数和第1个操作数参与运算,在将运算结果进行压栈操作。


                                (3) 重复步骤(2) 直到读入的后缀表达式结束为止。即操作数栈中的栈顶元素为表达式的计算结果。


------------------------------------------------------------------------


        八、程序代码实现后缀表达式计算其值!

                        1. 链栈类【LinkStack】

package data.linear_table.stack_queue;
import data.linear_table.node.Node;
//链栈类
public class LinkStack implements IStack {
    private Node top ;      //栈顶元素的引用
    //将栈置空
    @Override
    public void clear() {
        top = null ;
    }
    //判断链栈是否为空
    @Override
    public boolean isEmpty() {
        return top == null ;
    }
    //求链栈的长度
    @Override
    public int length() {
        Node p = top ;      //初始化,p指向栈顶元素
        int length = 0 ;    //length为计数器
        //从栈顶元素开始向后查找,直到p为空
        while (p != null ) {
            p = p.next ;    //p指向后继结点
            length++ ;      //长度增加1
        }
        return length ;
    }
    //取栈顶元素并返回其值
    @Override
    public Object peek() {
        //栈如果不为空
        if (!isEmpty()) {
            //返回栈顶元素
            return top.data ;
        }else {
            return null ;
        }
    }
    //入栈
    @Override
    public void push(Object x) throws Exception {
        //构造一个新结点
        Node p = new Node(x) ;
        p.next = top ;          //p的指针域指向栈顶元素
        top = p ;               //新结点成为当前的栈顶元素
    }
    //出栈
    @Override
    public Object pop() {
        if (isEmpty()) {
            return null ;
        }else {
            Node p = top ;          //p指向被删除的结点(栈顶元素)。 用于记录被删除的结点,以便返回删除的值
            top = top.next ;        //修改链指针,使栈顶结点从链栈中移去
            return p.data ;         //返回栈顶结点的数据域值
        }
    }
    //输出栈中所有数据元素(从栈顶元素到栈底元素)
    public void display() {
        Node p = top ;          //初始化,p指向栈顶元素
        while (p != null ) {    //输出所有非空结点的数据元素值
            System.out.print(p.data.toString()+" ");
            p = p.next ;        //p指针向后移
        }
    }
}

2. 后缀表达式,计算值类 【Example3】

                          类中方法如下:

                 1、 将算术表达式转换为后缀表达式的函数,结果以字符串的形式进行返回

package data.linear_table.test;
import data.linear_table.stack_queue.LinkStack;
//将算术表达式转换为后缀表达式的函数,结果以字符串的形式进行返回
    //expression 参数为要转换的算术表达式
    public String convertToPostFix(String expression) throws Exception {
        LinkStack st = new LinkStack();     //初始化一个运算符栈
        String postfix = new String();     //用于存放输出的后缀表达式
        for (int i = 0; expression != null && i < expression.length(); i++) {
            char c = expression.charAt(i);      //从算术表达式中读取一个字符
            if (c != ' ') {         //判断c不为空格
                if (isOpenParenthesis(c)) {
                    //为开括号 '( ',压栈
                    st.push(c);
                } else if (isCloseParenthesis(c)) {
                    //为闭括号 ' ) ' ,弹出
                    char ac = (Character) st.pop();         //强制类型转换,弹出栈顶元素;
                    while (!isOpenParenthesis(ac)) {       //循环一直到ac为开括号 ( 为止
                        postfix = postfix.concat(String.valueOf(ac));       //串联到后缀表达式的结尾
                        ac = (Character) st.pop();          //继续弹出元素
                    }
                } else if (isOperator(c)) {
                    //为运算符时
                    if (!st.isEmpty()) {       //判断栈非空时,取出栈顶优先级高的运算符送往后缀表达式
                        Character ac = (Character) st.pop();
                        while (ac!=null && priority(ac.charValue()) >= priority(c)) {
                            postfix = postfix.concat(String.valueOf(ac));   //串联到后缀表达式的结尾
                            ac = (Character) st.pop();         //继续弹出
                        }
                        if (ac != null) {    //若最后一次取出的优先级低的操作符,重新压栈
                            st.push(ac);
                        }
                    }
                    st.push(c);  //压栈
                }else {
                    //为操作数
                    postfix = postfix.concat(String.valueOf(c));       //串联到后缀表达式的结尾
                }
            }
        }
        while (! st.isEmpty()) {        //栈中剩余所有的操作符,串联到后缀表达式结尾
            postfix = postfix.concat(String.valueOf(st.pop())) ;
        }
        return postfix ;
    }

 2、判断字符串是否为运算符

 //判断字符串是否为运算符
    public boolean isOperator(char c) {
        //判断c是否等于 +-*/^ %
        if ('+' == c || '-' == c || '*' == c || '/' == c || '^' == c || '%' == c) {
            return true ;
        }else {
            return false ;
        }
    }

     3、判断字符串是否为开括号 ’ (  ‘

 //判断字符串是否为运算符
    public boolean isOperator(char c) {
        //判断c是否等于 +-*/^ %
        if ('+' == c || '-' == c || '*' == c || '/' == c || '^' == c || '%' == c) {
            return true ;
        }else {
            return false ;
        }
    }

3、判断字符串是否为开括号 ’ (  ‘

 //判断字符串是否为开括号
    public boolean isOpenParenthesis (char c) {
        return '(' == c ;
    }

  4、判断字符串是否为闭括号’  )‘

 //判断字符串是否为闭括号
    public boolean isCloseParenthesis(char c) {
        return ')' == c ;
    }

  5、判断运算法的优先级

   //判断运算法的优先级
    public int priority(char c) {
        if (c == '^') {     //为幂运算
            return 3 ;
        }
        if (c == '*' || c == '/' || c == '%' ) {        //为乘除,取模运算
            return 2 ;
        }else  if (c == '+' || c == '-') {              //为加减运算
            return 1 ;
        }else {                                         //其他运算
            return 0 ;
        }
    }
}

 6、对后缀表达式进行求值计算

//对后缀表达式进行求值计算
    public double numberCalculate(String postfix) throws Exception {
        LinkStack st = new LinkStack();             //初始化一个操作数栈
        for (int i = 0 ; postfix != null && i < postfix.length(); i++ ) {
            char c = postfix.charAt(i);             //从后缀表达式中读取第一个字符
            //当c为操作符时
            if (isOperator(c)) {
                //取出两个操作数
                //st.pop出栈,将前面的两个元素取出
                double d2 = Double.valueOf(st.pop().toString());
                double d1 = Double.valueOf(st.pop().toString());
                double d3 = 0 ;
                if ('+' == c) {         //加法运算
                    d3 = d1 + d2 ;
                }else if ('-' == c) {   //减法运算
                    d3 = d1 -d2 ;
                }else if ('*' == c ) {      //乘法运算
                    d3 = d1 * d2 ;
                }else if ('/' == c ) {      //除法运算
                    d3 = d1 / d2 ;
                }else if ('^' == c ) {      //幂运算
                    d3 = Math.pow(d1,d2);
                }else if ('%' == c ) {      //取模
                    d3 = d1 % d2 ;
                }
                //将处理结果,压栈到操作数栈
                st.push(d3);
            }else {
                //为操作数时,直接压栈
                st.push(c);
            }
        }
        //返回运算结果,栈顶出栈
        return (Double) st.pop();
    }

3. 测试类【Example3_Text】

package data.linear_table.test;
public class Example3_Text {
    public static void main(String[] args) throws Exception {
        //创建后缀表达式,计算值类
        Example3 p = new Example3();
        //调用方法转换为后缀表达式
        String postfix = p.convertToPostFix("(1+2)*(5-2)/2^2+5%3");
        System.out.println("转换的后缀表达式为:"+postfix);
        //对后缀表达式求值,并输出
        System.out.println("计算后缀表达式的结果为:" + p.numberCalculate(postfix));
    }
}

4. 测试结果

40bfe61ec183488abfcea71de856b90c.png

相关文章
|
1月前
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
35 1
|
27天前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
53 5
|
26天前
|
并行计算 算法 测试技术
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
57 1
|
1月前
|
缓存 NoSQL PHP
Redis作为PHP缓存解决方案的优势、实现方式及注意事项。Redis凭借其高性能、丰富的数据结构、数据持久化和分布式支持等特点,在提升应用响应速度和处理能力方面表现突出
本文深入探讨了Redis作为PHP缓存解决方案的优势、实现方式及注意事项。Redis凭借其高性能、丰富的数据结构、数据持久化和分布式支持等特点,在提升应用响应速度和处理能力方面表现突出。文章还介绍了Redis在页面缓存、数据缓存和会话缓存等应用场景中的使用,并强调了缓存数据一致性、过期时间设置、容量控制和安全问题的重要性。
43 5
|
1月前
|
算法
数据结构之购物车系统(链表和栈)
本文介绍了基于链表和栈的购物车系统的设计与实现。该系统通过命令行界面提供商品管理、购物车查看、结算等功能,支持用户便捷地管理购物清单。核心代码定义了商品、购物车商品节点和购物车的数据结构,并实现了添加、删除商品、查看购物车内容及结算等操作。算法分析显示,系统在处理小规模购物车时表现良好,但在大规模购物车操作下可能存在性能瓶颈。
49 0
|
1月前
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
210 9
|
1月前
|
存储 算法 Java
数据结构的栈
栈作为一种简单而高效的数据结构,在计算机科学和软件开发中有着广泛的应用。通过合理地使用栈,可以有效地解决许多与数据存储和操作相关的问题。
|
1月前
|
存储 JavaScript 前端开发
执行上下文和执行栈
执行上下文是JavaScript运行代码时的环境,每个执行上下文都有自己的变量对象、作用域链和this值。执行栈用于管理函数调用,每当调用一个函数,就会在栈中添加一个新的执行上下文。
|
1月前
|
存储
系统调用处理程序在内核栈中保存了哪些上下文信息?
【10月更文挑战第29天】系统调用处理程序在内核栈中保存的这些上下文信息对于保证系统调用的正确执行和用户程序的正常恢复至关重要。通过准确地保存和恢复这些信息,操作系统能够实现用户模式和内核模式之间的无缝切换,为用户程序提供稳定、可靠的系统服务。
51 4
|
2月前
|
算法 程序员 索引
数据结构与算法学习七:栈、数组模拟栈、单链表模拟栈、栈应用实例 实现 综合计算器
栈的基本概念、应用场景以及如何使用数组和单链表模拟栈,并展示了如何利用栈和中缀表达式实现一个综合计算器。
48 1
数据结构与算法学习七:栈、数组模拟栈、单链表模拟栈、栈应用实例 实现 综合计算器

热门文章

最新文章