【数据结构】栈

127 阅读4分钟

目录

一.栈(stack)的基本概念

二.栈的使用

 三.模拟实现栈

四.栈的应用场景

 1.改变元素的序列

2.将递归转化为循环

3.逆波兰表达式求值

4.括号匹配(有效的括号)

5.栈的压入、弹出序列 

五.栈、虚拟机栈、栈帧有什么区别呢 


 

一.栈(stack)的基本概念

栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作

  • 栈顶(Top):线性表允许进行插入和删除的一端。
  • 栈底(Bottom):固定的,不允许进行插入和删除的另一端。
  • 空栈:不含任何元素。

如上图:a1为栈底元素,an为栈顶元素。由于栈只能在栈顶进行插入和删除操作,故进栈次序依次为a1,a2,... ,an 而出栈次序为an,...,a2,a1。 栈的明显的操作特征为后进先出(Last In First Out,LIFO),故又称 后进先出的线性表。

栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则,即后进来的元素先出去,先进来的元素后出去

  • 压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。
  • 出栈:栈的删除操作叫做出栈。出数据在栈顶。

注:Push为入栈,Pop为出栈 

二.栈的使用

方法功能
Stack()构造一个空的栈
E push(E e)将e入栈,并返回e
E pop()将栈顶元素出栈并返回
E peek()获取栈顶元素
int size()获取栈中有效元素个数
boolean empty()检测栈是否为空
 public static void main(String[] args) {
        Stack<Integer> stack = new Stack<>();
        stack.push(12);//入栈
        stack.push(23);
        stack.push(34);
        stack.push(45);
        stack.pop();//出栈,删除栈顶的元素
        int x = stack.peek();//获取栈顶的元素,但不删除
        
        boolean flg = stack.empty();//检测栈的元素是否为空,不为空返回false
        System.out.println(flg);
    }

 三.模拟实现栈

package MyStackDemo;

import java.util.Arrays;

public class MyStack {
    public int[] elem;
    public int usedSize;

    public MyStack() {
        this.elem = new int[5];
    }

    /**
     * 判断栈是否满了
     * @return 满了返回true,否则返回false
     */
    public boolean isFull(){
        return this.usedSize == this.elem.length;
    }
    /**
     * 入栈
     * @param val
     */
    public void push(int val){
        if(isFull()){
            //扩容
            this.elem = Arrays.copyOf(this.elem,2*this.elem.length);
        }
        this.elem[usedSize] = val;
        this.usedSize++;
    }

    /**
     * 判断栈是否为空栈
     * @return
     */
    public boolean empty(){
        return this.usedSize == 0;
    }
    /**
     * 出栈
     * @return
     */
    public int pop(){
        if(empty()){
            throw new RuntimeException("栈为空");
        }
        int ret = elem[usedSize-1];
        // 如果元素时引用类型,需要 elem[usedSize-1]=null;
        usedSize--;
        return ret;
    }

    /**
     * 获取栈顶元素但不删除
     * @return
     */
    public int peek(){
        if(empty()){
            throw new RuntimeException("栈为空");
        }
        return elem[usedSize-1];
    }

    public int getUsedSize(){
        return this.usedSize;
    }
}

  

四.栈的应用场景

 1.改变元素的序列

1. 若进栈序列为 1,2,3,4 ,进栈过程中可以出栈,则下列不可能的一个出栈序列是()
A: 1,4,3,2 B: 2,3,4,1 C: 3,1,4,2 D: 3,4,2,1

2.一个栈的初始状态为空。现将元素12345ABCDE依次入栈,然后再依次出栈,则元素出栈的顺序是( )。
A: 12345ABCDE B: EDCBA54321 C: ABCDE12345 D: 54321EDCBA

答案是: C   B

2.将递归转化为循环

比如:逆序打印链表

//递归
public void diasplay(ListNode head){
   if(head == null){
      return ;
   }
   if(head.next == null){
      System.out.println(head.val);
      return ; 
   }
   display(head.next);
   System.out.println(head.val);
}


//循环 (通过栈)
public void diasplay(ListNode head){
   if(head == null){
      return ;
   }
   Stack<ListNode> stack = new stack<>();
   // 将链表中的结点保存在栈中
   ListNode cue = head;
   // 将链表里的元素入栈
   while(cur != null){
      stack.push(cur);
      cur = cur.next;
   }
   // 将栈中的元素出栈
   while(!stack.empty()){
      ListNode p = stack.pop();
      System.out.println(p.val);
   }
}

3.逆波兰表达式求值

