【数据结构-4】 队列与栈

学习目标:

掌握队列和栈


学习内容:

  1. 队列

队列

队列是一种先进先出(FIFO)的数据结构,常用于任务调度、广度优先搜索等。Java提供了Queue接口,以及LinkedList和ArrayDeque等实现。

常用队列包括:

  • Queue(接口):提供add/offer,element/peak,remove/poll等方法。
  • Deque(接口):双向队列。队列两端的元素既能入队(offer)也能出队(poll),如果将Deque限制为只能从一端入队和出队,则可实现栈的数据结构。如果固定一端进另一端出的话,那么它就相当于一个普通的队列。
  • LinkedList:链表结构,实现了List,Deque,Cloneable,Serializable等接口。
  • ArrayDeque:数组结构,实现了Deque,Cloneable,Serializable等接口。
  • PriorityQueue:优先队列,优先队列的作用是保证每次取出的元素都是队列中权重最小的。

双端队列

Deque是一个双端队列接口,继承自Queue接口,Deque的实现类是LinkedList、ArrayDeque、LinkedBlockingDeque,其中LinkedList是最常用的。

  • 普通队列(一端进另一端出):
    Queue queue = new LinkedList()或Deque deque = new LinkedList()

  • 双端队列(两端都可进出)
    Deque deque = new LinkedList()

  • 堆栈
    Deque deque = new LinkedList()

第一个元素 (头部)最后一个元素 (尾部)
抛出异常特殊值抛出异常特殊值
插入addFirst(e)offerFirst(e)addLast(e)offerLast(e)
删除removeFirst()pollFirst()removeLast()pollLast()
检查getFirst()peekFirst()getLast()peekLast()

例:利用双端队列Z字层序遍历二叉树

    public static List<List<Integer>> zigzagLevelOrder(TreeNode root) {
        List<List<Integer>> ret = new ArrayList<>();
        if (root == null) {
            return ret;
        }

        LinkedList<TreeNode> queue = new LinkedList<>();
        queue.add(root);
        int c1 = 1;//当前层节点数
        boolean odd = true;
        while (!queue.isEmpty()) {
            int c2 = 0;//下一层数量
            LinkedList<Integer> level = new LinkedList<>();
            for (int i = 0; i < c1; i++) {
                TreeNode n = queue.poll();
                if(odd){
                    level.offerLast(n.val);
                }else {
                    level.offerFirst(n.val);
                }

                if (n.left != null) {
                    queue.offer(n.left);
                    c2++;
                }
                if (n.right != null) {
                    queue.offer(n.right);
                    c2++;
                }
            }
            odd = !odd;
            ret.add(level);
            c1 = c2;
        }
        return ret;
    }


优先级队列

Java集合框架中提供了PriorityQueuePriorityBlockingQueue两种类型的优先级队列,PriorityQueue是线程不安全的,PriorityBlockingQueue是线程安全的,这里主要使用PriorityQueue,PriorityQueue的底层是堆。优先级队列(PriorityQueue)提供两个最基本的操作,一个是返回最高优先级对象,一个是添加新对象。

  • 使用时必须导入 PriorityQueue 所在的包 -> import java.util.PriorityQueue
  • PriorityQueue中放置的元素必须要能够比较大小
  • 不能插入 null 对象,否则会抛出 NullPointerException 异常
  • 没有容量限制,可以插入任意多个元素,其内部可以自动扩容
  • 插入和删除元素的时间复杂度均为 O(log2N)

创建优先级队列

    public static void main(String[] args) {
        //默认底层容量是11
        PriorityQueue<Integer> queue1 = new PriorityQueue<>();

        //initialCapacity是50
        PriorityQueue<Integer> queue2 = new PriorityQueue<>(50);

        //用集合创建一个优先级队列对象
        ArrayList<Integer> list = new ArrayList<>();

        list.add(4);
        list.add(0);
        list.add(2);
        list.add(3);
        //默认是小顶堆
        PriorityQueue<Integer> queue3 = new PriorityQueue<>(list);
        System.out.println(queue3.peek());
        System.out.println(queue3.size());
    }

可以改成大顶堆

	PriorityQueue<Integer> queue3 = new PriorityQueue<>((a,b)->Integer.Compare(b,a));
    queue3.addAll(list);

阻塞队列

阻塞队列(BlockingQueue) 是一个支持两个附加操作的队列。这两个附加的操作是:在队列为空时,获取元素的线程会等待队列变为非空当队列满时,存储元素的线程会等待队列可用。阻塞队列常用于生产者和消费者的场景,生产者是往队列里添加元素的线程,消费者是从队列里拿元素的线程。阻塞队列就是生产者存放元素的容器,而消费者也只从容器里拿元素。

  • ArrayBlockingQueue : 一个由数组结构组成的有界阻塞队列。
  • LinkedBlockingQueue : 一个由链表结构组成的有界阻塞队列。
  • PriorityBlockingQueue : 一个支持优先级排序的无界阻塞队列。
  • DelayQueue: 一个使用优先级队列实现的无界阻塞队列。
  • SynchronousQueue: 一个不存储元素的阻塞队列。
  • LinkedTransferQueue: 一个由链表结构组成的无界阻塞队列。
  • LinkedBlockingDeque: 一个由链表结构组成的双向阻塞队列。

