一文带你了解栈的基本概念以及栈的实现

一文带你了解栈的基本概念以及栈的实现_第1张图片

✏️✏️✏️今天给大家分享一下栈的基本概念、线性栈的自定义实现,以及栈的应用题目。

清风的CSDN博客

希望我的文章能对你有所帮助,有不足的地方还请各位看官多多指教,大家一起学习交流!

动动你们发财的小手,点点关注点点赞!在此谢过啦!哈哈哈!

一文带你了解栈的基本概念以及栈的实现_第2张图片

目录

一、关于栈(Stack)

1.1 栈的概念

1.2 栈的使用

 1.3 栈的模拟实现

1.3.1 栈的类定义

1.3.2 判断栈空或栈满

1.3.3 出栈

1.3.4 入栈

1.3.5 获取栈顶元素

1.3.6 获取栈中当前元素个数

二、栈的应用

2.1 后缀表达式求值

2.2 括号匹配

 2.3 最小栈

 2.4 栈的压入、弹出序列


一、关于栈(Stack)

1.1 栈的概念

一种特殊的线性表,其 只允许在固定的一端进行插入和删除元素操作 。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO( Last In First Out)原则。

 压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶

 出栈:栈的删除操作叫做出栈。出数据在栈顶

一文带你了解栈的基本概念以及栈的实现_第3张图片

一文带你了解栈的基本概念以及栈的实现_第4张图片

 栈在现实生活中的例子:

一文带你了解栈的基本概念以及栈的实现_第5张图片

一文带你了解栈的基本概念以及栈的实现_第6张图片

 上面的两个例子都遵循后进先出的原则。

1.2 栈的使用

一文带你了解栈的基本概念以及栈的实现_第7张图片

栈可以在库函数中直接调用,比如下面的代码:

public static void main(String[] args) {
Stack s = new Stack();
     s.push(1);
     s.push(2);
     s.push(3);
     s.push(4);
     System.out.println(s.size()); // 获取栈中有效元素个数---> 4
     System.out.println(s.peek()); // 获取栈顶元素---> 4
     s.pop(); // 4出栈,栈中剩余1 2 3,栈顶元素为3
     System.out.println(s.pop()); // 3出栈,栈中剩余1 2 栈顶元素为3
     if(s.empty()){
     System.out.println("栈空");
     }else{
         System.out.println(s.size());
     }
}

 1.3 栈的模拟实现

一文带你了解栈的基本概念以及栈的实现_第8张图片

从上图中可以看到, Stack 继承了 Vector Vector ArrayList 类似,都是动态的顺序表,不同的是 Vector 是线程安全的。

 所以,我们就可以利用顺序表来实现栈。

1.3.1 栈的类定义

默认栈的大小为10,也可以通过构造函数自己定义栈的大小。

public class MyStack {
    private final int DEFALUT_CAPACITY=10;
    private int[] elem;
    private int usedSize;
    public MyStack(){
        elem=new int[DEFALUT_CAPACITY];
    }

    public MyStack(int size){
        int[] elem=new int[size];
    }
}

1.3.2 判断栈空或栈满

栈空:在栈的类定义中,我们定义了一个usedSize来表示当前栈中的元素个数,因此,判断栈是否为空,只需要判断usedSize是否为0即可。

 public boolean isEmpty(){
        return usedSize==0;
    }

栈满:如果当前栈中的元素个数和数组的长度相等,那么就判栈满。

  public boolean isFull(){
        return elem.length==usedSize;
    }

1.3.3 出栈

数组的最后一个元素便是栈顶的元素,返回这个元素即可。

    public int pop(){
        if(isEmpty()){
            System.out.println("栈空!");
            return -1;
            //或者抛自定义的异常
        }
        int old=elem[usedSize-1];
        usedSize--;
        //若是引用类型:>elem[usedSize]=null;
        return old;
    }

1.3.4 入栈

    public void push(int data){
        if(isFull()){
            elem= Arrays.copyOf(elem,elem.length*2);
        }
        elem[usedSize]=data;
        usedSize++;
    }

1.3.5 获取栈顶元素

    public int peak(){
        if(isEmpty()){
            System.out.println("栈空!");
            return -1;
        }
        return elem[usedSize-1];//获取栈顶元素
    }

1.3.6 获取栈中当前元素个数

  public int size(){
        return usedSize;
    }

二、栈的应用

2.1 后缀表达式求值

后缀表达式求值的基本思路是:>当遇到的字符串是数字时,把它压入栈中,而当遇到的字符串是操作符时,从栈中弹出两个元素做对应的运算,再把运算结果压入栈中。字符串遍历完成后,栈顶元素就是计算的结果。这里需要注意,当遇到操作符要执行出栈操作是,第一次出栈的元素是计算时的右操作数,第二次出栈的元素是计算时的左操作数。

比如下面的题目:

给你一个字符串数组 tokens ,表示一个根据后缀表达式表示的算术表达式。

请你计算该表达式。返回一个表示表达式值的整数。

 根据上面的思路,我们写这个代码其实就非常简单了:>

      public int evalRPN(String[] tokens) {
        Stack stack=new Stack<>();
        for (String x:tokens) {
            if(!isOperation(x)){
                //如果不是操作符,就把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;
                }
                
            }
        }
        int ret = stack.pop();
        return ret;
    }
    private boolean isOperation(String s){
        if(s.equals("+") || s.equals("-") || s.equals("*") || s.equals("/")){
            return true;
        }
        return false;
    }

2.2 括号匹配

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

