栈和队列深入浅出

2024-10-09 15:43:33 浏览数 (4)

一. 栈的概念及使用:

1. 概念: 栈一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈 顶,另一端称为栈底。栈中的数据元素遵守先进后出的原则。

压栈:栈的插入操作叫做进栈/压栈/入栈; 出栈 栈的 删除操作叫做出栈压栈和出栈出入数据都在栈顶如下图:

2 栈的使用

代码:

代码语言:javascript复制
public static void main(String[] args) {
Stack<Integer> 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());
}
}

3 栈的模拟实现:

1. 注意:这里的peek方法是获得;栈顶元素,当再 push时 后面的元素不会被覆盖 。而

(1)push压栈方法:把元素压入栈中

代码语言:javascript复制
public class MyStack {
    public int[] elem;
    public int usedSize;

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

    }

    //压栈
    public void push(int val) {
        if (isFull()) {
            this.elem = Arrays.copyOf(elem,2*elem.length);
        }

        this.elem[usedSize  ] = val;
    }

(2)pop出栈方法

代码语言:javascript复制
public boolean isFull() {
      return this.usedSize == elem.length;
    }


    //判空
    public boolean isEmpty() {
        return this.usedSize == 0;
    }

    //出栈->获取栈顶元素,并且元素可以被覆盖
    public int pop() {
        if (isEmpty()) {
            throw new ExceptionEmpty("栈是空的!!!");
        }

       int val = this.elem[this.usedSize-1];
        this.usedSize--;//要减减,为后续push可以覆盖元素
        return val;
    }

(3) peek方法 获取栈顶元素: 后面 再push时 后面的元素不会被覆盖

代码语言:javascript复制
 //在不删除元素前提下获取栈顶元素
    public int peek() {
        if (isEmpty()) {
            throw new ExceptionEmpty("栈是空的!!!");
        }

        int val = this.elem[this.usedSize-1];
        //不用要减减

        return val;
    }

二.栈的相关经典OJ

1. 最小栈: . - 力扣(LeetCode)

我画了超详细的图,帮助理解:

代码:

代码语言:javascript复制
class MinStack {
   public Stack<Integer> minStack;
   public Stack<Integer> stack;
    public MinStack() {
        minStack = new Stack<>();
        stack = new Stack<>();
    }
    
    //入栈
    public void push(int val) {
        stack.push(val);
        if(minStack.empty()) {
            minStack.push(val);
        }else {
            if(val <= minStack.peek()) {
                minStack.push(val);
            }
        }
    }
    
    //出栈
    public void pop() {
        if(stack.empty()) {
            return;
        }
        int popVal = stack.pop();//普通栈出栈
        //最小栈出栈
        if(popVal == minStack.peek()) {
            minStack.pop();
        }

    }

    //获取普通栈的栈顶元素
    public int top() {
        if(stack.empty()) {
            return -1;
        }
       return stack.peek();
    }
    
    //获取最小栈的栈顶元素
    public int getMin() {
        if(minStack.empty()) {
            return -1;
        }
       return minStack.peek();
    }
}

/**
 * Your MinStack object will be instantiated and called as such:
 * MinStack obj = new MinStack();
 * obj.push(val);
 * obj.pop();
 * int param_3 = obj.top();
 * int param_4 = obj.getMin();
 */

2.栈的压入、弹出序列:栈的压入、弹出序列_牛客题霸_牛客网

理解图:

代码:

代码语言:javascript复制
//栈的压入、弹出序列
    public boolean IsPopOrder (int[] pushV, int[] popV) {
        // write code here
        Stack<Integer> stack = new Stack<>();
        int j = 0;
        for(int i = 0; i < pushV.length; i  ) {
            stack.push(pushV[i]);

            /**
             * 这里注意stack.peek() == popV[j],不能写入while循环,不然栈顶元素和popV元素,
             *              不相等,会一直死循环i不会加加,就没法压入pushV的下一个元素
             */
            //注意压栈的同时,和popV[j]比较,不相同借助for循环继续往后压栈
            while(!stack.empty() && j < pushV.length &&
                    stack.peek() == popV[j]) {
                    stack.pop();
                    j  ;
            }
        }

        return stack.empty();
    }
}

3.逆波兰表达式求值:. - 力扣(LeetCode)

图解:

代码语言:javascript复制
class Solution {
    public int evalRPN(String[] tokens) {
         Stack<Integer> stack = new Stack<>();
         //遍历字符串
         for(String str : tokens) {
            if(!isOperator(str)) {
                //是字符转为数字,放入栈中
               int x = Integer.parseInt(str);//字符转为数字
               stack.push(x);
            }else {
                //是操作符,就拿出来两个操作数计算
                int val2 = stack.pop(); 
                int val1 = stack.pop();
                switch(str) {
                    case " ":
                      stack.push(val1 val2);
                    break;
                    case "-":
                      stack.push(val1-val2);
                    break;
                    case "*":
                    stack.push(val1*val2);
                    break;
                    case "/":
                    stack.push(val1/val2);
                    break;
                }
            }
         }
         
         //最后弹出栈中的元素
         return stack.pop();

    }

