数据结构—栈和队列

栈(Stack)

1.认识栈

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

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

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

image-20230407194032609

image-20230407194123956

入栈出栈遵循后进先出原则,后来的元素不出去,先前进来的元素也不能出去。

在现实中,弹匣装填子弹就是入栈,射击就是出栈,最上面的子弹是最后装进来的,如果最上面的子弹没有射击出去,下面的子弹也不能射击。

image-20230407194300640

2.栈的使用

方法功能
Stack()构造一个空的栈
E push(E e)将e入栈,并返回e
E pop()将栈顶元素出栈并返回
E peek()获取栈顶元素
int size()获取栈中有效元素个数
boolean empty()栈是否为空

image-20230407200138752


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.模拟栈

使用数组实现栈

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

    public MyStack() {
        this.elem = new int[10];
    }
    //压栈
    public void push(int val) {
        if(isFull()) {
            //扩容
            elem = Arrays.copyOf(elem,2*elem.length);
        }
        elem[usedSize++] = val;
    }
    public boolean isFull() {
        return usedSize == elem.length;
    }
    //出栈
    public int pop() {
        if(isEmpty()) {
            System.out.println("空栈");
        }
        return elem[--usedSize];
    }
    public boolean isEmpty() {
        return usedSize == 0;
    }
    public int peek() {
        if(isEmpty()) {
            System.out.println("空栈");
        }
        return elem[usedSize-1];
    }
}

【拓展】LinkedList可以作为栈使用

public static void main(String[] args) {
    LinkedList<Integer> stack = new LinkedList<>();
    //入栈
    stack.push(1);
    stack.push(2);
    stack.push(3);
    stack.push(4);
    //出栈
    System.out.println(stack.pop());
    //栈顶元素
    System.out.println(stack.peek());
}

image-20230407235653106

观察底层源码,LinkedList的push是头插,pop是删除头结点,所以符合栈的原则

4.OJ题

1.逆波兰表达式求值

力扣链接

image-20230407210629636

思路:后缀表达式转中缀表达式,数字依次压栈,遇到算术运算符,就pop()作为算数运算法右边和左边(一定是先右边,后左边),计算出值再压栈,依次这样操作,知道最后栈里面就一个数字,pop()出来就是最终的结果。
image-20230407211556388

