一、概念
同栈一样队列是一种特殊的线性表,只能在头部输出和尾部输入,特点为先进先出。
例子:排号取餐,越早排号的取餐越早。
两种创建方式:
数组更适合访问,链表更适合插入删除
//顺序表(动态数组实现
Queue<Object> queue = new ArrayDeque<>();
//链表(双向链表结构实现
Queue<Object> queue = new LinkedList<>();
二、基础操作
删除(出队):poll()
添加(入队):offer()
查看队首元素:peek()
判空:isEmpty()
自定义类实现方法在最后。
题目:225. 用队列实现栈. - 力扣(LeetCode). - 备战技术面试?力扣提供海量技术面试资源,帮助你高效提升编程技能,轻松拿下世界 IT 名企 Dream Offer。https://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>
}
}
}
代码:
暴力:每次操作完成后对数组排序,确保始终操作最小的两位数(数据量过大时,报废)
数组排序时间复杂度(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;
}
}
思路:
哈希表排序去重,优先级队列排序输出前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;
}
}