有效字符串需满足:

  • 左括号必须用相同类型的右括号闭合。
  • 左括号必须以正确的顺序闭合。
  • 每个右括号都有一个对应的相同类型的左括号。

思路:>

  • 遍历字符串,当遇到这三种左括号时,全部压入栈中。
  •  当遇到右括号时,如果当前栈空,直接返回false,因为这种情况是不可能匹配成功的。
  • 如果当前栈不空,先获取(不能直接出栈)栈顶元素,与当前的右括号进行匹配。
  • 若匹配成功,当前与之匹配的栈顶元素出栈,继续向后遍历。
  • 否则匹配不成功,返回false。
  • 当遍历完成后,只需判断当前栈是否为空,若为空,那肯定是匹配成功。
  • 若遍历完成后,当前栈非空,说明匹配失败,返回false。(说明左括号多)

下面是代码实现:>

       public boolean isValid(String s) {
        Stack 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()){
                    //如果栈不空
                    char ch2=stack.peek();//ch是右括号,ch2是左括号
                    if((ch2=='(' && ch==')') || (ch2=='{' && ch=='}') || (ch2=='[' && ch==']')){
                        //左括号出栈
                        stack.pop();
                    }else {
                        return false;
                    }
                }else {
                    return false;
                }
            }
        }
        if(!stack.empty()){
            //i已经遍历完成,栈还不为空,返回false
            return false;
        }
        return true;
    }

 2.3 最小栈

设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。

实现 MinStack 类:

  • MinStack() 初始化堆栈对象。
  • void push(int val) 将元素val推入堆栈。
  • void pop() 删除堆栈顶部的元素。(这里指普通栈)
  • int top() 获取堆栈顶部的元素。(这里指普通栈)
  • int getMin() 获取堆栈中的最小元素。

思路:>

利用两个栈来同时进行相关的操作,需要定义一个普通栈(Stack),还需要定义一个存放最小元素的栈(MinStack)。

关于入栈:>

  • 普通栈无论如何是要进行入栈操作的,那么只需要考虑最小栈是否要进行入栈操作。
  • 最小栈存放的是最小元素,所以每次普通栈进行入栈的时候,需要把当前要进入普通栈的元素(val)和在最小栈里的栈顶元素进行比较,如果val小于等于最小栈中的栈顶元素,此时最小栈也是需要执行入栈操作的。
  • 需要注意的是,在普通栈进行第一次入栈操作的时候,最小栈也是需要入栈的,也就是说,当最小栈当前为空,直接入栈即可。若最小栈非空,才需要比较大小,让小的压入最小栈。

关于出栈:> 

  •  执行出栈操作时,为了确保在获取最小元素的时候不出错,同样需要把当前从普通栈弹出的元素和最小栈中的栈顶元素比较(因为要确保最小栈存放的是当前栈的最小值)。
  • 如果普通栈中弹出的元素比最小栈中的栈顶元素大,那么普通栈弹出元素并不会影响获取当前栈中的最小元素,直接出栈即可。
  • 当普通栈中弹出元素等于(不可能小于)最小栈的栈顶元素时,这两个栈要同时执行出栈操作。(因为如果此时最小栈不弹出,并不能更新普通栈弹出元素后,此时普通栈的最小值)

下面的具体的代码实现:>

class MinStack {
    private Stack stack;
    private Stack MinStack;
    
    
    public MinStack() {
         stack=new Stack<>();
         MinStack=new Stack<>();
    }
    
    public void push(int val) {
        stack.push(val);
        if(MinStack.empty()){
            MinStack.push(val);
        }else{
            int peekVal=MinStack.peek();
            if(val<=peekVal){
                MinStack.push(val);
            }
        }
    }
    
    public void pop() {
        /**
         * pop的时候和stack的栈顶元素比较,如果相等,全部出栈
         * 不一样,只出普通栈
         */
        int val=stack.pop();
        if(!MinStack.empty()){
            if(val==MinStack.peek()){
                MinStack.pop();
            }
        }
    }
    
    public int top() {
        return stack.peek();
    }
    
    public int getMin() {
        if(!MinStack.empty()){
            return MinStack.peek();
        }
        return -1;
    }
}

 2.4 栈的压入、弹出序列

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

  •  0<=pushV.length == popV.length <=1000
  • -1000<=pushV[i]<=1000 
  • pushV 的所有数字均不相同

 思路:>

  • 遍历入栈数组,同时遍历给定的弹出序列。
  • 每次将入栈数组中的元素入栈后,就和给定的弹出序列比较。
  • 若相等,那么直接将入栈的元素弹出。
  • 遍历结束后,若栈空,说明给定的序列可以成为该栈的弹出序列。否则,返回false。 

 下面是具体的实现代码:>

    public boolean IsPopOrder (int[] pushV, int[] popV) {
        // write code here
        Stack stack = new Stack<>();
        int j = 0;
        for (int i = 0; i < pushV.length; i++) {
            stack.push(pushV[i]);
            while (!stack.empty() && j < popV.length && stack.peek() == popV[j]) {
                stack.pop();
                j++;
            }
        }
        if (stack.empty()) {
            return true;
        }
        return false;
    }

希望各位看官读完文章后,能够有所提升。

好啦,今天的分享就到这里!!

✨创作不易,还希望各位大佬支持一下!

点赞,你的认可是我创作的动力!

⭐收藏,你的青睐是我努力的方向!

✏️评论:你的意见是我进步的财富!

一文带你了解栈的基本概念以及栈的实现_第9张图片

你可能感兴趣的:(数据结构,数据结构,java)