    private boolean isOperator(String ch) {
        if(ch.equals(" ") || ch.equals("-") || ch.equals("*") || ch.equals("/")) {
            return true;
        }
        return false;
    }
}

4. 括号匹配: . - 力扣(LeetCode)

这题画图是关键主要三种情况:要同时,满足栈为空并且 i 遍历完右括号,才是有效括号

代码:

代码语言:javascript复制
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 {

                //情况1.栈为空,但是i没有遍历完,右括号多
                if(Stack.empty()) {
                    return false;
                }

                //这里开始比较括号匹不匹配

                //情况2.(括号顺序不对),刚开始比较第一个括号就不匹配
                if(Stack.peek() == '[' && ch == ']' || ch == ')'
                && Stack.peek() == '(' || ch == '}' 
                && Stack.peek() == '{' ) {
                    Stack.pop();
                } else {
                    return false;
                }
            }
        } 

        //情况3.栈不为空,虽然i遍历完了,(左括号多)
        if(!Stack.empty()) {
            return false;
        }
        return true;
    }
}

三. 队列的概念及使用:

1. 概念 :

队列 只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表

队列具有先进先出

入队列:进行插入操作的一端称为队尾

出队列:进行删除操作的一端称为队头

2 队列的使用:

在 Java 中, Queue 是个接口, 底层是通过链表实现

注意:Queue是个接口,在实例化时必须实例化LinkedList的对象,因为LinkedList实现 Queu接口。

3. 队列模拟实现:队列的实现,可以使用 顺序结构和链式结构 ,这里我们双链表实现,时间复杂度是O(1)很方便。

offer方法;尾插入队:

代码语言:javascript复制
public class MyQueue {
   static class ListNode {
       public int val;
       public ListNode next;
       public ListNode prev;

       public ListNode(int val) {
           this.val = val;
       }

   }

   public ListNode first = null;
   public ListNode last = null;
   public int usedSize;

   public void offer (int val) {
       ListNode node = new ListNode(val);
       if (isEmpty()) {
           last = first = node;
       }else {
           //尾插
           last.next = node;
           node.prev = last;
           last = last.next;

       }
       this.usedSize  ;
   }

poll方法:删除的前提下获取队列队头

代码语言:javascript复制
 //删除前提下获取队列的队头
   public int poll() {
       if (isEmpty()){
           return -1;
       }

       int ret = first.val;
       first = first.next;

       //只删剩一个节点时,空没有prev!
       if(first != null) {
           first.prev = null;
       }
       this.usedSize--;
       return ret;
   }

peek方法:不删除的前提下获取队列队头

代码语言:javascript复制
//不删除前提下获取队列的队头
   public int peek() {
       if (isEmpty()){
           return -1;
       }

       return first.val;
   }

   public boolean isEmpty() {
       return this.usedSize == 0;
//       return fast == null;
   }
}

4.循环队列:

用顺序结构也可以 实现队列 但是普通数组,会比较浪费空间 ,这里我们 让数组循环起来就不会浪费太多空间。这里我们叫它 循环队列。

设计循环队列的oj:. - 力扣(LeetCode)

这里也自己画了理解图:这里我们区分循环队列,是否为非空用了图里说的第三种方法。浪费一个空间

代码语言:javascript复制

代码:

代码语言:javascript复制
class MyCircularQueue {
        public int Rear;
        public int Front;
        public int[] elem;
        public MyCircularQueue(int k) {
            elem = new int[k 1];
        }




       //入队列
        public boolean enQueue(int value) {
            if(isFull()) {
                return false;
            }
            elem[Rear] = value;
            Rear = (Rear 1) % elem.length;
            return true;
        }

        //出队列
        public boolean deQueue() {
            if(isEmpty()) {
                return false;
            }
            Front = (Front 1) % elem.length;
            return true;
        }

        //不删除获取队头
        public int Front() {
            if(isEmpty()) {
                return -1;
            }

            return elem[Front];
        }

        //获取队尾
        public int Rear() {
            if(isEmpty()) {
                return -1;
            }


            //Rear是插入元素遍历数组的下标
            int index = ((Rear == 0) ? (elem[elem.length-1]) : (elem[Rear-1]));

            return index;
        }

        public boolean isEmpty() {
            return Rear == Front;
        }

