博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

数据结构之栈

Posted on 2020-08-04 17:32  池塘鱼  阅读(173)  评论(0)    收藏  举报

一、概述

  • 作用:栈可以理解为给数据临时休息的地方,数据可以进入栈,也可以从栈中出去。
  • 特点:栈是一种基于先进后出(FILO)的数据结构,是一种只能在一端进行插入删除操作的特殊线性表。数据进入栈成为“压栈”或“入栈”,先进入的数据被压入栈底,最后进入的数据在栈顶,如果要获取数据,只能从栈顶开始弹出数据,也就是栈顶的数据先被读取,战帝的数据最后被读取。

既然是特殊的线性表,也就是既可以用顺序存储结构实现,也可以用链式存储结构实现。下面选择用链式存储结构来实现栈结构。

二、栈的实现

  • 结构:为了保证元素“先进后出”的特性,也就是元素的出栈入栈操作都在栈顶进行,头结点head作为辅助结点,始终指向栈顶元素,元素的插入删除都是在head后面的第一个元素结点位置完成。注意和传统的单向链表结构区分开。
  • 特点:先进后出。
  • 代码实现:
    package com.ex.stack;
    
    import java.util.Iterator;
    
    /**
     * 用链式结构实现栈
     */
    public class Stack<T> implements Iterable<T>{
        //首结点
        private Node<T> head;
        //元素个数
        private int N;
    
        private class Node<T> {
            //数据域
            private T item;
            //指针域
            private Node<T> next;
    
            public Node(T item, Node next) {
                this.item = item;
                this.next = next;
            }
        }
    
        //构造函数
        public Stack() {
            this.head=new Node<T>(null,null);
            this.N=0;
        }
    
        //判断栈是否为空,是则返回true,否则返回false
        public boolean isEmpty(){
            return N==0;
        }
    
        //获取栈中元素的数量
        public int size(){
            return N;
        }
    
        //元素入栈
        public void push(T t){
            //创建新结点
            //令新结点指向原第一个元素结点
            Node<T> newNode=new Node(t,head.next);
            //令首结点指向新结点
            head.next=newNode;
            //元素数+1
            N++;
        }
    
        //元素出栈
        public T pop(){
            //获取第一个元素结点
            Node<T> curr=head.next;
            //令首结点指向第二个元素结点
            if (curr!=null){
                head.next=curr.next;
            }
            //结点数-1
            N--;
            //返回弹出的元素
            return curr.item;
    
        }
    
        //以下方法和内部类均为了可以实现外部foreach遍历
    
        @Override
        public Iterator<T> iterator() {
            return new SIterator();
        }
    
        private class SIterator implements Iterator<T> {
            private Node<T> n;
    
            public SIterator() {
                this.n = head;
            }
    
            @Override
            public boolean hasNext() {
                return n.next!=null;
            }
    
            @Override
            public T next() {
                n=n.next;
                return n.item;
            }
        }
    }
    package com.ex.stack;
    
    public class StackTest {
        public static void main(String[] args) {
            Stack<Integer> stack = new Stack();
            //测试入栈
            stack.push(1);
            stack.push(2);
            stack.push(3);
            stack.push(4);
            stack.push(5);
            for (Integer val:stack) {
                System.out.println(val);
            }
            System.out.println("--------");
    
            //测试出栈
            System.out.println(stack.pop());
            //测试isEmpty()和size()
            System.out.println("isEmpty:"+stack.isEmpty());
            System.out.println("size:"+stack.size());
    
        }
    }
    View Code

     

