前面一篇博客咱们讲解了并不像数组同样彻底做为存储数据功能,而是做为构思算法的辅助工具的数据结构——栈,本篇博客咱们介绍另一个这样的工具——队列。栈是后进先出,而队列恰好相反,是先进先出。前端
一、队列的基本概念
队列(queue)是一种特殊的线性表,特殊之处在于它只容许在表的前端(front)进行删除操做,而在表的后端(rear)进行插入操做,和栈同样,队列是一种操做受限制的线性表。进行插入操做的端称为队尾,进行删除操做的端称为队头。队列中没有元素时,称为空队列。java
队列的数据元素又称为队列元素。在队列中插入一个队列元素称为入队,从队列中删除一个队列元素称为出队。由于队列只容许在一端插入,在另外一端删除,因此只有最先进入队列的元素才能最早从队列中删除,故队列又称为先进先出(FIFO—first in first out)线性表。算法
好比咱们去电影院排队买票,第一个进入排队序列的都是第一个买到票离开队列的人,而最后进入排队序列排队的都是最后买到票的。后端
在好比在计算机操做系统中,有各类队列在安静的工做着,好比打印机在打印列队中等待打印。数组
队列分为:数据结构
①、单向队列(Queue):只能在一端插入数据,另外一端删除数据。数据结构和算法
②、双向队列(Deque):每一端均可以进行插入数据和删除数据操做。工具
这里咱们还会介绍一种队列——优先级队列,优先级队列是比栈和队列更专用的数据结构,在优先级队列中,数据项按照关键字进行排序,关键字最小(或者最大)的数据项每每在队列的最前面,而数据项在插入的时候都会插入到合适的位置以确保队列的有序。post
二、Java模拟单向队列实现
在实现以前,咱们先看下面几个问题:
①、与栈不一样的是,队列中的数据不老是从数组的0下标开始的,移除一些队头front的数据后,队头指针会指向一个较高的下标位置,以下图:
②、咱们再设计时,队列中新增一个数据时,队尾的指针rear 会向上移动,也就是向下标大的方向。移除数据项时,队头指针 front 向上移动。那么这样设计好像和现实状况相反,好比排队买电影票,队头的买完票就离开了,而后队伍总体向前移动。在计算机中也能够在队列中删除一个数以后,队列总体向前移动,可是这样作效率不好。咱们选择的作法是移动队头和队尾的指针。
③、若是向第②步这样移动指针,相信队尾指针很快就移动到数据的最末端了,这时候可能移除过数据,那么队头会有空着的位置,而后新来了一个数据项,因为队尾不能再向上移动了,那该怎么办呢?以下图:
为了不队列不满却不能插入新的数据,咱们可让队尾指针绕回到数组开始的位置,这也称为“循环队列”。
弄懂原理以后,Java实现代码以下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
|
package
com.ys.datastructure;
public
class
MyQueue {
private
Object[] queArray;
//队列总大小
private
int
maxSize;
//前端
private
int
front;
//后端
private
int
rear;
//队列中元素的实际数目
private
int
nItems;
public
MyQueue(
int
s){
maxSize = s;
queArray =
new
Object[maxSize];
front =
0
;
rear = -
1
;
nItems =
0
;
}
//队列中新增数据
public
void
insert(
int
value){
if
(isFull()){
System.out.println(
"队列已满!!!"
);
}
else
{
//若是队列尾部指向顶了,那么循环回来,执行队列的第一个元素
if
(rear == maxSize -
1
){
rear = -
1
;
}
//队尾指针加1,而后在队尾指针处插入新的数据
queArray[++rear] = value;
nItems++;
}
}
//移除数据
public
Object remove(){
Object removeValue =
null
;
if
(!isEmpty()){
removeValue = queArray[front];
queArray[front] =
null
;
front++;
if
(front == maxSize){
front =
0
;
}
nItems--;
return
removeValue;
}
return
removeValue;
}
//查看对头数据
public
Object peekFront(){
return
queArray[front];
}
//判断队列是否满了
public
boolean
isFull(){
return
(nItems == maxSize);
}
//判断队列是否为空
public
boolean
isEmpty(){
return
(nItems ==
0
);
}
//返回队列的大小
public
int
getSize(){
return
nItems;
}
}
|
测试:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
package
com.ys.test;
import
com.ys.datastructure.MyQueue;
public
class
MyQueueTest {
public
static
void
main(String[] args) {
MyQueue queue =
new
MyQueue(
3
);
queue.insert(
1
);
queue.insert(
2
);
queue.insert(
3
);
//queArray数组数据为[1,2,3]
System.out.println(queue.peekFront());
//1
queue.remove();
//queArray数组数据为[null,2,3]
System.out.println(queue.peekFront());
//2
queue.insert(
4
);
//queArray数组数据为[4,2,3]
queue.insert(
5
);
//队列已满,queArray数组数据为[4,2,3]
}
}
|
三、双端队列
双端队列就是一个两端都是结尾或者开头的队列, 队列的每一端均可以进行插入数据项和移除数据项,这些方法能够叫作:
insertRight()、insertLeft()、removeLeft()、removeRight()
若是严格禁止调用insertLeft()和removeLeft()(或禁用右端操做),那么双端队列的功能就和前面讲的栈功能同样。
若是严格禁止调用insertLeft()和removeRight(或相反的另外一对方法),那么双端队列的功能就和单向队列同样了。
四、优先级队列
优先级队列(priority queue)是比栈和队列更专用的数据结构,在优先级队列中,数据项按照关键字进行排序,关键字最小(或者最大)的数据项每每在队列的最前面,而数据项在插入的时候都会插入到合适的位置以确保队列的有序。
优先级队列 是0个或多个元素的集合,每一个元素都有一个优先权,对优先级队列执行的操做有:
(1)查找
(2)插入一个新元素
(3)删除
通常状况下,查找操做用来搜索优先权最大的元素,删除操做用来删除该元素 。对于优先权相同的元素,可按先进先出次序处理或按任意优先权进行。
这里咱们用数组实现优先级队列,这种方法插入比较慢,可是它比较简单,适用于数据量比较小而且不是特别注重插入速度的状况。
后面咱们会讲解堆,用堆的数据结构来实现优先级队列,能够至关快的插入数据。
数组实现优先级队列,声明为int类型的数组,关键字是数组里面的元素,在插入的时候按照从大到小的顺序排列,也就是越小的元素优先级越高。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
|
package
com.ys.datastructure;
public
class
PriorityQue {
private
int
maxSize;
private
int
[] priQueArray;
private
int
nItems;
public
PriorityQue(
int
s){
maxSize = s;
priQueArray =
new
int
[maxSize];
nItems =
0
;
}
//插入数据
public
void
insert(
int
value){
int
j;
if
(nItems ==
0
){
priQueArray[nItems++] = value;
}
else
{
j = nItems -
1
;
//选择的排序方法是插入排序,按照从大到小的顺序排列,越小的越在队列的顶端
while
(j >=
0
&& value > priQueArray[j]){
priQueArray[j+
1
] = priQueArray[j];
j--;
}
priQueArray[j+
1
] = value;
nItems++;
}
}
//移除数据,因为是按照大小排序的,因此移除数据咱们指针向下移动
//被移除的地方因为是int类型的,不能设置为null,这里的作法是设置为 -1
public
int
remove(){
int
k = nItems -
1
;
int
value = priQueArray[k];
priQueArray[k] = -
1
;
//-1表示这个位置的数据被移除了
nItems--;
return
value;
}
//查看优先级最高的元素
public
int
peekMin(){
return
priQueArray[nItems-
1
];
}
//判断是否为空
public
boolean
isEmpty(){
return
(nItems ==
0
);
}
//判断是否满了
public
boolean
isFull(){
return
(nItems == maxSize);
}
}
|
insert() 方法,先检查队列中是否有数据项,若是没有,则直接插入到下标为0的单元里,不然,从数组顶部开始比较,找到比插入值小的位置进行插入,并把 nItems 加1.
remove 方法直接获取顶部元素。
优先级队列的插入操做须要 O(N)的时间,而删除操做则须要O(1) 的时间,后面会讲解如何经过 堆 来改进插入时间。
五、总结
本篇博客咱们介绍了队列的三种形式,分别是单向队列、双向队列以及优先级队列。其实你们听名字也能够听得出来他们之间的区别,单向队列遵循先进先出的原则,并且一端只能插入,另外一端只能删除。双向队列则两端均可插入和删除,若是限制双向队列的某一段的方法,则能够达到和单向队列一样的功能。最后优先级队列,则是在插入元素的时候进行了优先级别排序,在实际应用中单项队列和优先级队列使用的比较多。后面讲解了堆这种数据结构,咱们会用堆来实现优先级队列,改善优先级队列插入元素的时间。
经过前面讲的栈以及本篇讲的队列这两种数据结构,咱们稍微总结一下:
①、栈、队列(单向队列)、优先级队列一般是用来简化某些程序操做的数据结构,而不是主要做为存储数据的。
②、在这些数据结构中,只有一个数据项能够被访问。
③、栈容许在栈顶压入(插入)数据,在栈顶弹出(移除)数据,可是只能访问最后一个插入的数据项,也就是栈顶元素。
④、队列(单向队列)只能在队尾插入数据,对头删除数据,而且只能访问对头的数据。并且队列还能够实现循环队列,它基于数组,数组下标能够从数组末端绕回到数组的开始位置。
⑤、优先级队列是有序的插入数据,而且只能访问当前元素中优先级别最大(或最小)的元素。
⑥、这些数据结构都能由数组实现,可是能够用别的机制(后面讲的链表、堆等数据结构)实现。