数据结构和算法六:栈

文章目录

  • 简介
  • 栈的应用场景
  • 用数组实现栈
  • 用链表实现栈
  • 使用栈实现综合计算器(后缀表达式)
  • 前缀表达式
    • 求值方式
  • 中缀表达式
  • 后缀表达式
    • 求值方式
  • 逆波兰计算器
  • 中缀表达式转后缀表达式
    • 中缀转后缀代码实现

简介

  1. 栈的英文为(stack)
  2. 栈是一个先入后出(FILO)的有序列表
  3. 栈是限制线性表中元素的插入和删除只能在线性表的同一端进行的一种特殊线性表。允许插入和删除的一段,为变化的一段,称为栈顶,另一端为固定的一段,称为栈底
  4. 根据栈的定义可知,最先放入栈中元素在栈底,最后放入的元素在栈顶,而删除元素刚好相反,最后放入的元素先删除,最先放入的元素最后删除

栈的应用场景

  1. 子程序的调用:在跳往子程序前,会先将下个指令的地址存在堆栈中,直到子程序执行完后再将地址取出,以回到原来的程序中。
  2. 处理递归调用:和子程序的调用类似,只是除了存储下一个指定的地址外,也将参数、区域变量等数据存入堆栈中
  3. 表达式的转换(中缀表达式转后缀表达式)与求值
  4. 二叉树的遍历
  5. 图形的深度优先(depth-first)搜素法

用数组实现栈

实现思路:

  1. 使用数组模拟栈
  2. 定义一个top表示栈顶,初始化为 -1
  3. 入栈的操作,当有数据加入到栈时,top++;stack[top] = data;
  4. 出栈的操作,int value = stack[top]; top–; return value;
 package com.atguigu.stack;

public class ArrayStackDemo {
    public static void main(String[] args) {
        ArrayStack arrayStack = new ArrayStack(4);
        boolean empty = arrayStack.isEmpty();
        System.out.println("isEmpty?"+empty);
        arrayStack.push(3);
        arrayStack.push(6);
        arrayStack.push(2);
        arrayStack.push(7);
        boolean full = arrayStack.isFull();
        System.out.println("isFull?" + full);
        arrayStack.show();
        while (!arrayStack.isEmpty()) {
            System.out.println(arrayStack.pop());
        }
        arrayStack.show();
    }
}

/**
 * 定义一个类表示栈
 */
class ArrayStack{
    private int maxSize;
    private int[] stack;
    private int top = -1;

    /**
     * 展示整个栈
     */
    public void show() {
        if (isEmpty()) {
            System.out.println("当前栈空,无法显示");
            return;
        }
        for (int i = top; i >= 0; i--) {
            System.out.printf("stack[%d] = %d", i, stack[i]);
            System.out.println();
        }
    }

    /**
     * 出栈
     * @return
     */
    public int pop() {
        if (isEmpty()) {
            System.out.println("当前栈空,出栈失败");
            throw new RuntimeException("当前栈空,出栈失败");
        }
        int data = stack[top];
        top--;
        return data;
    }

    /**
     * 数据入栈
     * @param data
     */
    public void push(int data) {
        if (isFull()) {
            System.out.println("当前栈满,入栈失败");
            return;
        }
        top++;
        stack[top] = data;
    }

    /**
     * 判断栈是否为空
     * @return
     */
    public boolean isEmpty() {
        return top == -1;
    }

    /**
     * 判断栈是否满
     * @return
     */
    public boolean isFull() {
        return top == maxSize - 1;
    }


    public ArrayStack(int maxSize) {
        this.maxSize = maxSize;
        stack = new int[maxSize];
    }
}

用链表实现栈

package com.atguigu.stack;