class Solution {
    public int evalRPN(String[] tokens) {
      Stack<Integer> stack = new Stack<>();
       for(String s :tokens){
           if(!isOperation(s)){
               stack.push(Integer.valueOf(s));
           }else{
               //先取的数字放在右边
               //后取得数字放在左边
               int num2 = stack.pop();
               int num1 = stack.pop();
               switch(s){
                   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();
    }
    //判断是不是算术运算符
    private boolean isOperation(String x){
        if (x.equals("+") || x.equals("-") || x.equals("/") || x.equals("*")) {
            return true;
        }
        return false;
    }
}

2.有效的括号

力扣链接

有效字符串需满足:

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

image-20230407214209876
image-20230407220035608

最后栈为空:返回true;有一个不匹配返回false

class Solution {
    public boolean isValid(String s) {
        Stack<Character> stack =  new Stack<>();
        for(int i=0 ; i < s.length(); i++ ){
            char c = s.charAt(i);
            if(c=='{'||c=='['||c=='('){
                stack.push(c);
            }else{
                //栈空,说明没有左括号,这个时候进来了有括号,肯定不对
                 if(stack.empty()){
                    return false;
                }
                //拿栈顶元素,如果匹配,就下一次循环
                //不匹配,直接返回false
                char c1 = stack.pop();
                if(c=='}'&&c1=='{'||c==')'&&c1=='('||c==']'&&c1=='['){
                   continue;
                }else{
                    return false;
                }
            }
        }
        //遍历完了,如果栈也空了,就返回true
        if(stack.empty()){
            return true;
        }
        return false;       
    }
}

3.栈的压入、弹出序列

牛客链接

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

image-20230407221639633

思路:设pushA是压栈顺序,popA是出栈顺序。i指向pushA的第一个元素,j指向popA的第一个元素。

通过i遍历pushA中的元素,先把pushA[i]压栈,再peek()与popA中的j指向的元素进行对比,不能直接用pushA[i]与popA[j]比较,如果不一样继续操作,一样就出栈
image-20230407225115520

public class Solution {
    public boolean IsPopOrder(int [] pushA, int [] popA) {
        Stack<Integer> stack = new Stack<>();
        int j = 0;
        for (int i = 0; i < pushA.length; i++) {
            stack.push(pushA[i]);
            //不能使用if,存在连续操作
            while (j < popA.length && !stack.isEmpty() && stack.peek() == popA[j]) {
                stack.pop();
                j++;
            }
        }
        if (stack.isEmpty()) {
            return true;
        }
        return false;
    }
}

4.最小栈

力扣链接

实现 MinStack 类:

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

思路:image-20230407234333946

class MinStack {
    private Stack<Integer> stack;
    private Stack<Integer> minStack;

    public MinStack() {
        stack = new Stack<>();
        minStack = 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()) {
            int val = stack.pop();
            //维护最小栈
            if (val == minStack.peek()) {
                minStack.pop();
            }
        }
    }

    // peek
    public int top() {
        if (!stack.empty()) {
            return stack.peek();
        }
        return -1;
    }

    public int getMin() {
        return minStack.peek();
    }
}

队列(Queue)

1.认识队列

只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out) 入队列:进行插入操作的一端称为队尾(ail/Rear) 出队列:进行删除操作的一端称为队头(Head/Front)

image-20230408000837347

2.队列的使用

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

image-20230408001423446

方法功能
boolean offffer(E e)入队列
E poll()出队列
peek()获取队头元素
int size()获取队列中有效元素个数
boolean isEmpty()检测队列是否为空
public static void main(String[] args) {
	Queue<Integer> q = new LinkedList<>();
	q.offer(1);
	q.offer(2);
	q.offer(3);
	q.offer(4);
	q.offer(5); // 从队尾入队列
	System.out.println(q.size());
	System.out.println(q.peek()); // 获取队头元素
	q.poll();
	System.out.println(q.poll()); // 从队头出队列,并将删除的元素返回
	if(q.isEmpty()){
		System.out.println("队列空");
	}else{
		System.out.println(q.size());
	}
}

3.模拟队列

使用单链表实现队列:只能尾插,头删

public class MyQueue {
    static class Node {
        public int val;
        public Node next;

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

    public Node head;//头结点
    public Node last;//尾结点
    public int usedSize;

    //入队
    public void offer(int val) {
        Node node = new Node(val);
        if (head == null) {
            head = node;
            last = node;
        } else {
            last.next = node;
            last = node;
        }
        usedSize++;
    }

    public int poll() {
        if (empty()) {
            System.out.println("队列为空");
        }
        int ret = head.val;
        head = head.next;
        if (head == null) {
            last = null;//只有一个节点 那么last也要置空
        }
        usedSize--;
        return ret;
    }

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

    public int peek() {
        if (empty()) {
            System.out.println("队列为空");
        }
        return head.val;
    }

    public int getUsedSize() {
        return usedSize;
    }
}

4.循环队列

力扣链接

image-20230408093628463
image-20230408095137355
image-20230408095034017

问题:rear假设从7下标变成了0下标,这个时候算是空队列还是满队列

实现方法一

添加一个usedSize,如果usedSize=8就是满队列,usedSize=0就是空队列
image-20230408094525182

//循环队列
public class MyCircularQueue {
    //队列数据
    private int[] elems ;
    //队头指针
    private int front;
    //队尾指针
    private int rear;
    //队列元素个数
    private int size;

   public MyCircularQueue(int k) {
        elems = new int[k];
    }

    //出队
    public boolean deQueue() {
        if (isEmpty()) {
            //队列为空
            return false;
        }
        int ret = elems[front];
        front =(front+1)%elems.length;
        size--;
        return true;
    }

