文章目录
1.概念
先进者先出
,是一种操作受限的线性结构,只有两种操作:入队enquque()
和 出队dequeue()
.
2.分类
基于数组实现的叫顺序队列
,基于链表实现的叫链式队列
.
2.1顺序队列
2.1.1 不可变大小的队列
package com.desmond.codebase.algorithm.queue;
/**
* 固定大小的顺序队列
* @author presleyli
* @date 2019/1/7 12:51 PM
*/
public class FixedArrayQueue {
private String[] arr;
private int n;
private int head = 0;
private int tail = 0;
public FixedArrayQueue(int n) {
this.n = n;
this.arr = new String[n];
}
/**
* O(1)
* @param ele
* @return
*/
public boolean enQueue(String ele) {
if(tail == n) { // 1
return false;
}
arr[tail++] = ele; // 1
return true;
}
/**
* O(1)
* @return
*/
public String deQueue() {
if(head == tail) { // 1
return "";
}
String ele = arr[head++]; // 1
return ele;
}
public static void main(String[] args) {
FixedArrayQueue arrayQueue = new FixedArrayQueue(3);
System.out.println(arrayQueue.enQueue("1"));
System.out.println(arrayQueue.enQueue("2"));
System.out.println(arrayQueue.enQueue("3"));
System.out.println(arrayQueue.enQueue("4"));
System.out.println(arrayQueue.enQueue("5"));
System.out.println(arrayQueue.deQueue());
System.out.println(arrayQueue.deQueue());
System.out.println(arrayQueue.deQueue());
System.out.println(arrayQueue.deQueue());
System.out.println(arrayQueue.deQueue());
}
}
2.1.2 可变大小的顺序队列
package com.desmond.codebase.algorithm.queue;
/**
* 可扩容的顺序队列-无限扩容.
* @author presleyli
* @date 2019/1/7 12:51 PM
*/
public class ChangeableArrayQueue {
private String[] arr;
private int n;
private int head = 0;
private int tail = 0;
public ChangeableArrayQueue(int n) {
this.n = n;
this.arr = new String[n];
}
/**
* 最好:O(1)
* 最坏: O(n)
* 平均(均摊): O(1)
* @param ele
* @return
*/
public boolean enQueue(String ele) {
if(tail == n) { // 1
ensureCapable();
}
arr[tail++] = ele; // 1
return true;
}
/**
* 扩容.
* O(n)
*/
public void ensureCapable() {
int newSize = n * 2;
String[] newArr = new String[newSize];
System.arraycopy(arr, 0, newArr, 0, n);
n = newSize;
arr = newArr;
}
/**
* O(1)
* @return
*/
public String deQueue() {
if(head == tail) { // 1
return "";
}
String ele = arr[head++]; // 1
return ele;
}
public static void main(String[] args) {
ChangeableArrayQueue arrayQueue = new ChangeableArrayQueue(2);
System.out.println(arrayQueue.enQueue("1"));
System.out.println(arrayQueue.enQueue("2"));
System.out.println(arrayQueue.enQueue("3"));
System.out.println(arrayQueue.enQueue("4"));
System.out.println(arrayQueue.enQueue("5"));
System.out.println(arrayQueue.enQueue("6"));
System.out.println(arrayQueue.deQueue());
System.out.println(arrayQueue.deQueue());
System.out.println(arrayQueue.deQueue());
System.out.println(arrayQueue.deQueue());
System.out.println(arrayQueue.deQueue());
System.out.println(arrayQueue.deQueue());
}
}
2.1.3 利用固定长度,可变的顺序队列
package com.desmond.codebase.algorithm.queue;
/**
* 可变顺序队列-利用固定arr本身剩余空间.
* @author presleyli
* @date 2019/1/7 12:51 PM
*/
public class ChangeableArrayQueueV2 {
private String[] arr;
private int n;
private int head = 0;
private int tail = 0;
public ChangeableArrayQueueV2(int n) {
this.n = n;
this.arr = new String[n];
}
/**
* 最好:O(1)
* 最坏: O(n)
* 平均(均摊): O(1)
* @param ele
* @return
*/
public boolean enQueue(String ele) {
if(tail == n) { // 1
// 如果已经是第一个了,表明无元素出队,
// 队列满了
if(head == 0) {
return false;
}
// 不扩充arr容量,搬迁.
/*
最好: head = tail - 1, 只搬移一个元素:O(1)
最坏: head = 1, 搬移 n-1个元素:O(n)
平均: 1*(1/(n-1)) + 2*(1/(n-1))
+ 3*(1/(n-1)) + (n-1)*(1/(n-1)) = n²/(2n-2) -> O(n)
*/
for(int i= head; i < tail; i++) {
arr[i-head] = arr[i];
}
tail = tail - head;
head = 0;
}
arr[tail++] = ele; // 1
return true;
}
/**
* O(1)
* @return
*/
public String deQueue() {
if(head == tail) { // 1
return "";
}
String ele = arr[head++]; // 1
return ele;
}
public static void main(String[] args) {
ChangeableArrayQueueV2 arrayQueue = new ChangeableArrayQueueV2(2);
System.out.println(arrayQueue.enQueue("1"));
System.out.println(arrayQueue.deQueue());
System.out.println(arrayQueue.enQueue("2"));
System.out.println(arrayQueue.enQueue("3"));
System.out.println(arrayQueue.enQueue("4"));
System.out.println(arrayQueue.enQueue("5"));
System.out.println(arrayQueue.enQueue("6"));
System.out.println(arrayQueue.deQueue());
System.out.println(arrayQueue.deQueue());
System.out.println(arrayQueue.deQueue());
System.out.println(arrayQueue.deQueue());
System.out.println(arrayQueue.deQueue());
}
}
2.2链式队列
package com.desmond.codebase.algorithm.queue;
/**
* 链式队列.
* @author presleyli
* @date 2019/1/7 10:33 PM
*/
public class LinkedListQueue {
private Node head;
private Node tail;
/**
* O(1).
* @param data
* @return
*/
public boolean enQueue(String data) {
if(tail == null) {
Node node = new Node(data, null);
tail = node; // 只是把node的首地址给tail
head = node; // 只是把node的首地址给tail
} else {
Node node = new Node(data, null);
tail.next = node; // 利用tail首地址(指针)找到tail所指的实际堆里的对象,把其对象的next指针指向新的结点node.
tail = node; // 只是把node的首地址给tail
}
printAll();
return true;
}
/**
* O(1).
* @return
*/
public String deQueue() {
if(head == null) {
return null;
}
String data = head.data;
if(head.next == null) {
head = null;
tail = null;
} else {
Node current = head;
head = head.next;
current = null;
}
printAll();
return data;
}
public void printAll() {
StringBuilder sb = new StringBuilder();
for(Node node = head; node != null || node != tail; node = node.next) {
if(node == null) {
break;
}
sb.append(node.data).append(">>>>");
}
System.out.println(sb);
}
public static void main(String[] args) {
LinkedListQueue queue = new LinkedListQueue();
System.out.println(queue.enQueue("1"));
System.out.println(queue.enQueue("2"));
System.out.println(queue.enQueue("3"));
System.out.println(queue.enQueue("4"));
System.out.println(queue.enQueue("5"));
System.out.println(queue.deQueue());
System.out.println(queue.deQueue());
System.out.println(queue.deQueue());
System.out.println(queue.deQueue());
System.out.println(queue.deQueue());
System.out.println(queue.deQueue());
System.out.println(queue.deQueue());
System.out.println(queue.deQueue());
}
private static class Node {
private String data;
private Node next;
public Node(String data, Node next) {
this.data = data;
this.next = next;
}
}
}
2.3循环队列
2.3.1 概念
循环队列:
循环队列,顾名思义,它长得像一个环。原本数组是有头有尾的,是一条直线。现在我们把首尾相连,扳成了一个环
最重要的是 队空和队满判定:
队列满条件:head = (tail+1)%n
队列空条件:head=tail
2.3.2 示意图
2.3.3 代码
package com.desmond.codebase.algorithm.queue;
/**
* 循环队列:
* 判断 队列满条件:head = (tail+1)%n <br/>
* 队列空条件:head=tail <br/>
* 浪费了一个空间.
* @author presleyli
* @date 2019/1/8 7:55 PM
*/
public class CircularQueue {
private int head;
private int tail;
private int n;
private String[] arr;
public CircularQueue(int n) {
this.n = n;
arr = new String[n];
}
/**
* O(1)
* @param data
* @return
*/
public boolean enQueue(String data) {
int tmp = (tail + 1)%n; // head = (tail+1)%n
if(head == tmp) { // 队满条件: head = (tail+1)%n
return false;
}
arr[tail++] = data;
tail = tail >= n ? 0 : tail;
printAll();
return true;
}
/**
* O(1)
* @return
*/
public String deQueue() {
// 队空 条件: head=tail
if(head == tail) {
return null;
}
String data = arr[head];
// head = head + 1;
// head = head >= n ? 0 : head;
head = (head + 1) % n;
printAll();
return data;
}
public void printAll() {
StringBuilder sb = new StringBuilder();
if(head < tail) {
for(int i=head;i<tail;i++) {
sb.append(arr[i]).append(">>>");
}
} else if(head > tail) {
for(int i=head; i<n; i++) {
sb.append(arr[i]).append(">>>");
}
for(int i=0; i<tail; i++) {
sb.append(arr[i]).append(">>>");
}
}
System.out.println(sb);
}
public static void main(String[] args) {
CircularQueue queue = new CircularQueue(4);
System.out.println(queue.enQueue("1"));
System.out.println(queue.enQueue("2"));
System.out.println(queue.enQueue("3"));
System.out.println(queue.deQueue());
System.out.println(queue.deQueue());
System.out.println(queue.deQueue());
System.out.println(queue.enQueue("5"));
System.out.println(queue.enQueue("6"));
System.out.println(queue.enQueue("7"));
System.out.println(queue.enQueue("8"));
System.out.println(queue.enQueue("9"));
System.out.println(queue.deQueue());
}
}
3.应用
3.1阻塞队列
3.2并发队列
3.3资源池
线程池没有空闲线程时,新的任务请求线程资源时,线程池该如何处理?各种处理策略又是如何实现的呢? 我们一般有两种处理策略。
- 第一种是非阻塞的处理方式,直接拒绝任务请求;
- 另一种是阻塞的处理方式,将请求排队,等到有空闲线程时,取出排队的请求继续处理。
那如何存储排队的请求呢? 我们希望公平地处理每个排队的请求,先进者先服务,所以队列这种数据结构很适合来存储排队请求。我们前面说过,队列有基于链表和基于数组这两种实现方式。这两种实现方式对于排队请求又有什么区别呢? 基于链表的实现方式,可以实现一个支持无限排队的无界队列(unbounded queue),但是可能会导致过多的请求排队等待,请求处理的响应时间过长。所以,针对响应时间比较敏感的系统,基于链表实现的无限排队的线程池是不合适的。
而基于数组实现的有界队列(bounded queue),队列的大小有限,所以线程池中排队的请求超过队列大小时,接下来的请求就会被拒绝,这种方式对响应时间敏感的系统来说,就相对更加合理。不过,设置一个合理的队列大小,也是非常有讲究的。队列太大导致等待的请求太多,队列太小会导致无法充分利用系统资源、发挥最大性能。
除了前面讲到队列应用在线程池请求排队的场景之外,队列可以应用在任何有限资源池中,用于排队请求,比如数据库连接池等。实际上,对于大部分资源有限的场景,当没有空闲资源时,基本上都可以通过“队列”这种数据结构来实现请求排队