public class LinkedListStackDemo {
    public static void main(String[] args) {
        LinkedListStack linkedListStack = new LinkedListStack();
        boolean empty1 = linkedListStack.isEmpty();
        System.out.println(empty1);
        Node node = new Node(1);
        Node node1 = new Node(6);
        Node node2 = new Node(3);
        Node node3 = new Node(8);
        linkedListStack.push(node);
        linkedListStack.push(node1);
        linkedListStack.push(node2);
        linkedListStack.push(node3);
        linkedListStack.show();
        boolean empty = linkedListStack.isEmpty();
        System.out.println(empty);
    }
}

class LinkedListStack {
    private Node top = null;

    /**
     * 遍历展示栈内容
     */
    public void show() {
        if (isEmpty()) {
            System.out.println("当前栈空,无数据展示");
        }
        Node help = top;
        while (help != null) {
            System.out.println(help.getData());
            help = help.getPre();
        }
    }

    /**
     * 弹出数据
     * @return
     */
    public Node pop() {
        if (isEmpty()) {
            System.out.println("当前栈空,无法弹出");
            return null;
        }
        Node node = top;
        top = node.getPre();
        top.setNext(null);
        return node;
    }

    /**
     * 压入数据
     * @param node
     */
    public void push(Node node) {
        if (top == null) {
            top = node;
            return;
        }
        top.setNext(node);
        node.setPre(top);
        top = top.getNext();
    }

    /**
     * 判断栈是否为空
     * @return
     */
    public boolean isEmpty() {
        return top == null;
    }


}

class Node{
    private int data;
    private Node next;
    private Node pre;

    @Override
    public String toString() {
        return "Node{" +
                "data=" + data +
                '}';
    }

    public Node getPre() {
        return pre;
    }

    public void setPre(Node pre) {
        this.pre = pre;
    }

    public int getData() {
        return data;
    }

    public void setData(int data) {
        this.data = data;
    }

    public Node getNext() {
        return next;
    }

    public void setNext(Node next) {
        this.next = next;
    }

    public Node(int data) {
        this.data = data;
    }
}

使用栈实现综合计算器(后缀表达式)

实现思路:

  1. 通过一个index值,来遍历表达式
  2. 如果发现是数字,就入数栈
  3. 如果是一个符号,就分如下情况
    1. 如果符号栈为空,就直接入栈
    2. 如果符号栈不为空,就进行比较
    3. 如果当前操作符的优先级小于或者等于栈中的操作符,就需要从数栈中pop出两个数,从符号栈中pop出一个符号,进行运算,将结果入数栈,将当前的操作符入符号栈
    4. 如果当前符号的优先级大于栈中的操作符,就将当前的操作符入符号栈
  4. 当表达式扫描完毕,就顺序的从数栈和符号栈中pop出相应的数和符号,并运行
  5. 最后数栈只有一个数字,就是表达式的结果

数据结构和算法六:栈_第1张图片

package com.atguigu.stack;

import java.nio.file.Paths;

public class Calculator {
    public static void main(String[] args) {
        String expression = "3+2*6-2";
        //创建两个栈
        ArrayStack2 numStack = new ArrayStack2(10);
        ArrayStack2 operStack = new ArrayStack2(10);
        char ch = ' ';
        for (int i = 0; i < expression.length(); i++) {
            ch = expression.substring(i, i + 1).charAt(0);
            if (operStack.isOper(ch)) {
                //是运算符,先判断符号栈是否为空,为空直接入栈
                if (operStack.isEmpty()) {
                    operStack.push(ch);
                    continue;
                }
                //符号栈不为空,判断一下优先级
                //先出栈一个运算符,和当前的字符优先级进行比较
                //如果当前优先级大于出栈的运算符,当前优先级直接入栈
                //如果当前优先级小于等于出栈的运算符,就先计算,再将当前优先级入栈
                //这里面,我直接出栈了一个符号,可以增加一个方法,返回当前栈顶的数据,
                // 但不是出栈,只是看一眼
//                int pop = operStack.pop();
                if (operStack.priority(operStack.peek()) < operStack.priority(ch)) {
//                    operStack.push(pop);
                    operStack.push(ch);
                }else{
                    //从数栈出栈两个数
                    int num1 = numStack.pop();
                    int num2 = numStack.pop();
                    int pop = operStack.pop();
                    //计算
                    int cal = numStack.cal(num1, num2, (char) pop);
                    //将结果入数栈,将当前的运算符入符号栈
                    numStack.push(cal);
                    operStack.push(ch);
                }
            }else {
                //是数字,入数栈
                //这里,记得转换为int值,否则入栈的是字符串对应的ASCLL值
                numStack.push(Integer.parseInt(expression.substring(i, i + 1)));
            }
        }
        //将两个栈打印看看
        numStack.show();
        System.out.println("----------");
        //符号栈显示可能会有些奇怪,因为显示的是符号对应的ASCLL码值
        operStack.show();

        //运算符处理完毕,开始计算栈中的数据
        while (!operStack.isEmpty()) {
            //数栈出两个,符号栈出一个
            int num1 = numStack.pop();
            int num2 = numStack.pop();
            int pop = operStack.pop();
            //计算
            int cal = numStack.cal(num1, num2, (char) pop);
            numStack.push(cal);
        }
        //运算结束,数栈中有一个元素,就是运算符结果
        int result = numStack.pop();
        System.out.println(expression + "=" + result);
    }
}


