队列(JAVA)

一、概念

同栈一样队列是一种特殊的线性表,只能在头部输出和尾部输入,特点为先进先出。

例子:排号取餐,越早排号的取餐越早。

两种创建方式:

数组更适合访问,链表更适合插入删除

//顺序表(动态数组实现    
 Queue<Object> queue = new ArrayDeque<>();
//链表(双向链表结构实现
Queue<Object> queue = new LinkedList<>();

二、基础操作

删除(出队):poll()

添加(入队):offer()

查看队首元素:peek()

判空:isEmpty()

自定义类实现方法在最后。

题目:225. 用队列实现栈. - 力扣(LeetCode). - 备战技术面试?力扣提供海量技术面试资源,帮助你高效提升编程技能,轻松拿下世界 IT 名企 Dream Offer。icon-default.png?t=N7T8https://leetcode.cn/problems/implement-stack-using-queues/description/

三、双向队列、循环队列

1、双向队列

        // 使用 ArrayDeque 创建双向队列
        Deque<Integer> deque1 = new ArrayDeque<>();

        // 使用 LinkedList 创建双向队列
        Deque<String> deque2 = new LinkedList<>();

与前面先进先出队列不同,双向队列可在队列的两端都进行增删操作。

添加元素:offerFirst()、offerLast()

删除元素:pollFirst()、pollLast()

查看元素:peekFirst()、peekLast()

2、循环队列

暂时没碰到相关题目,以后补(如果还记得的话)。

四、优先级队列

与普通队列相同,优先级队列拥有一个队首和一个队尾且从队首进行删除操作,不同的是优先级队列内的元素按照优先级顺序排序。

创建并输出(未制定规则按照自然顺序输出,小 -> 大)

import java.util.PriorityQueue;

public class Main {
    public static void main(String[] args) {
        // 创建一个优先级队列
        PriorityQueue<Integer> pq= new PriorityQueue<>();

        // 添加元素到队列
        pq.add(3);
        pq.add(1);
        pq.add(2);

        // 弹出队列中的元素(按照优先级)
        while (!pq.isEmpty()) {
            System.out.println(pq.poll());
        //输出顺序<1,2,3>
        }
    }
}

例题:力扣100232. 超过阈值的最少操作数 II

代码:

暴力:每次操作完成后对数组排序,确保始终操作最小的两位数(数据量过大时,报废)

数组排序时间复杂度(nlogn)

class Solution {
    public int minOperations(int[] nums, int k) {
        int cont = 0;
        Arrays.sort(nums); // 在循环外部对数组进行排序
        int i = 0;
        while (i < nums.length) {
            if (nums[i] >= k) return cont;
            cont++;
            if (i < nums.length - 1) {
                long x = Math.min(nums[i], nums[i + 1]) * 2L + Math.max(nums[i], nums[i + 1]);
                if (x > k) nums[i + 1] = k;
                else nums[i + 1] = (int) x;
            }
            i++;
            Arrays.sort(nums, i, nums.length); // 在循环内部进行操作后仅对部分数组进行排序
        }
        return cont;
    }
}

优化:使用优先级队列,避免了每次操作后都要对数组进行排序,降低了时间复杂度

优先级队列插入元素时间复杂度(logn)

class Solution {
    public  int minOperations(int[] nums, int k) {
        //使用Long防止数据溢出
        PriorityQueue<Long> pq = new PriorityQueue<>();

        for (int i : nums){
            pq.add((long) i);
        }

        int cont = 0;

       //每次对前两个最小的数操作,知道队首元素大于等于k
        while(pq.peek() < k){
            cont++;
            Long x = pq.poll();
            Long y = pq.poll();
            pq.add(x * 2 + y);
        }

        return cont;
    }
}

疑问:为什么将数据插入队尾后,队列会自动排序。

解答:因为在优先级队列的插入操作并不是简单的在队尾添加,而是与前面的元素比较优先级,不断向前移动。

下面将模拟优先级队列的入队操作:

class PriorityQData{
   Object elem;//元素值
   int priority;//优先级数
   
}