堆栈

堆栈是一种后进先出(LIFO)的数据结构,常用于实现撤销操作、表达式求值等。Java堆栈Stack类已经过时,Java官方推荐使用Deque替代Stack使用。Deque堆栈操作方法:push()、pop()、peek()。

例1:判断有效括号

  public static boolean isValid(String s) {
        LinkedList<Character> stack = new LinkedList<>();
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            if (c == '(') stack.push(')');
            else if (c == '[') stack.push(']');
            else if (c == '{') stack.push('}');
            else if (c == ')' || c == ']' || c == '}') {
                if (!stack.isEmpty() && c == stack.peek()) {
                    stack.pop();
                } else return false;
            }
        }
        return stack.isEmpty();
    }

例2:逆波兰表达式

    public static int evalRPN(String[] tokens) {
        LinkedList<Character> stack = new LinkedList<>();
        for (String token : tokens) {
            switch (token) {
                case "+" -> {
                    Integer b = stack.pop();
                    Integer a = stack.pop();
                    stack.push(a + b);
                }
                case "-" -> {
                    Integer b = stack.pop();
                    Integer a = stack.pop();
                    stack.push(a - b);
                }
                case "*" -> {
                    Integer b = stack.pop();
                    Integer a = stack.pop();
                    stack.push(a * b);
                }
                case "/" -> {
                    Integer b = stack.pop();
                    Integer a = stack.pop();
                    stack.push(a / b);
                }
                default -> {
                    stack.push(Integer.parseInt(token));
                }
            }
        }
        return stack.pop();
    }

3.中缀转后缀

思路:
1.遇到非运算符,直接拼串
2.遇到±*/ 则判断优先级是否比栈顶运算符高:
- 如果比栈顶高:入栈
- 如果没有栈顶高:把栈里优先级>=它的全出栈,再入栈
3.遍历完成,栈里剩余运算符依次出栈
4.带()
- 遇到 ( 直接入栈,优先级为0
- 遇到 ) 把到 ( 为止的所有运算符出栈

    public static int priority(char c) {
        return switch (c) {
            case '*', '/' -> 2;
            case '+', '-' -> 1;
            case '(' ->0;
            default -> throw new IllegalArgumentException("不合法运算符" + c);
        };
    }

    public static String infixToSuffix(String exp) {
        LinkedList<Character> stack = new LinkedList<>();
        StringBuilder sb = new StringBuilder(exp.length());
        for (int i = 0; i < exp.length(); i++) {
            char c = exp.charAt(i);
            switch (c) {
                case '+', '-', '*', '/' -> {
                    if (stack.isEmpty()) {
                        stack.push(c);
                    } else {
                        if (priority(c) > priority(stack.peek())) {
                            stack.push(c);
                        } else {
                            while (!stack.isEmpty() && priority(stack.peek()) >= priority(c)) {
                                sb.append(stack.pop());
                            }
                            stack.push(c);
                        }
                    }
                }
                case '(' ->{
                    stack.push(c);
                }
                case ')'->{
                    while(!stack.isEmpty()&&stack.peek()!='('){
                        sb.append(stack.pop());
                    }
                    if(!stack.isEmpty()){
                        stack.pop();    
                    }
                }
                default -> {
                    sb.append(c);
                }
            }
        }
        while (!stack.isEmpty()){
            sb.append(stack.pop());
        }
        return sb.toString();
    }

4.用两个栈模拟队列

    //用栈实现队列
    public class myQueue{
        LinkedList<Character> s1 = new LinkedList<>();
        LinkedList<Character> s2 = new LinkedList<>();
        
        public void push(int x){
            s2.push(x);
        } 
        
        public int pop(){
            if (!s1.isEmpty()) {
                while(!s2.isEmpty()){
                    s1.push(s2.pop());
                }
            }
            return s1.pop();
        }
        
        public int peek(){
            if (!s1.isEmpty()) {
                while(!s2.isEmpty()){
                    s1.push(s2.pop());
                }
            }
            return s1.peek();
        }
        
        public boolean empty(){
            return s1.isEmpty()&&s2.isEmpty();
        }
    }

5.用队列模拟栈

    public static class MyStack{
        Queue<Integer> queue = new LinkedList<>();
        public void push(int x ){
            queue.offer(x);
            int len = queue.size();
            for (int i = 0; i < len-1; i++) {
                queue.offer(queue.poll());
            }
        }
        public int pop(){
            if(!queue.isEmpty())
                return queue.poll();
            return -1;
        }

        public int top(){
            return queue.peek();
        }

        public boolean empty(){
            return queue.isEmpty();
        }
    }

参考文章:
https://blog.csdn.net/devnn/article/details/82716447
https://blog.csdn.net/BillieFan/article/details/107578057


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值