    //入队
    public boolean enQueue(int elem) {
        if (isFull()) {
            //队列满
            return false;
        }
        elems[rear] = elem;
        rear = (rear+1)%elems.length;
        size++;
        return true;
    }
    //获取队头元素
    public int Front() {
        if(isEmpty()) {
            return -1;
        }
        return elems[front];
    }
    //获取对尾元素
    public int Rear() {
        if(isEmpty()) {
            return -1;
        }
        int index = (rear == 0) ? elems.length-1 : rear-1;
        return elems[index];
    }
    //是否为空队列
    public boolean isEmpty() {
        return size==0;
    }
    //是否满队列
    public boolean isFull() {
       if( size == elems.length) {
            return true;
        }
        return false;
    }
}
实现方法二

牺牲一个空间,当rear=7表示满队列

image-20230408094433549

class MyCircularQueue {
    private int[] elem;
    private int front;//表示队列的头
    private int rear;//表示队列的尾

    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;
        }
        int val = elem[front];
        front = (front+1)%elem.length;
        return true;
    }
    
    public int Front() {
        if(isEmpty()){
            return -1;
        }
        return elem[front];
    }
    
    public int Rear() {
         if(isEmpty()){
            return -1;
        }
        if(rear == 0){
            return elem[elem.length-1];
        }
        return elem[rear-1];
    }
    
    public boolean isEmpty() {
        if(rear == front){
            return true;
        }
        return false;
    }
    
    public boolean isFull() {
        if((rear+1)%elem.length == front){
            return true;
        }
        return false;
    }
}

5.双端队列(Deque)

普通队列:一端进一端出

双端队列:每一端既可以进也可以出
image-20230408105710237

Deque是一个接口,使用时必须创建LinkedList的对象。

在实际工程中,使用Deque接口是比较多的,栈和队列均可以使用该接口。

Deque<Integer> stack = new ArrayDeque<>();//双端队列的线性实现
Deque<Integer> queue = new LinkedList<>();//双端队列的链式实现

6.OJ题

1.用队列实现栈

力扣链接

思路:image-20230408111728574

class MyStack {
    private Queue<Integer> qu1;
     private Queue<Integer> qu2;
    public MyStack() {
        qu1 = new LinkedList<>();
        qu2 = new LinkedList<>();
    }
    //入栈
    public void push(int x) {
        if(!qu1.isEmpty()){  
            qu1.offer(x);
        }else if(!qu2.isEmpty()){
            qu2.offer(x);
        }else{
            qu1.offer(x); 
        }

    }
    //出栈
    public int pop() {
        if(empty()){
            return -1;
        }
        if(!qu1.isEmpty()){
            int size = qu1.size() - 1 ;
            for(int i = 0 ; i < size ; i++){
                int val = qu1.poll();
                qu2.offer(val);
            }
            return qu1.poll();
        }
        if(!qu2.isEmpty()){
            int size = qu2.size() - 1 ;
            for(int i = 0 ; i < size ; i++){
                int val = qu2.poll();
                qu1.offer(val);
            }
            return qu2.poll();
        }
        return -1;
    }
    //栈顶元素
    public int top() {
         if(empty()) {
            return -1;//两个队列都为空,意味着当前的栈为空
        }
        if(!qu1.isEmpty()) {
            int size = qu1.size();
            int val = -1;
            for (int i = 0; i < size; i++) {
                val = qu1.poll();
                qu2.offer(val);
            }
            return val;
        }else {
            int size = qu2.size();
            int val = -1;
            for (int i = 0; i < size; i++) {
                val = qu2.poll();
                qu1.offer(val);
            }
            return val;
        }
    }
    //是否为空 
    public boolean empty() {
        if(qu1.isEmpty() && qu2.isEmpty()){
            return true;
        }
        return false;
    }
}
2.用栈实现队列

力扣链接

思路:在这里插入图片描述

class MyQueue {

    private Stack<Integer> stack1;
    private Stack<Integer> stack2;

    public MyQueue2() {
        stack1 = new Stack<>();
        stack2 = new Stack<>();
    }
    
    public void push(int x) {
        stack1.push(x);
    }
    
    public int pop() {
        if(empty()) {
            return -1;
        }
        if(stack2.empty()) {
            while (!stack1.empty()) {
                stack2.push(stack1.pop());
            }
        }
        return stack2.pop();
    }
    
    public int peek() {
        if(empty()) {
            return -1;
        }
        if(stack2.empty()) {
            while (!stack1.empty()) {
                stack2.push(stack1.pop());
            }
        }
        return stack2.peek();
    }
    public boolean empty() {
        return stack1.empty() && stack2.empty();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值