• 数据结构与算法(三)——栈


    一、栈

    1、介绍

      栈是一种先进后出的线性表,它要求只能在表尾(栈顶)进行插入和删除操作。可以用数组或链表来实现,一般用顺序表来实现。
      栈的应用:
      ①子程序的调用:在跳往子程序前,会先将下个指令的地址存到堆栈中,直到子程序执行完后再将地址取出,以回到原来的程序中。
      ②处理递归调用:和子程序的调用类似,只是除了储存下一个指令的地址外,也将参数、区域变量等数据存入堆栈中。
      ③表达式的转换(中缀表达式转后缀表达式)与求值(实际解决)。
      ④二叉树的遍历。
      ⑤图的深度优先(depth一first)搜索。

    2、栈的顺序存储实现

      顾名思义,用顺序表的方法实现,通常用数组。栈的顺序存储结构:

      代码示例:数组实现栈

     1 // 用数组实现栈
     2 public class MyStack<T> {
     3     private T[] elementData;
     4     // 栈顶指针
     5     private int top;
     6 
     7     public MyStack(int initialCapacity) {
     8         elementData = (T[]) new Object[initialCapacity];
     9         top = 0;
    10     }
    11 
    12     // 入栈
    13     public T push(T item) {
    14         if (isFull()) {
    15             throw new RuntimeException("堆栈溢出~");
    16         }
    17 
    18         elementData[top] = item;
    19         top++;
    20 
    21         return item;
    22     }
    23 
    24     // 出栈
    25     public T pop() {
    26         if (empty()) {
    27             return null;
    28         }
    29 
    30         top--;
    31         return elementData[top];
    32     }
    33 
    34     // 读取栈顶元素
    35     public T peek() {
    36         if (empty()) {
    37             return null;
    38         }
    39 
    40         return elementData[top - 1];
    41     }
    42 
    43     // 判空
    44     public boolean empty() {
    45         return top == 0;
    46     }
    47 
    48     // 判满
    49     private boolean isFull() {
    50         return top == elementData.length;
    51     }
    52 
    53     // 栈的元素个数
    54     public int size() {
    55         int count = 0;
    56         for (int i = top - 1; i >= 0; i++) {
    57             count++;
    58         }
    59         return count;
    60     }
    61 
    62     @Override
    63     public String toString() {
    64         if (empty()) {
    65             return "[]";
    66         }
    67 
    68         StringBuilder builder = new StringBuilder();
    69         builder.append("[");
    70         for (int i = top - 1; i >= 0; i--) {
    71             builder.append(elementData[i])
    72                     .append(",");
    73         }
    74 
    75         // 去掉最后一个,
    76         builder.deleteCharAt(builder.length() - 1);
    77         builder.append("]");
    78 
    79         return builder.toString();
    80     }
    81 }
    数组实现栈

      代码示例:测试类

     1 // 测试类
     2 public class Main {
     3     public static void main(String[] args) {
     4         MyStack<Integer> stack = new MyStack<>(5);
     5 
     6         // 压栈
     7         stack.push(9);
     8         stack.push(5);
     9         stack.push(2);
    10         stack.push(7);
    11 
    12         while (!stack.empty()) {
    13             // 从栈顶弹出一个.会删除元素
    14             System.out.println(stack.pop());
    15         }
    16 
    17         // 从栈顶弹出一个.不会删除元素
    18         // stack.peek();
    19         // System.out.println(stack);
    20     }
    21 }
    22 
    23 // 结果
    24 7  2  5  9
    测试类

    3、栈的链式存储实现

      顾名思义,用链表的方法实现,通常用单链表。栈的链式储存结构:

      代码示例:单链表(不带头结点)实现栈

     1 public class MyLinkedStack<E> {
     2 
     3     // 头指针
     4     private Node<E> head;
     5 
     6     public MyLinkedStack() {
     7 
     8     }
     9 
    10     // 入栈
    11     public E push(E item) {
    12         final Node<E> node = new Node<>(item);
    13 
    14         node.next = head;
    15         head = node;
    16 
    17         return item;
    18     }
    19 
    20     // 出栈
    21     public E pop() {
    22         final Node<E> temp = head;
    23 
    24         if (temp == null) {
    25             return null;
    26         }
    27 
    28         head = head.next;
    29         return temp.item;
    30     }
    31 
    32     // 读取栈顶元素
    33     public E peek() {
    34         if (head == null) {
    35             return null;
    36         }
    37 
    38         return head.item;
    39     }
    40 
    41     // 判空
    42     public boolean empty() {
    43         return head == null;
    44     }
    45 
    46     // 栈的元素个数
    47     public int size() {
    48         int size = 0;
    49 
    50         Node<E> temp = head;
    51         while (temp != null) {
    52             size++;
    53             temp = temp.next;
    54         }
    55 
    56         return size;
    57     }
    58 
    59     @Override
    60     public String toString() {
    61         Node<E> temp = head;
    62 
    63         if (temp == null) {
    64             return "[]";
    65         }
    66 
    67         StringBuilder builder = new StringBuilder();
    68         builder.append("[");
    69 
    70         while (temp != null) {
    71             builder.append(temp.item)
    72                     .append(",");
    73 
    74             temp = temp.next;
    75         }
    76 
    77         // 去掉最后一个,
    78         builder.deleteCharAt(builder.length() - 1);
    79         builder.append("]");
    80 
    81         return builder.toString();
    82     }
    83 
    84     private static class Node<E> {
    85         public E item;
    86 
    87         public Node<E> next;
    88 
    89         public Node(E item) {
    90             this.item = item;
    91         }
    92 
    93     }
    94 }
    单链表(不带头结点)实现栈

      代码示例:测试类

     1 // 测试类
     2 public static void main(String[] args) {
     3     MyLinkedStack<Integer> stack = new MyLinkedStack<>();
     4     // 压栈
     5     stack.push(9);
     6     stack.push(5);
     7     stack.push(2);
     8     stack.push(7);
     9 
    10     while (!stack.empty()) {
    11         // 从栈顶弹出一个.会删除元素
    12         System.out.println(stack.pop());
    13     }
    14 }
    15 
    16 // 结果
    17 7  2  5  9
    测试类

    4、前缀、中缀、后缀表达式

      中缀表达式:(3 + 4)* 5 - 16
      前缀表达式(波兰表达式):- * + 3 4 5 16
      后缀表达式(逆波兰表达式):3 4 + 5 * 16 -

      ①计算前缀表达式:"- * + 3 4 5 16"

      计算流程图:注:这里仅支持多位整数计算。

      代码示例:见【类Poland】

      ②计算中缀表达式:"31+3 /1 -21"

      计算流程图:创建两个栈,一个数栈,一个符号栈。注:这里仅支持多位整数计算,且不支持括号。

      代码示例:见【类Poland】

      ③计算后缀表达式:"11 2 3 + 4 * + 5 -"

      计算流程图:注:这里仅支持多位整数计算。

      代码示例:见【类Poland】

      前缀和后缀计算流程的区别:①遍历表达式方向不同;②弹栈出来的运算符号先后不同,一个a - b,一个b - a。

      ④中缀转后缀:

      流程图:创建一个栈 stack 和一个结果存储list。注:仅支持整数和括号。

      代码示例:见【类Poland】

      代码示例:Poland

      1 // 前缀、中缀、后缀表达式
      2 public class Poland {
      3 
      4     /**
      5      * 匹配整数或小数
      6      */
      7     private static final Pattern NUM_PATTERN = Pattern.compile("^(\-|\+)?\d+(\.\d+)?$");
      8 
      9     /**
     10      * 中缀表达式转后缀表达式
     11      *
     12      * @param infixExpression 11+((2+3) * 4) - 5
     13      * @return 11 2 3 + 4 * + 5 -
     14      */
     15     public static String toSuffixExpression(String infixExpression) {
     16         infixExpression = infixExpression.replace(" ", "");
     17 
     18         Stack<String> stack = new Stack<>();
     19         List<String> result = new ArrayList<>();
     20 
     21         StringBuilder builder = new StringBuilder();
     22         // 1.遍历
     23         for (int i = 0; i < infixExpression.length(); i++) {
     24             final char c = infixExpression.charAt(i);
     25 
     26             // ①.数,就直接添加到result.注意:可能是多位数
     27             if (isNum(c)) {
     28                 builder.append(c);
     29 
     30                 // 可能会越界.有下一个且下一个是数
     31                 if (i != infixExpression.length() - 1 && isNum(infixExpression.charAt(i + 1))) {
     32                     continue;
     33                 }
     34 
     35                 result.add(builder.toString());
     36 
     37                 // 清空 builder
     38                 builder.delete(0, builder.length());
     39             } else if (c == '(') {
     40                 stack.push(String.valueOf(c));
     41             } else if (c == ')') {
     42                 // stack依次出栈添加到result中直到遇到'('
     43                 while (!"(".equals(stack.peek())) {
     44                     result.add(stack.pop());
     45                 }
     46 
     47                 // 将'('弹出,消除这对括号
     48                 stack.pop();
     49             } else {
     50                 // ②.是运算符
     51                 while (!stack.empty() && !"(".equals(stack.peek()) && priority(String.valueOf(c), stack.peek()) <= 0) {
     52                     result.add(stack.pop());
     53                 }
     54 
     55                 // 将c压栈
     56                 stack.push(String.valueOf(c));
     57             }
     58         }
     59 
     60         // 2.尾处理.将stack中剩余的运算符依次弹出加入result
     61         while (!stack.empty()) {
     62             result.add(stack.pop());
     63         }
     64 
     65         // 3.此时result已是中缀表达式.用String输出
     66         for (String s : result) {
     67             builder.append(s)
     68                     .append(" ");
     69         }
     70         // 去除最后一个空格
     71         if (builder.toString().length() != 0) {
     72             builder.deleteCharAt(builder.length() - 1);
     73         }
     74 
     75         return builder.toString();
     76     }
     77 
     78     // 计算前缀表达式(波兰表达式)
     79     public static double calculatePrefix(String prefixExpression) {
     80         final String[] list = prefixExpression.split(" ");
     81 
     82         Stack<String> stack = new Stack<>();
     83         for (int i = list.length - 1; i >= 0; i--) {
     84             if (isNum(list[i])) {
     85                 stack.push(list[i]);
     86             } else {
     87                 final double num1 = Double.parseDouble(stack.pop());
     88                 final double num2 = Double.parseDouble(stack.pop());
     89 
     90                 stack.push(String.valueOf(cal(num2, num1, list[i])));
     91             }
     92         }
     93 
     94         // 最后stack中的数据是运算结果
     95         return Double.parseDouble(stack.pop());
     96     }
     97 
     98     // 计算中缀表达式
     99     public static double calculateInfix(String infixExpression) {
    100         infixExpression = infixExpression.replace(" ", "");
    101 
    102         MyStack<Double> numStack = new MyStack<>(20);
    103         MyStack<Character> operStack = new MyStack<>(20);
    104 
    105         StringBuilder builder = new StringBuilder();
    106         for (int i = 0; i < infixExpression.length(); i++) {
    107             final char c = infixExpression.charAt(i);
    108 
    109             if (!isOper(c)) {
    110                 // 数,注意:可能是多位数
    111                 builder.append(c);
    112 
    113                 // 可能会越界.有下一个且下一个是数
    114                 if (i != infixExpression.length() - 1 && !isOper(infixExpression.charAt(i + 1))) {
    115                     continue;
    116                 }
    117 
    118                 numStack.push(Double.parseDouble(builder.toString()));
    119 
    120                 // 清空 builder
    121                 builder.delete(0, builder.length());
    122             } else {
    123                 // 符号
    124                 if (!operStack.empty() && priority(c, operStack.peek()) <= 0) {
    125                     // 注意顺序
    126                     final double num1 = numStack.pop();
    127                     final double num2 = numStack.pop();
    128 
    129                     final double cal = cal(num1, num2, operStack.pop());
    130                     numStack.push(cal);
    131                 }
    132 
    133                 // 将当前符号入栈
    134                 operStack.push(c);
    135             }
    136         }
    137 
    138         while (!operStack.empty()) {
    139             final double num1 = numStack.pop();
    140             final double num2 = numStack.pop();
    141 
    142             final double cal = cal(num1, num2, operStack.pop());
    143             numStack.push(cal);
    144         }
    145 
    146         return numStack.pop();
    147     }
    148 
    149     // 计算后缀表达式(逆波兰表达式)
    150     public static double calculateSuffix(String suffixExpression) {
    151         final String[] list = suffixExpression.split(" ");
    152 
    153         Stack<String> stack = new Stack<>();
    154         for (String s : list) {
    155             if (isDecimal(s)) {
    156                 stack.push(s);
    157             } else {
    158                 final double num1 = Double.parseDouble(stack.pop());
    159                 final double num2 = Double.parseDouble(stack.pop());
    160 
    161                 stack.push(String.valueOf(cal(num1, num2, s)));
    162             }
    163         }
    164 
    165         // 最后stack中的数据是运算结果
    166         return Double.parseDouble(stack.pop());
    167     }
    168 
    169     /**
    170      * 计算
    171      *
    172      * @param num1 数1
    173      * @param num2 数2
    174      * @param oper 运算符
    175      * @return
    176      */
    177     private static double cal(double num1, double num2, char oper) {
    178         double res = 0;
    179         switch (oper) {
    180             case '+':
    181                 res = num1 + num2;
    182                 break;
    183             case '-':
    184                 res = num2 - num1;
    185                 break;
    186             case '*':
    187                 res = num1 * num2;
    188                 break;
    189             case '/':
    190                 res = num2 / num1;
    191                 break;
    192             default:
    193                 break;
    194         }
    195         return res;
    196     }
    197 
    198     private static double cal(double num1, double num2, String oper) {
    199         return cal(num1, num2, oper.charAt(0));
    200     }
    201 
    202     /**
    203      * 判断是不是数字 int double long float
    204      */
    205     private static boolean isDecimal(String s) {
    206         return NUM_PATTERN.matcher(s).matches();
    207     }
    208 
    209     /**
    210      * 判断是不是数字
    211      *
    212      * @param val
    213      * @return
    214      */
    215     private static boolean isNum(String val) {
    216         return val.matches("\d+");
    217     }
    218 
    219     /**
    220      * 判断是不是数字
    221      *
    222      * @param val
    223      * @return
    224      */
    225     private static boolean isNum(char val) {
    226         // '0'[48] -> '9'[57]
    227         return val >= 48 && val <= 57;
    228     }
    229 
    230     /**
    231      * 判断是不是一个运算符
    232      *
    233      * @param val
    234      * @return
    235      */
    236     private static boolean isOper(char val) {
    237         return val == '+' || val == '-' || val == '*' || val == '/';
    238     }
    239 
    240     /**
    241      * 判断是不是一个符号
    242      *
    243      * @param val
    244      * @return
    245      */
    246     private static boolean isSymbol(char val) {
    247         return isOper(val) || val == '(' || val == ')';
    248     }
    249 
    250     /**
    251      * 比较两个符号的优先级
    252      *
    253      * @param oper1 符号1 + - * /
    254      * @param oper2 符号2
    255      * @return 1:符号1>符号2 ; 0:符号1=符号2 ; -1:符号1<符号2
    256      */
    257     public static int priority(char oper1, char oper2) {
    258         if (oper1 == '*' || oper1 == '/') {
    259             if (oper2 == '*' || oper2 == '/') {
    260                 return 0;
    261             }
    262             return 1;
    263         }
    264 
    265         // oper1 = + -
    266         if (oper2 == '*' || oper2 == '/') {
    267             return -1;
    268         }
    269         return 0;
    270     }
    271 
    272     /**
    273      * 比较两个符号的优先级
    274      *
    275      * @param oper1 符号1 + - * /
    276      * @param oper2 符号2
    277      * @return 1:符号1>符号2 ; 0:符号1=符号2 ; -1:符号1<符号2
    278      */
    279     public static int priority(String oper1, String oper2) {
    280         return priority(oper1.charAt(0), oper2.charAt(0));
    281     }
    282 
    283 }
    Poland表达式

      代码示例:测试类

     1 // 测试类
     2 public static void main(String[] args) {
     3     // 1.计算前缀表达式: (3+4)×5-16=19
     4     final double v = Poland.calculatePrefix("- * + 3 4 5 16");
     5     System.out.println("计算前缀表达式的结果是:" + v);
     6 
     7     // 2.计算中缀表达式: 31+3 /1 -21=13  (仅支持多位整数计算,且不支持括号)
     8     final double v1 = Poland.calculateInfix("31+3 /1 -21");
     9     System.out.println("计算中缀表达式的结果是:" + v1);
    10 
    11     // 3.中 转 后
    12     String infixExpression = "11+( (2+3) * 4) - 5";
    13     System.out.println("中缀表达式:" + infixExpression);
    14     final String suffixExpression = Poland.toSuffixExpression(infixExpression);
    15     System.out.println("中转后:" + suffixExpression);
    16 
    17     // 4.计算后缀表达式: 11+( (2+3) * 4) - 5=26
    18     final double v2 = Poland.calculateSuffix(suffixExpression);
    19     System.out.println("计算后缀表达式的结果是:" + v2);
    20 }
    21 
    22 // 结果
    23 计算前缀表达式的结果是:19.0
    24 计算中缀表达式的结果是:13.0
    25 中缀表达式:11+( (2+3) * 4) - 5
    26 中转后:11 2 3 + 4 * + 5 -
    27 计算后缀表达式的结果是:26.0
    测试类

    5、栈实现综合计算器

      综合计算器的实现会用到逆/波兰表达式,只是功能更全面,支持"+"、"-"、"*"、"/"、"(" 、")",多位整数及小数运算。
      代码示例:栈实现的综合计算器

     1 public class MyCalculator {
     2 
     3     public double calculate(String expression) {
     4         // 1.中转后
     5         final String suffixExpression = this.toSuffixExpression(expression);
     6 
     7         System.out.println(suffixExpression);
     8 
     9         // 2.计算后缀表达式
    10         return Poland.calculateSuffix(suffixExpression);
    11     }
    12 
    13     /**
    14      * 中缀表达式转后缀表达式.支持小数,多位整数
    15      *
    16      * @param infixExpression "12.8 + (2 - 3.55)*4+10/5.0"
    17      * @return "12.8 2 3.55 - 4 * + 10 5.0 / +"
    18      */
    19     private String toSuffixExpression(String infixExpression) {
    20         if (infixExpression == null || "".equals((infixExpression = infixExpression.replaceAll("\s+", "")))) {
    21             throw new RuntimeException("infixExpression is illegal!");
    22         }
    23 
    24         Stack<String> stack = new Stack<>();
    25         List<String> result = new ArrayList<>();
    26 
    27         StringBuilder builder = new StringBuilder();
    28         // 1.遍历
    29         for (int i = 0; i < infixExpression.length(); i++) {
    30             final char c = infixExpression.charAt(i);
    31 
    32             // ①.数,就直接添加到result.注意:可能是多位数 或 小数
    33             if (isDecimal(c)) {
    34                 builder.append(c);
    35 
    36                 // 可能会越界.有下一个且下一个是数
    37                 if (i != infixExpression.length() - 1 && isDecimal(infixExpression.charAt(i + 1))) {
    38                     continue;
    39                 }
    40 
    41                 result.add(builder.toString());
    42 
    43                 // 清空 builder
    44                 builder.delete(0, builder.length());
    45             } else if (c == '(') {
    46                 stack.push(String.valueOf(c));
    47             } else if (c == ')') {
    48                 // stack依次出栈添加到result中直到遇到'('
    49                 while (!"(".equals(stack.peek())) {
    50                     result.add(stack.pop());
    51                 }
    52 
    53                 // 将'('弹出,消除这对括号
    54                 stack.pop();
    55             } else {
    56                 // ②.是运算符
    57                 while (!stack.empty() && !"(".equals(stack.peek()) && Poland.priority(String.valueOf(c), stack.peek()) <= 0) {
    58                     result.add(stack.pop());
    59                 }
    60 
    61                 // 将c压栈
    62                 stack.push(String.valueOf(c));
    63             }
    64         }
    65 
    66         // 2.尾处理.将stack中剩余的运算符依次弹出加入result
    67         while (!stack.empty()) {
    68             result.add(stack.pop());
    69         }
    70 
    71         // 3.此时result已是中缀表达式.用String输出
    72         for (String s : result) {
    73             builder.append(s)
    74                     .append(" ");
    75         }
    76         // 去除最后一个空格
    77         if (builder.toString().length() != 0) {
    78             builder.deleteCharAt(builder.length() - 1);
    79         }
    80 
    81         return builder.toString();
    82     }
    83 
    84     /**
    85      * 是不是小数
    86      *
    87      * @param val
    88      * @return
    89      */
    90     private boolean isDecimal(char val) {
    91         // '0'[48] -> '9'[57]
    92         return (val >= 48 && val <= 57) || val == '.';
    93     }
    94 
    95 }
    栈实现的综合计算器

      代码示例:测试类

     1 // 测试类
     2 public static void main(String[] args) {
     3     MyCalculator calculator = new MyCalculator();
     4     final double v = calculator.calculate("12.8 + (2 - 3.55)*4+10/5.0");
     5 
     6     System.out.println("result = " + v);
     7 }
     8 
     9 // 结果
    10 12.8 2 3.55 - 4 * + 10 5.0 / +
    11 result = 8.600000000000001
    测试类

    作者:Craftsman-L

    本博客所有文章仅用于学习、研究和交流目的,版权归作者所有,欢迎非商业性质转载。

    如果本篇博客给您带来帮助,请作者喝杯咖啡吧!点击下面打赏,您的支持是我最大的动力!

  • 相关阅读:
    ubuntu给手机建wifi
    ubuntu系统之难
    【百度之星2014~复赛 解题报告~正解】The Query on the Tree
    【百度之星2014~复赛)解题报告】The Query on the Tree
    【百度之星2014~初赛解题报告】
    【百度之星2014~初赛(第二轮)解题报告】JZP Set
    【百度之星2014~资格赛解题报告】
    【百度之星2014~初赛(第二轮)解题报告】Chess
    vi 中插入当前时间
    安装软件时依赖冲突的万能解决方案
  • 原文地址:https://www.cnblogs.com/originator/p/14069776.html
Copyright © 2020-2023  润新知