力扣

  • 根据 逆波兰表示法,求表达式的值。
  • 有效的算符包括 +、-、*、/ 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。
  • 注意 :两个整数之间的除法只保留整数部分。
  • 可以保证给定的逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。

     我们首先发现,token字符串构成了一个后缀表达式,这里我们有必要先来说明一下后缀表达式

和中缀表达式的转化

中缀表达式转后缀表达式

为什么转:转成后缀表达式,机器更好计算

怎么转:

  1. 先根据运算符的优先级 加括号
  2. 再把运算符号移到括号的后边(括号外)
  3. 最后再把括号去掉 

例如:
中缀表达式 a+b*c+(d*e+f)*g 转化 成 后缀表达式 abc* + de*f + g*+ 

这就是我们上面讲的步骤

  1. 先根据运算符的优先级 加括号
  2. 再把运算符号移到括号的后边(括号外)
  3. 最后再把括号去掉 

那么怎么计算后缀表达式用栈

遍历这一组数据,如果是数字,就入栈,

遇到运算符号时,弹出栈顶的两个元素,这两个数作运算

class Solution {
    public int evalRPN(String[] tokens) {
        Stack<Integer> stack = new Stack<>();
        //1.遍历这个字符串,如果是数字就入栈,不是就出栈
        for(int i = 0 ;i<tokens.length;i++){
            String x = tokens[i];
            if(!isOperation(x)){
                stack.push(Integer.parseInt(x));
            }else{
                int num2 = stack.pop();
                int num1 = stack.pop();
                switch(x){
                    case"+":
                       stack.push(num1+num2);
                       break;
                    case"-":
                       stack.push(num1-num2);
                       break;
                    case"*":
                       stack.push(num1*num2);
                       break;
                    case"/":
                       stack.push(num1/num2);
                       break;
                }
            }
        }
       return stack.pop();
    }
    public boolean isOperation(String S){
        if(S.equals("+") || S.equals("-") || S.equals("*") || S.equals("/") ){
            return true;
        }
        return false;
    }
}

注:需要强调的是

 别忘了:先出栈的时运算符的右操作数,后出栈的是左操作数

4.括号匹配(有效的括号)

力扣

给定一个只包括 ' ( ' , ' ) ',' { ', ' } ',' [ ',' ] ' 的字符串 s ,判断字符串是否有效。

有效字符串需满足:

左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。

四种情况:
不匹配: (  (  )  左括号多
(  ))右括号多
(   ]     左右括号不匹配
匹配

最后一个左括号与第一个右括号匹配 则匹配 

1.左括号入栈
2.遇到右括号就和 当前栈顶元素 相比较。如果匹配则出栈,继续遍历
字符串,不匹配则结束
3.字符串遍历完成后,栈中还有元素,那么说明左括号多
4.栈为空时,字符串还没有遍历完,那么右括号多

class Solution {
    public boolean isValid(String s) {
        Stack<Character> stack = new Stack<>();
        for(int i = 0; i<s.length();i++){
            char ch = s.charAt(i);
            if(ch == '(' || ch =='[' || ch == '{'){
                //左括号则入栈
                stack.push(ch);
            }else{
                if(stack.empty()){
                    //右括号比左括号多
                    return false;
                }else{
                    char c = stack.peek();
                    if(c=='('&&ch==')' || c=='['&&ch==']' || c=='{'&&ch=='}'){
                        stack.pop();//当前这两个括号是匹配的
                    }else{
                        //当前两个括号不匹配
                        return false;
                    }
                }
            }
        }
        if(!stack.empty()){
            //左括号比右括号多
            return false;
        }
        return true;
    }
}

5.栈的压入、弹出序列 

栈的压入、弹出序列_牛客题霸_牛客网 (nowcoder.com)

输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。 

 

这就是我们的思路 

import java.util.ArrayList;
import java.util.Stack;
public class Solution {
    public boolean IsPopOrder(int [] pushA,int [] popA) {
     if(pushA.length ==0 || popA.length ==0) return false;
        Stack<Integer> stack = new Stack<>();     
        int j = 0;//遍历popA数组
      for(int i = 0; i<pushA.length;i++){
         int val = pushA[i];
         stack.push(val);
         while(!stack.empty() && j< popA.length && stack.peek()==popA[j]){
             stack.pop();
             j++;
         }
       }
      if(stack.empty()){
          return true;
      }
        return false;
    }
}

五.栈、虚拟机栈、栈帧有什么区别呢 

  • 栈:数据结构 描述 和 组织 这个数据的
  • Java虚拟机栈:一块内存【局部变量】
  • 栈帧:函数调用的时候,在Java虚拟机栈上给这个函数开辟的一块内存空间 

 这里我们就简单描述一下啦~