队列的特点:先入先出,就好像饭堂排队打饭的排的队一样,排第一的先拿到饭去打菜,跑得快,拿第一有饭吃,跑得慢。。。。
操作:插入,在队尾插入元素,就好像在饭堂打饭需要按序排队。
删除,删除队首的元素,就好像在饭堂拿到饭的人,不能老是在第一站着位置。
实现:
class Queue{
List<Integer> list;
int index; //记录队首的位置
// 初始化队列
public Queue(){
list = new ArrayList<>();
index = 0;
}
// 实现入队操作
public boolean enQueue(int value){
// 队列满了后就不能再加入元素了
list.add(value);
}
// 实现删除操作
public boolean remove(){
if(isEmpty) return false;
index++;
return true;
}
public int Front(){
return list.get(index);
}
// 判断是否为空
publi boolean isEmpty(){
return index >= list.size();
}
}
基于 链表 实现的队列:它可以创建任意数据类型的队列。
public class Queue<Item>{
private Node first; // 指向最早添加结点的链接
private Node last; // 指向最近添加结点的链接
private int N; // 结点的个数
// 定义结点的嵌套类
public class Node{
Item item;
Node next;
}
public boolean isEmpty(){
return fisrt == null;
}
public int size(){ return N;}
public void enqueu(Item item){
Node oldlast = last;
last = new Node();
last.item = item;
last.next = null;
if(isEmpty()) first = last;
else oldlast.next = last;
}
public Item dequeue(){
Item item = first.item;
first = first.next;
if(isEmpty()) last = null;
N--;
return item;
}
}
缺点:如果队列头指针移到队列的中间位置,那么前面的空间就白白浪费了,这时候就循环队列就应运用而生了。
循环队列:设置两个指针 head,tail,head 记录队列的队首的指针的位置和 tail 记录队尾指针的位置,如果 tail 到队列的尾部的时候,如果队列的前面还有位置,那么可以将元素存储到前面去,这时候将 tail 的值更新即,此时 tail 的值是小于 head 的值的。
优点:极大的利用了空间。
实现:同时也是 leetcode.622 的一道题:设计循环队列,重点是判空,答案中用的是 head == -1,其实也可以用 head == tail
队列满的条件:(tail + 1)%size = head。
class myQueue(){
int[] data;
int head;
int tail;
int size;
public myQueue(int k){
data = new int[k];
head = -1;
tail = -1;
size = k;
}
// 入队操作
public boolean enQueue(int value){
if(isFull()) return false;
if(isEmpty()){ head = 0;}
tail = (tail+1) % size;
data[tail] = value;
return true;
}
// 出队操作
public boolean deQueue(){
if(isEmpty()) return false;
// 清空将下标置为 1
if(tail == head){head = -1;tail = -1;return true;}
head = (head+1) % size;
return true;
}
// 获取队首元素
public int Front(){
if(isEmpty()) return false;
return data[head];
}
// 获取队尾元素
public int Rear(){
if(isEmpty()) return false;
return data[tail];
}
// 判断队列是否为空
public boolean isEmpty(){
return head == -1;
}
// 判断队列满了没
public boolean isFull(){
return (tail+1) % size == head;
}
}
java 内置库的实现:不用重复造轮子了。
class myQueue{
public static void main(string[] args){
Queue<Integer> queue = new linkedList<>();
System.out.println("队首元素" + queue.peek());
queue.offer(5);
queue.offer(4);
queue.offer(3);
// 删除队首元素,而 peek 只是返回队首元素,不删除。
queue.poll();
System.out.println("队首元素" + queue.peek());
System.out.println("队列的长度:"+queue.size());
}
}
队列在其他知识的运用:
1.可用在操作系统中的进程调度的算法,先来先服务,只考虑了等待时间,未考虑运行时间,因为如果是一个运行时间较短的后来,那么刚好碰到前面的长作业,需要将长作业运行完,才能执行下一步,不利于短作业的实现。
2.操作系统中的页面淘汰策略:淘汰在内存里面停留时间最长的页面,实现简单,但对于特定序列的页面效率低下。
3.实现生产者-消费者模型:当队列为空的时候,从队头取数据会被阻塞,因为没有数据可取,直到有了数据才能返回。
而如果队列已经满了的话,那么那么插入数据的操作就会被阻塞,直到队列中有空位置,才继续插入,然后返回。
4.当线程池没有空闲线程的时候,新的任务请求线程,那么一般有两种处理策略:直接拒绝,让他排队去。
而这时,如果是用链表实现请求任务的队列,那么可以实现一个无限排队的队列,针对对时间敏感的系统,基于链表实现是不合适的,而如果用数组实现,那么如果超出数组的界限的时候,将会被拒绝,这种对响应时间敏感的更合适,但是设置一个合理的长度,却是个问题。
参考:
极客时间数据结构与算法