目录
普通队列
双端队列
阻塞队列
优先级队列
循环队列
一、试题 算法训练 JOE的早餐(优先队列)
- 普通队列是最常见的队列类型,元素按照先进先出(FIFO)的顺序排列。
- Java 中提供了 Queue 接口和它的实现类,比如 LinkedList 和 PriorityQueue,用于实现普通队列。
普通队列的示例代码:
import java.util.Queue;
import java.util.LinkedList;
public class QueueExample {
public static void main(String[] args) {
// 创建一个 LinkedList 实例来实现队列
Queue queue = new LinkedList<>();
// 添加元素到队列尾部
queue.offer("Apple");
queue.offer("Banana");
queue.offer("Cherry");
// 输出队列头部的元素,并移除该元素
System.out.println(queue.poll()); // Output: Apple
System.out.println(queue.poll()); // Output: Banana
// 获取队列头部的元素,但不移除该元素
System.out.println(queue.peek()); // Output: Cherry
// 检查队列是否为空
System.out.println("Is queue empty? " + queue.isEmpty()); // Output: false
// 获取队列中元素的个数
System.out.println("Queue size: " + queue.size()); // Output: 1
}
}
- 双端队列是一种具有队列和栈特性的数据结构,可以在队列的两端进行添加和移除元素。
- Java 中提供了 Deque 接口和它的实现类,比如 ArrayDeque 和 LinkedList,用于实现双端队列。
双端队列的示例代码:
import java.util.LinkedList;
import java.util.Deque;
public class DequeExample {
public static void main(String[] args) {
Deque deque = new LinkedList<>();
// 在队列尾部添加元素
deque.addLast(1);
deque.addLast(2);
deque.addLast(3);
// 在队列头部添加元素
deque.addFirst(0);
// 访问头部和尾部的元素
System.out.println("头部元素:" + deque.getFirst());
System.out.println("尾部元素:" + deque.getLast());
// 遍历队列
System.out.println("遍历队列:");
for (Integer element : deque) {
System.out.print(element + " ");
}
System.out.println();
// 移除头部和尾部的元素
deque.removeFirst();
deque.removeLast();
// 获取队列大小
System.out.println("队列大小:" + deque.size());
}
}
- 阻塞队列是在队列已满或为空时会阻塞线程的特殊类型队列。
- Java 中提供了 BlockingQueue 接口和它的实现类,比如 ArrayBlockingQueue 和 LinkedBlockingQueue,用于实现阻塞队列。
阻塞队列的示例代码:
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ArrayBlockingQueue;
public class BlockingQueueExample {
public static void main(String[] args) {
// 创建一个容量为3的ArrayBlockingQueue
BlockingQueue queue = new ArrayBlockingQueue<>(3);
// 生产者线程
Thread producer = new Thread(() -> {
try {
queue.put("1");
System.out.println("Produced: 1");
queue.put("2");
System.out.println("Produced: 2");
queue.put("3");
System.out.println("Produced: 3");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
// 消费者线程
Thread consumer = new Thread(() -> {
try {
Thread.sleep(2000); // 模拟消费者消费的时间
System.out.println("Consumed: " + queue.take());
Thread.sleep(2000);
System.out.println("Consumed: " + queue.take());
Thread.sleep(2000);
System.out.println("Consumed: " + queue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
});
// 启动生产者和消费者线程
producer.start();
consumer.start();
}
}
- 优先级队列是根据元素的优先级来进行排序的队列,元素按照优先级的顺序排列。Java 中提供了 PriorityQueue 类用于实现优先级队列。
通常情况下,优先级队列是通过堆(heap)这种数据结构来实现的。(堆是最值在顶部)
当从优先级队列中弹出元素时,会弹出具有最高优先级的元素,这通常是堆顶的元素。然后会调整剩余元素的位置以确保堆的特性得以保持。
可以自定义比较器,确定元素的弹出顺序
import java.util.PriorityQueue;
public class PriorityQueueExample {
public static void main(String[] args) {
// 创建一个优先级队列
PriorityQueue priorityQueue = new PriorityQueue<>();
// 添加元素到优先级队列
priorityQueue.add(10);
priorityQueue.add(30);
priorityQueue.add(20);
// 从优先级队列中获取元素
System.out.println("优先级队列的顶部元素:" + priorityQueue.peek());
// 删除优先级最高的元素
while (!priorityQueue.isEmpty()) {
System.out.println("从优先级队列中删除的元素:" + priorityQueue.poll());
}
}
}
循环队列中,队列的头部和尾部可以相邻,实现循环的队列结构。
对于循环队列来说,入队和出队操作在一个固定大小的数组上执行。当队列尾部指针到达数组末尾时,会循环回到数组的起始位置,实现了循环队列的队列结构。这样可以有效地利用数组空间,能够有效地解决普通队列的"假溢出"问题,避免因为队列头部出现空闲空间而导致空间浪费的问题。
循环队列的示例代码:
public class CircularQueue {
private int[] queue;
private int front;
private int rear;
private int size;
public CircularQueue(int size) {
this.size = size;
queue = new int[size];
front = -1;
rear = -1;
}
public boolean isEmpty() {
return front == -1;
}
public boolean isFull() {
return (front == 0 && rear == size - 1) || (rear == (front - 1) % (size - 1));
}
public void enQueue(int item) {
if (isFull()) {
System.out.println("Queue is full");
} else {
if (front == -1) {
front = 0;
}
rear = (rear + 1) % size;
queue[rear] = item;
System.out.println(item + " enqueued to queue");
}
}
public int deQueue() {
int item;
if (isEmpty()) {
System.out.println("Queue is empty");
return -1;
} else {
item = queue[front];
if (front == rear) {
front = -1;
rear = -1;
} else {
front = (front + 1) % size;
}
return item;
}
}
}
分析:
- 处理大数,使用BigDecimal类,否则会溢出
在使用
BigDecimal
时,你需要将结果存储在变量中,因为BigDecimal
是不可变的,所以任何算术操作都会返回一个新的BigDecimal
对象。- 如果直接把每一堆的重量都加入队列中,再开始合并,会运行超时
- 1、把相同种类的堆先合并,找规律,使用公式
- 比如合并【2,2,2,2,2】
- 消耗的体力值:
- 2*2
- 4*2
- 6*2
- 8*2
- 即为2*(2+4+6+8),即为2*2*(1+2+3+4),(1+2+……+(q-1))累加公式
- 抽象成公式,即为 p*p*q*(q-1)/2
- 合并后的堆重量即为 p*q
- 2、然后把队列里不同种类的堆合并起来
- 优先级队列根据元素的优先级弹出元素
- 但下列代码只能拿90分,最后10分应该是相同种类合并时的问题,但实在找不到错误在哪
import java.util.*;
import java.io.*;
import java.math.*;
class Main {
public static void main(String[] args) throws NumberFormatException, IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
int n = Integer.parseInt(reader.readLine());
BigDecimal result = BigDecimal.valueOf(0);// 初始化消耗的体力值
PriorityQueue pq = new PriorityQueue<>();// 队列,存储每堆早餐的重量
BigDecimal temp=new BigDecimal(0);// BigDecimal做算术运算时,需要一个临时对象来接收返回的内容
for (int i = 0; i < n; i++) {
//读取数据
String[] s = reader.readLine().split(" ");
BigDecimal p = new BigDecimal(s[0]);// 重量为p的早餐有q堆
BigDecimal q = new BigDecimal(s[1]);
// 合并相同种类的早餐
if (q.compareTo(BigDecimal.valueOf(1))>0) {// q>1
temp = p.multiply(p).multiply(q).multiply(q.subtract(BigDecimal.valueOf(1))).divide(BigDecimal.valueOf(2));// p*p*q*(q-1)/2
} else{// q<=1,上条公式不适用
temp = p.multiply(q);
}
result = result.add(temp);
BigDecimal weight = p.multiply(q);// 合并后的堆的重量
pq.offer(weight);//把当前种类早餐的合并后的堆加入队列中
}
while (pq.size() > 1) {// 合并不同种类的早餐
BigDecimal a = pq.poll();
BigDecimal b = pq.poll();
//合并两堆消耗的体力值
temp = a.multiply(b);
result = result.add(temp);
//合并两堆后的重量
temp = a.add(b);
pq.offer(temp);
}
System.out.println(result);
}
}