队列

1、队列的定义及基本概念

“队列”(Queue)这个单词时英国人说的 “排”(line)(一种等待服务的方式),在生活中,队列在我们日常生活中经常碰到,例如,排队买东西。

在计算机科学中,队列是一种数据结构,是一种特殊的线性表。和栈类似。但相差很大。

队的操作是在两端进行,其中一端只能进行插入,该端称为队列的队尾,而另一端只能进行删除,该端称为队列的队首。队列的运算规则时FIFO(First In First Out)

在这里插入图片描述

队列的基本运算:

  • 置空队:建立一个空队列

  • 判断队空:队列是否为空

  • 判断队满:队列是否满

  • 入队:在队列非满时,从队尾插入元素

  • 出队:在队列非空时,从队首删除元素

  • 取队首元素:不删除元素,返回队首元素

  • 遍历队列:打印出数组中的有效数据

队列的存储具有顺序存储和链式存储两种。顺序存储通过数组存储,链式存储通过链表存储。

2、单向队列

我们通过数组来模拟单向队列。

由于队列的对头和队尾的位置是变化的,因而要设置两个指针front和rear来分别指示队头元素和队尾元素在空间中的位置。

在构造顺序队列时,两个指针初始化置为0,入队时将新元素插入rear所指的位置,然后将rear加1,出队时,删除front所指的元素,然后将front加1并返回被删元素。

在入队和出队时,我们需要判断队空和队满。

图解:

初始化:创建一个maxSize的数组 。 front 和 rear 为-1 。

队列初始化

添加数据:先将rear加1,然后将rear指向的数组赋值

添加数据

删除数据:先front加1,然后取出front指向的数据,因为front指向的是队列头的前一个位置。

删除数据

判断为满:可以看出。当rear 等于 maxSize-1时,队列满。

判断队列是否满

判断为空:当font和rear相等时,为空

判断队列是否空

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
75
76
77
78
79
class ArrayQueue{
//表示数组的最大容量
private int maxSize;
//队列头
private int front;
//队列尾
private int rear;
//该数据用于存放数据,模拟队列
private int[] arr;

//队列构造器
public ArrayQueue(int arrMaxSize){
maxSize = arrMaxSize;
//创建一个长度为maxSize的数据
arr = new int[maxSize];

//指向队列的头部,分析出front时指向队列头的前一个位置
front = -1;
//指向队列的尾部,指向队列尾部的数据
rear = -1;
}

//判断队列是否为满
public boolean isFull(){
return rear == maxSize - 1;
}

//判断队列为空
public boolean iSEmpty(){
return front == rear;
}

//添加数据到队列中
public void AddQueue(int n){
if(!isFull()){
rear++;
arr[rear] = n;
}
else {
System.out.println("队列已满,无法添加数据");
}
}

//出队列,并返回数据
public int GetQueue(){
if(iSEmpty()){
System.out.println("队列为空,无法返回数据");
throw new RuntimeException("队列空,不能取数据");
}

front++;
return arr[front];
}

//遍历队列
public void ShowQueue(){
if(iSEmpty()){
System.out.println("队列为空");
return;
}

for (int i = front; i < rear; i++) {
System.out.print(arr[i + 1] + "\t");
}

System.out.println();
}

//显示头数据
public int GethandQueue(){
if(iSEmpty()){
System.out.println("队列为空,无法返回数据");
throw new RuntimeException("队列空,不能取数据");
}

return arr[front + 1];
}

}

测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void main(String[] args) {
ArrayQueue arrayQueue = new ArrayQueue(3);
System.out.println(arrayQueue.iSEmpty());

arrayQueue.AddQueue(1);
arrayQueue.AddQueue(2);
arrayQueue.ShowQueue();

arrayQueue.GetQueue();
arrayQueue.ShowQueue();

System.out.println(arrayQueue.GethandQueue());

}

问题:
我们添加三次数据,然后删除3次数据。 显示为满,但实际上队列中没有数据