/**
 * 定义一个类表示栈
 */
class ArrayStack2 {
    private int maxSize;
    private int[] stack;
    private int top = -1;

    /**
     * 偷偷看一眼栈顶,不是出栈
     * @return
     */
    public int peek() {
        return stack[top];
    }

    /**
     * 计算
     * @param num1
     * @param num2
     * @param oper
     * @return
     */
    public int cal(int num1, int num2, char oper) {
        int res = 0;
        switch (oper) {
            case '+':
                res = num2 + num1;
                break;
            case '-':
                res = num2 - num1;
                break;
            case '*':
                res = num2 * num1;
                break;
            case '/':
                res = num2 / num1;
                break;
            default:
                break;
        }
        return res;
    }

    /**
     * 判断是否是运算符
     * @param val
     * @return
     */
    public boolean isOper(char val) {
        return val == '+' || val == '-' || val == '*' || val == '/';
    }

    /**
     * 返回运算符的优先级,优先级越高,数字越大
     * @param oper
     * @return
     */
    public int priority(int oper) {
        if (oper == '*' || oper == '/') {
            return 1;
        } else if (oper == '+' || oper == '-') {
            return 0;
        }else {
            //暂时不考虑括号等运算符
            return -1;
        }
    }

    /**
     * 展示整个栈
     */
    public void show() {
        if (isEmpty()) {
            System.out.println("当前栈空,无法显示");
            return;
        }
        for (int i = top; i >= 0; i--) {
            System.out.printf("stack[%d] = %d", i, stack[i]);
            System.out.println();
        }
    }

    /**
     * 出栈
     * @return
     */
    public int pop() {
        if (isEmpty()) {
            System.out.println("当前栈空,出栈失败");
            throw new RuntimeException("当前栈空,出栈失败");
        }
        int data = stack[top];
        top--;
        return data;
    }

    /**
     * 数据入栈
     * @param data
     */
    public void push(int data) {
        if (isFull()) {
            System.out.println("当前栈满,入栈失败");
            return;
        }
        top++;
        stack[top] = data;
    }

    /**
     * 判断栈是否为空
     * @return
     */
    public boolean isEmpty() {
        return top == -1;
    }

    /**
     * 判断栈是否满
     * @return
     */
    public boolean isFull() {
        return top == maxSize - 1;
    }


    public ArrayStack2(int maxSize) {
        this.maxSize = maxSize;
        stack = new int[maxSize];
    }
}

前缀表达式

  1. 前缀表达式又称波兰表达式,前缀表达式的运算符位于操作数之前
  2. 比如:(3+4)*5-6对应的前缀表达式 - * + 3 4 5 6

求值方式

从右至左扫描表达式,遇到数字是,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对他们做相应的运算,并将结果入栈;直到表达式左端