三、栈的扩展

  1.括号匹配问题

    • 题目:给定一个字符串,里边可能包含"()"小括号和其他字符,请编写程序检查该字符串的中的小括号是否成对出现。例如:"(上海)(长安)":正确匹配,"上海(长安))":错误匹配。
    • 思路:如果是一对括号想要正确匹配,必须先使用左括号才能使用右括号。因此可以使用栈,将待匹配的左括号入栈,遇到右括号就出栈一个左括号使其配对,当检索完字符串后栈已空则说明没有剩余的左括号则配对成功,否则失败。如果在检索过程中,发现左括号不够匹配,自然也是失败。
    • 代码实现:
      package com.ex.stack;
      
      /**
       * 用栈解决括号匹配问题
       */
      public class BracketMatchingTest {
          public static void main(String[] args) {
              String str="((上海)))长安";
              boolean match = bracketMatch(str);
              System.out.println("是否正确配对:"+match);
          }
      
          private static boolean bracketMatch(String str) {
              //1.将字符串转换为数组
              char[] array = str.toCharArray();
              //2.依次检索字符,如果是左括号,就入栈,如果是右括号,就出栈一个左括号
              Stack<Character> stack=new Stack<>();
              for (int i = 0; i < array.length; i++) {
                  if (array[i]=='('){
                      stack.push('(');
                  }else if(array[i]==')'){
                      if (!stack.isEmpty()){
                          stack.pop();
                      }else {
                          //左括号不够匹配
                          return false;
                      }
                  }
              }
              //3.判断栈是否已空,如果已经空则说明括号配对正确,否则失败
              if (stack.isEmpty()){
                  return true;
              }
              //右括号不够匹配,还有剩余的左括号
              return false;
          }
      }
           

  2.逆波兰表达式

    • 题目:
      • 中缀表达式:就是我们平常生活中使用的表达式,例如:1+3*2,2-(1+3)等,它的特点是:二元运算符总是置于两个操作数中间。中缀表达式对于人们来说简单易懂可读性强,但对于计算机来说却很麻烦,不仅运算顺序不具有规律性。不同的运算符具有不同的优先级,如果计算机执行中缀表达式,需要解析表达式语义,做大量的优先级相关操作。
      • 逆波兰表达式(后缀表达式):因此以一位波兰逻辑学家提出了逆波兰表达式,它的特点是运算符总是放在跟它相关的操作数之后。例如:中缀表达式 a+(b-c)*d  的逆波兰表达为 abc-d*+
      • 题目内容:给定一个只包含加减乘除四种运算的逆波兰表达式的字符串数组,求出该逆波兰表达式的结果。
    • 思路:顺序检索字符串数组,如果当前字符串是操作数就入栈,如果是操作符就出栈两个操作数进行计算并将计算结果入栈(注意先出栈的是放在表达式的第二个位置,ab/ ——> a/b),直到检索完字符串数组做完所有计算操作,栈中会留下唯一元素即为结果。
    • 代码实现:
      package com.ex.stack;
      
      import java.util.regex.Pattern;
      
      /**
       * 用栈实现根据逆波兰表达式进行四则运算
       */
      public class ReversePolishNotationTest {
          public static void main(String[] args) {
              String str[]={"10","3","4","-","2","*","+"};
              int calculate = calculate(str);
              System.out.println("计算结果为:"+calculate);
          }
      
          /**
           * @param str 逆波兰表达式字符串
           * @return 解析逆波兰表达式为计算式,返回计算式的结果
           */
          private static int calculate(String str[]) {
              //1.遍历字符数组,如果当前字符串为操作数则入栈,如果当前字符串为运算符号则出栈两个操作数进行计算,并将结果入栈
              Stack<Integer> stack=new Stack<>();
              for (int i = 0; i < str.length; i++) {
                  //判断当前字符串是不是数字
                  Pattern pattern = Pattern.compile("^-?\\d+(\\.\\d+)?$");
                  boolean isNumber = pattern.matcher(str[i]).matches();
                  if (isNumber){
                      //如果是数字:入栈
                      stack.push(Integer.valueOf(str[i]));
                  }else {
                      //如果是运算符号:出栈两个操作数进行计算,并将结果入栈
                      //result:计算结果,bottom:后出栈的内容,更栈底的操作数,top:先出栈的内容,靠上
                      int result=0;
                      int top=stack.pop();
                      int bottom=stack.pop();
                      switch (str[i]){
                          case "+":
                              result=bottom + top;
                              break;
                          case "-":
                              result=bottom - top;
                              break;
                          case "*":
                              result=bottom * top;
                              break;
                          case "/":
                              result=bottom / top;
                              break;
                      }
                      stack.push(result);
                  }
              }
              //2.返回计算结果
              return stack.pop();
          }
      }

        

四、官方API中的栈

以JDK1.8为参考:

栈Stack<T>的底层采用Vector<T>,即线程安全的动态数组,因此Stack也是线程安全的,并且入栈出栈操作相应调用的是Vector的添加删除元素方法。