//头节点 
Node head;
//尾节点
Node last;
public void offer(Object x){
    
    PriorityQData pn = (PriorityQData) x;
    Node s = new Node(pn);

    if(head == null) return head = last = s;//队列为空
   
    Node p = head, q = head;//p 指向当前节点,q 用于记录上一个节点。

    //比较优先级
    while(p != null && pn.priority >= ((PriorityQData) p.data).priority){
        q = p;
        p = p.next; 
    }

  //插入元素的优先级最小,插入队尾(当前节点 p 是队尾节点
    if(p == null){
       last.next = s;
       last = s;
    }
  //插入元素的优先级最大,插入队首(当前节点 p 是头节点
   esle if(p == head){
       s.next = head;
       head = s;
    }
// 否则,将新节点插入到当前节点之前
   esle{
       q.next = s;
       s.next = p;
    }


}

实战:347. 前 K 个高频元素

思路:

哈希表排序去重,优先级队列排序输出前k个高频数

难点:我们既要获得出现次数,又要获得高频数

解决:自定义排序规则,针对出现次数排序

PriorityQueue<int[]> pq = new PriorityQueue<>((a1,a2) -> a1[1] - a2[1]);

拓展:Lambda表达式 (a1,a2) -> a1[1] - a2[1]   ,确定a1 、 a2的排序

如果结果为负,则 a1 应排在 a2 的前面;如果结果为零,则它们相等;如果结果为正,则 a1 应排在 a2 的后面。

例:pq内有两个数组分别为{1,2} 、{2,3} 第一位用于存放返回的数,第二存放优先级数(出现的次数),此时a1[1] - a2[1] = 2 - 3 < 0,说明a1优先级高,排序为a1 -> a2

class Solution {
    //优先级队列
    public int[] topKFrequent(int[] nums, int k) {
        int[] num = new int[k];
        HashMap<Integer,Integer> map = new HashMap<>();

        for(int i : nums){
            map.put(i,map.getOrDefault(i,0) + 1);
        }

       //自定义排序规则
       
        PriorityQueue<int[]> pq = new PriorityQueue<>((a1,a2) -> a2[1] - a1[1]);

        for(Map.Entry<Integer,Integer> entry : map.entrySet()){ 
            //队列未满加入数据
            if(pq.size() < k){
                pq.add(new int[]{entry.getKey(),entry.getValue()});
            }else{
                
              //如果下一个出现的次数大于队首,弹出队首
                if(entry.getValue() > pq.peek()[1]){
                    pq.poll();
                    pq.add(new int[]{entry.getKey(),entry.getValue()});
                }
            }
        }

        for(int i = 0; i < k;i++){
            num[i] = pq.poll()[0]; 
        }

        return num;

    }
}

五、使用链表实现队列的基本操作

Java中没有指针的概念,需要创建节点类模拟指针

 //队列的链式实现

    class LinkQueue {

        //节点类模拟指针
        class Node {
            Object val;
            Node next;

            public Node(){}
            public Node(Object val){
                this.val = val;
                next = null;
            }
            public Node(Object val,Node next){
                this.val = val;
                this.next = next;
            }


        }

        Node head; //队首指针
        Node last;  //队尾指针


        //初始化
        public LinkQueue() {
            head = last = null;
        }

        //置空
        public void clear(){
            head = last = null;//head指向last,last再指向空
        }

        //判空
        public boolean isEmpty(){
            return head == null;
        }

        //查看队首元素
        public Object peek(){
            if(isEmpty()) return null;
            return head.val;
        }

        //入队
        public void offer(Object x){
            Node p = new Node(x);
            if(!isEmpty()) {
                last.next = p;//加入节点
                last = p;//更新尾指针
            }else head = last = p;

        }

        //出队
        public Object poll(){
            if(isEmpty()) return null;
            Node x = head;
            head = head.next;//头指针后移
            
            //如果删除的是最后一个节点,尾节点指向空
            if(x == last) last = null;
            return x.val;
        }
    }

  • 24
    点赞
  • 39
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值