中缀表达式

  1. 也就是常见的运算表达式 (3+4) * 5 - 6
  2. 计算机在计算时,中缀表达式很不方便,一般要转成后缀表达式

后缀表达式

  1. 又称为逆波兰表达式,运算符位于操作数之后
  2. (3+4) * 5 - 6 --> 3 4 + 5 * 6 -

求值方式

从左至右扫描,遇到数字时,将数字压入栈,遇到运算符时,弹出栈顶的两个数,运算符对他们做相应的计算,并将结果入栈。重复该操作,直到最右端。

逆波兰计算器

要求:

  1. 输入一个逆波兰表达式,使用栈计算其结果
  2. 支持小括号和多位整数,仅支持整数的计算
package com.atguigu.stack;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Stack;

public class PolandNotation {
    public static void main(String[] args) {
        //先定义一个逆波兰表达式
        //(3+4) * 5 - 6
        //为了简化操作,逆波兰表达式中的数字和符号使用空格隔开
        String suffixExpression = "3 4 + 5 * 6 -";
        //1.先将 suffixExpression 放到 ArrayList中
        //2.将ArrayList传给一个方法,配合栈完成计算
        List<String> listString = getListString(suffixExpression);
        int calculate = calculate(listString);
        System.out.println(calculate);
    }

    public static List<String> getListString(String suffixExpression) {
        //分隔字符串
        String[] split = suffixExpression.split(" ");
        ArrayList<String> strings = new ArrayList<>(Arrays.asList(split));
        return strings;
    }

    public static int calculate(List<String> list) {
        Stack<Integer> numStack = new Stack<>();
        int num1;
        int num2;
        for (String str : list) {
            if (str.matches("\\d+")) {
                numStack.push(Integer.parseInt(str));
            } else {
                num1 = numStack.pop();
                num2 = numStack.pop();
                int result = 0;
                switch (str.charAt(0)) {
                    case '+':
                        result = num2 + num1;
                        break;
                    case '-':
                        result = num2 - num1;
                        break;
                    case '*':
                        result = num2 * num1;
                        break;
                    case '/':
                        result = num2 / num1;
                        break;
                    default:
                        break;
                }
                numStack.push(result);
            }
        }
        return numStack.pop();
    }
}

中缀表达式转后缀表达式

步骤:

  1. 初始化两个栈:运算符栈s1和存储中间结果的栈s2
  2. 从左至右扫描整个中缀表达式
  3. 遇到操作数时,将其压入s2
  4. 遇到运算符时,比较其与s1栈顶运算符的优先级:
    1. 如果s1为空,或栈顶运算符为左括号"(",则直接将此运算符入栈
    2. 如果优先级比栈顶优先级高,则直接将运算符入栈
    3. 否则,将s1栈顶的运算符弹出并压入到s2中,再次转到(4-1)与s1中新的栈顶运算符相比较
  5. 遇到括号时:
    1. 如果是左括号"(",则直接压入s1
    2. 如果是右括号")",则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止,此时将这一对括号丢弃
  6. 重复2~5步,直到表达式的最右边
  7. 将s1中剩余的运算符一次弹出并压入s2
  8. 依次弹出s2中的元素并输出,结果的逆序即为中缀表达式对应的后缀表达式

中缀转后缀代码实现

package com.atguigu.stack;

import java.util.*;

public class PolandNotation {
    public static void main(String[] args) {
        //中缀表达式转后缀表达式
        String expression = "1+((2+3)*4)-5";
        List<String> strings = toInfixExpressionList(expression);
        System.out.println(strings);
        List<String> strings1 = parseSuffixExpressionList(strings);
        System.out.println(strings1);
        int calculate1 = calculate(strings1);
        System.out.println(expression + "=" + calculate1);


        //先定义一个逆波兰表达式
        //(3+4) * 5 - 6
        //为了简化操作,逆波兰表达式中的数字和符号使用空格隔开
        String suffixExpression = "3 4 + 5 * 6 -";
        //1.先将 suffixExpression 放到 ArrayList中
        //2.将ArrayList传给一个方法,配合栈完成计算
        List<String> listString = getListString(suffixExpression);
        int calculate = calculate(listString);
        System.out.println(calculate);
    }