        public boolean isFull() {
            return (Rear 1) % elem.length == Front;
        }
  }

/**
 * Your MyCircularQueue object will be instantiated and called as such:
 * MyCircularQueue obj = new MyCircularQueue(k);
 * boolean param_1 = obj.enQueue(value);
 * boolean param_2 = obj.deQueue();
 * int param_3 = obj.Front();
 * int param_4 = obj.Rear();
 * boolean param_5 = obj.isEmpty();
 * boolean param_6 = obj.isFull();
 */

二. 队列的相关经典OJ:

1. 用队列实现栈 :. - 力扣(LeetCode)

图解:主要就是定义两个队列做到 "先进后出",这里注意入栈和出栈操作,和写peek方法时,定义一个中间变量这些细节,如图:

代码:

代码语言:javascript复制
class MyStack {
    Queue<Integer> q1;
    Queue<Integer> q2;
    public MyStack() {
        q1 = new LinkedList<>();
        q2 = new LinkedList<>();
    }
    
    //模拟入栈
    public void push(int x) {
        if(!q1.isEmpty()) {
            q1.offer(x);
        }else if(!q2.isEmpty()) {
            q2.offer(x);
        }else {
            //q1或者q2都为空,就任意放一个
            q1.offer(x);
        }
    }
    
    //模拟出栈
    public int pop() {
        if(empty()) {
            return -1;

        }else if(!q1.isEmpty()) {
            int size = q1.size();
            for(int i = 0; i < size-1; i  ) {
                q2.offer(q1.poll());
            }
            return q1.poll();
        }else {
            int size = q2.size();
            for(int i = 0; i < size-1; i  ) {
                q1.offer(q2.poll());
            }
            return q2.poll();
        }
    }
    
    //模拟获取栈顶元素
    public int top() {

        if(empty()) {
            return -1;

        }else if(!q1.isEmpty()) {
            int size = q1.size();
            //定义一个中间变量,把一个栈的元素经过中间变量
            //   全部放入另一个栈中,最后返回,最后经过中间变量的元素

            int val = 0;
            for(int i = 0; i < size; i  ) {
                val = q1.poll();
                q2.offer(val);
            }
            return val;
        }else {
            int size = q2.size();
            int val = 0;
            for(int i = 0; i < size; i  ) {
                val = q2.poll();
                q1.offer(val);
            }
            return val;
        }
    }
    
    public boolean empty() {
        
        return q1.isEmpty() && q2.isEmpty();

    }
}

/**
 * Your MyStack object will be instantiated and called as such:
 * MyStack obj = new MyStack();
 * obj.push(x);
 * int param_2 = obj.pop();
 * int param_3 = obj.top();
 * boolean param_4 = obj.empty();
 */

2. 用栈实现队列: . - 力扣(LeetCode)

图解:定义两个栈做到 "先进先出",主要就是通过两个栈把栈里的元素反过来,

代码:

代码语言:javascript复制
class MyQueue {
    public ArrayDeque<Integer> stack1;
    public ArrayDeque<Integer> stack2;

    public MyQueue() {
        stack1 = new ArrayDeque<>();
        stack2 = new ArrayDeque<>();
    }
    
    //模拟实现入队
    public void push(int x) {
        //把与元素全部放入stack1中
        stack1.push(x);
    }
    
    //模拟实现出队
    public int pop() {
        //因为是用栈模拟实现队列,所以判断一下,
          //如果两个栈为空,那么整个队列就为空
          if(empty()) {
            return -1;
          }
          
          //走到这里,stack1 不为空,stack2为空
          if(stack2.isEmpty()) {
            //不为空就把stack1全部元素放入stack2,达到元素逆置效果
          while(!stack1.isEmpty()) {
            stack2.push(stack1.pop());
          }
        }

        return stack2.pop();
    }
    
   //模拟实现获取队列队头元素(思路和模拟实现出队差不多,只是最后是eek)
    public int peek() {

        //因为是用栈模拟实现队列,所以判断一下,
          //如果两个栈为空,那么整个队列就为空
          if(empty()) {
            return -1;
          }
          
          //走到这里,stack1 不为空,stack2为空
          if(stack2.isEmpty()) {
            //不为空就把stack1全部元素放入stack2
          while(!stack1.isEmpty()) {
            stack2.push(stack1.pop());
          }
        }

        return stack2.peek();
          
    }
    
    public boolean empty() {
        return stack1.isEmpty() && stack2.isEmpty();
    }
}

/**
 * Your MyQueue object will be instantiated and called as such:
 * MyQueue obj = new MyQueue();
 * obj.push(x);
 * int param_2 = obj.pop();
 * int param_3 = obj.peek();
 * boolean param_4 = obj.empty();
 */

0 人点赞