队列
- 先进先出,后进后出
- 和栈的入栈push、出栈pop类似,队列提供入队enqueue、出队dequeue两种操作,也是一种操作受限的线性表数据结构
- 常用:循环队列、阻塞队列、并发队列
- 基于数组:顺序队列
- 基于链表:链式队列
顺序队列
//java实现一个顺序队列
public class ArrayQueue{
private String[] items;
//数组大小
private int n = 0;
private int head = 0;
private int tail = 0;
//构造
public ArrayQueue(int capacity){
this.items = new String[capacity];
this.n = capacity;
}
//入队enqueue
public boolean enqueue(String item){
if(tail==n){
//队列满
return false;
}else{
items[tail] = item;
++tail;
return true;
}
}
//出队dequeue
public String dequeue(){
if(head==tail){
//队列空
return null;
}else{
String res = items[head];
++head;
return res;
}
}
}
- 和栈不同的是,栈只有一个栈顶指针,出入都在栈顶。
- 而队列需要头指针head和尾指针tail,head用于出队,tail用于入队。
问题:
tail已经移到队列最尾部,head经过几次出队在数组中间,此时队列的数组前面还是空的,但却无法做入队操作?
**答:**参考数组删除元素,数据搬移问题的解决办法,通过标记出队的元素,当队尾没有空间但队头有空间,一次性清楚被标记的元素,并做数据搬移。
修改入队enqueue()方法:
public boolean enqueue(String item){
if(tail==n){
//队尾没有空间
if(head==0){
//队头没有空间
return false;
}else{
//队头有空间
//数据搬移
for(int i=head;i<tail;++i){
items[i-head] = items[i];
}
tail = tail-head;
head = 0;
}
}
}
出队: O(1)
入队: 最好O(1),最坏O(n),均摊O(1)
循环队列
- 当队尾没有空间时,队头还有空间,tail指向下标0。
- 例如:队列容量n为9,某时tail指向8,当有元素入队将元素放在下标8的位置,tail此时不指向9,而时指向0;
- 假如此时head=0,tail也即将指向0,那么此时队列在入队操作后就满了;
问题关键在于 怎样确定队空和队满?
非循环队列:队空 head=tail ,队满 tail=n
循环队列: 队空 head=tail, 队满 (tail+1)%n=head
//顺序循环队列
public class CircularQueue {
// 数组:items,数组大小:n
private String[] items;
private int n = 0;
// head表示队头下标,tail表示队尾下标
private int head = 0;
private int tail = 0;
// 申请一个大小为capacity的数组
public CircularQueue(int capacity) {
items = new String[capacity];
n = capacity;
}
// 入队
public boolean enqueue(String item) {
// 队列满了
if ((tail + 1) % n == head) return false;
items[tail] = item;
tail = (tail + 1) % n;
return true;
}
// 出队
public String dequeue() {
// 如果head == tail 表示队列为空
if (head == tail) return null;
String ret = items[head];
head = (head + 1) % n;
return ret;
}
}
阻塞队列
- 阻塞队列其实就是在队列基础上增加了阻塞操作。
- 在队列为空的时候,从队头取数据会被阻塞,直到队列中有了数据才能返回;
- 如果队列已经满了,则插入数据的操作就会被阻塞,直到队列中有空闲位置后再插入数据。
并发队列
- 线程安全的队列我们叫作并发队列。
- 最简单直接的实现方式是直接在 enqueue()、dequeue() 方法上加锁,但是锁粒度大并发度会比较低,同一时刻仅允许一个存或者取操作。
- 实际上,基于数组的循环队列,利用 CAS 原子操作,可以实现非常高效的并发队列。
- 这也是循环队列比链式队列应用更加广泛的原因。
熄灯
队列应用场景:
- 对于大部分资源有限的场景,当没有空闲资源时,基本上都可以通过“队列”这种数据结构来实现请求排队。
- 例如:线程池,数据库连接池等。
本文深入探讨了队列数据结构的原理与应用,包括先进先出(FIFO)特性,以及循环队列、阻塞队列和并发队列的实现。通过具体示例,如Java实现的顺序队列和循环队列,解析了队列在资源有限场景中的关键作用。
837