    /**
     * 将中缀表达式转换为后缀表达式
     * @param list
     * @return
     */
    public static List<String> parseSuffixExpressionList(List<String> list) {
        Stack<String> s1 = new Stack<>();
        //数字栈在转换过程中没有pop操作,并且后面要逆序输出,因此直接使用ArrayList即可
        ArrayList<String> resultList = new ArrayList<>();
        for (String item : list) {
            //如果是数字,加入链表
            if (item.matches("\\d+")) {
                resultList.add(item);
            } else if (item.equals("(")) {
                s1.push(item);
            } else if (item.equals(")")) {
                //如果是右括号,依次弹出s1中的符号,直到遇到一个左括号,弹出的符号加入list中
                while (!s1.peek().equals("(")) {
                    resultList.add(s1.pop());
                }
                s1.pop();
            }else {
                //判断item和栈顶的优先级
                //当item的优先级,小于等于s1栈顶的优先级,将s1栈顶的运算符弹出加入list中
                while (!s1.isEmpty() && Operation.getValue(s1.peek()) >= Operation.getValue(item)) {
                    resultList.add(s1.pop());
                }
                //将item压入s1
                s1.push(item);
            }
        }
        //将s1中剩下的元素加入到list中
        while (!s1.isEmpty()) {
            resultList.add(s1.pop());
        }
        return resultList;
    }



    /**
     * 中缀表达式扫描,获取一个list
     * @param s
     * @return
     */
    public static List<String> toInfixExpressionList(String s) {
        ArrayList<String> list = new ArrayList<>();
        StringBuilder sb = new StringBuilder();
        char c;
        for (int index = 0; index < s.length(); index++) {
            c = s.charAt(index);
            //判断c是否是一个数字
            if (c < 48 || c > 57) {
                //不是一个数字
                list.add(c + "");
            }else{
                //是一个数字,考虑多位数的问题
                sb.delete(0, sb.length());
                while (index < s.length() && (c = s.charAt(index)) >= 48 && (c = s.charAt(index)) <= 57) {
                    sb.append(c);
                    index++;
                }
                index--;
                list.add(sb.toString());
            }
        }
        return list;
    }


    /**
     * 字符串转list
     * @param suffixExpression
     * @return
     */
    public static List<String> getListString(String suffixExpression) {
        //分隔字符串
        String[] split = suffixExpression.split(" ");
        ArrayList<String> strings = new ArrayList<>(Arrays.asList(split));
        return strings;
    }

    /**
     * 计算中缀表达式的值
     * @param list
     * @return
     */
    public static int calculate(List<String> list) {
        Stack<Integer> numStack = new Stack<>();
        int num1;
        int num2;
        for (String str : list) {
            if (str.matches("\\d+")) {
                numStack.push(Integer.parseInt(str));
            } else {
                num1 = numStack.pop();
                num2 = numStack.pop();
                int result = 0;
                switch (str.charAt(0)) {
                    case '+':
                        result = num2 + num1;
                        break;
                    case '-':
                        result = num2 - num1;
                        break;
                    case '*':
                        result = num2 * num1;
                        break;
                    case '/':
                        result = num2 / num1;
                        break;
                    default:
                        break;
                }
                numStack.push(result);
            }
        }
        return numStack.pop();
    }
}

/**
 * 定义一个类,返回运算符对应的优先级
 */
class Operation {
    private static int ADD = 1;
    private static int SUB = 1;
    private static int MUL = 2;
    private static int DIV = 2;

    public static int getValue(String operation) {
        int result = 0;
        switch (operation) {
            case "+":
                result = ADD;
                break;
            case "-":
                result = SUB;
                break;
            case "*":
                result = MUL;
                break;
            case "/":
                result = DIV;
                break;
            default:
                break;
        }
        return result;
    }
}

你可能感兴趣的:(数据结构和算法)