1
2
3
4
5
6
7
8
9
10
11
12
13
public static void main(String[] args) {
ArrayQueue arrayQueue = new ArrayQueue(3);

arrayQueue.AddQueue(1);
arrayQueue.AddQueue(2);
arrayQueue.AddQueue(3);

arrayQueue.GetQueue();
arrayQueue.GetQueue();
arrayQueue.GetQueue();

System.out.println(arrayQueue.isFull()); //true
}

此时数组为空,但无法存储数据;
在这里插入图片描述
为了解决这个问题,我们引入循环队列,消除假溢出;

3、循环队列

我们通过取模来实现循环队列,将数组看成一个环形。也就是当队尾到最大值maxSize时,取模,使队尾等于0。
在循环队列中,判空的条件使rear == front ,判满的条件也是rear == front,为了避免二义性。所以人为的浪费一个空间,这样判满的条件为 front == (rear + 1)% maxSize;

图解:

初始化:front和rear都等于0。

此时的front和rear的含义与单向队列中的含义不同了;

front是指向队头

rear是指向队尾的下一个数据

在这里插入图片描述

添加一个数据:先存入数据,然后:rear = (rear + 1)% maxSize

在这里插入图片描述

删除一个数据: 因为front指向的是当前数据,只能用一个临时变量将当前的数据存上,然后 front = (front + 1)% maxSize,最后返回临 时变量

在这里插入图片描述

判空:rear == front

在这里插入图片描述

判满:front == (rear + 1)% maxSize

在这里插入图片描述

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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
class ArrayCircularQueue{

//表示数组的最大容量
private int maxSize;

//队列头 此处的front指向队列的第一个数据
private int front;

//队列尾 此处的rear最后一个数据的下一个数据
private int rear;

//该数据用于存放数据,模拟队列
private int[] arr;

//初始化队列
public ArrayCircularQueue(int Size){
maxSize = Size;

front = 0;
rear = 0;

arr = new int[maxSize];
}

//判空
public boolean isEmpty(){
return rear == front;
}

//判满
public boolean isFull(){
return front == (rear + 1) % maxSize;
}

//添加数据
public void AddQueue(int n){
if (isFull()){
System.out.println("队列已满");
}

arr[rear] = n;
rear = (rear + 1) % maxSize;

}

//出队列,返回队头数据
public int GetQueue(){
if(isEmpty()){
System.out.println("队列为空,无法返回数据");
throw new RuntimeException("队列空,不能取数据");
}

int value = arr[front];
front = (front + 1) % maxSize;

return value;
}

//遍历队列
public void ShowQueue(){
if (isEmpty()){
System.out.println("队列为空");
return;
}

int i = front;

while(i != rear)
{
System.out.print(arr[i] + "\t");

i = (i + 1) % maxSize;
}
System.out.println();
}

//得到队列的有效数据个数
public int Size(){
return (rear - front + maxSize) % maxSize;
}

//得到队列的队首数据
public int getHandQueue(){
if (isEmpty()){
System.out.println("队列为空,无法返回数据");
throw new RuntimeException("队列空,不能取数据");
}

return arr[front];
}

}

测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static void main(String[] args) {
//实际上只能存储2个数据
ArrayCircularQueue arrayCircularQueue = new ArrayCircularQueue(3);

System.out.println(arrayCircularQueue.isEmpty());

arrayCircularQueue.AddQueue(1);
arrayCircularQueue.AddQueue(2);

arrayCircularQueue.ShowQueue();

arrayCircularQueue.GetQueue();

arrayCircularQueue.ShowQueue();

System.out.println(arrayCircularQueue.getHandQueue());

System.out.println(arrayCircularQueue.Size());


}

这样就解决了假溢出问题,队列也可以重复使用:

1
2
3
4
5
6
7
8
9
ArrayCircularQueue arrayCircularQueue = new ArrayCircularQueue(3);

arrayCircularQueue.AddQueue(1);
arrayCircularQueue.AddQueue(2);

arrayCircularQueue.GetQueue();
arrayCircularQueue.GetQueue();

System.out.println(arrayCircularQueue.isFull()); //false

队列
https://johnjoyjzw.github.io/2020/01/08/队列/
Author
John Joy
Posted on
January 8, 2020
Licensed under