数据结构与算法——队列

cuixiaogang

队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。

队列的特点

  • 遵循先进先出的原则,与栈相反,栈遵循先进后出的原则
  • 队列中通常存在两个指针front和rear,分别称为队头、队尾。数据通常是从队尾入队(插入操作),从队头出队(删除操作)。
  • 队列可以通过链表、数组进行实现
  • 队列又被称为先进先出线性表(FIFO—first in first out)

相关知识点

  • 下溢现象:当队列为空时,做出队运算产生的溢出现象。
  • 上溢现象:当队列已满时,做入队运算产生的溢出现象
  • 假上溢现象:当使用数组进行实现队列时,因为数组的长度为定值,如果不能重复利用已经出队的数据空间,使得队列头、队列尾不断的增加,最后导致虽然队列中的可用数据量未达到队列的最大容量,但是也不能进行入队操作,该现象称为“假上溢”,在实际实现的过程中应该设法避免

顺序队列

我们都知道在JAVA/C/C++中数组是一种顺序线性表,那么,顺序队列其实就是使用数组进行模拟队列。

在顺序队列中,一般存在两个指针,一个是队头指针front,它指向队头元素;另一个是队尾指针rear,它指向下一个入队元素的存储位置。每次在队尾插入一个元素是,rear增1;每次在队头删除一个元素时,front增1。随着插入和删除操作的进行,队列元素的个数不断变化,队列所占的存储空间也在为队列结构所分配的连续空间中移动。当front=rear时,队列中没有任何元素,称为空队列。当rear增加到指向分配的连续空间之外时,队列无法再插入新元素,但这时往往还有大量可用空间未被占用(假上溢现象),这些空间是已经出队的队列元素曾经占用过得存储单元。

image.png

代码实现

  • 封装类
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
93
94
package ArrayQueue;
//顺序队列
public class ArrayQueue {
//队头指针,指向的是下一个出队的数据的前一个位置
private int front = -1;
//队尾指针,指向的是下一个入队的数据的前一个位置
private int rear = -1;

//队列存储数据的最大值,因为是通过数组模拟队列,所以需要设置队列的最大值
private int maxSize;

//数据存储的位置
private int[] data;

public ArrayQueue(int maxSize) {
this.maxSize = maxSize;
this.data = new int[maxSize];
}

/**
* 队列是否为空
*
* @return boolean
**/
public boolean isEmpty() {
return this.rear == this.front;
}

/**
* 队列是否已满
*
* @return boolean
**/
public boolean isFull() {
return this.rear == (this.maxSize - 1);
}

/**
* 数据入队
*
* @param data int 数据值
*
* @return boolean
**/
public boolean inQueue(int data) {
if (this.isFull()) {
return false;
}
this.data[++this.rear] = data;
return true;
}

/**
* 数据出队
*
* @return int
*
* @throws RuntimeException
**/
public int outQueue() {
if (this.isEmpty()) {
throw new RuntimeException("队列为空,出队失败");
}
return this.data[++this.front];
}

/**
* 偷看一下下一个将要出队的数据
*
* @return void
**/
public void peek() {
if (this.isEmpty()) {
return;
}
System.out.printf("下一个即将出队的数据为:%d \n", this.data[this.front + 1]);
}

/**
* 打印队列数据
*
* @return void
**/
public void print() {
if (this.isEmpty()) {
return;
}
System.out.println("下面是队列中的所有数据");
for (int i = this.front + 1; i <= this.rear; i++) {
System.out.printf("%d \t", this.data[i]);
}
System.out.println();
}
}
  • 代码执行
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
package ArrayQueue;
//调用
public class Main {
public static void main(String[] args) {
ArrayQueue arrayQueue = new ArrayQueue(10);
arrayQueue.inQueue(10);
arrayQueue.inQueue(20);
arrayQueue.inQueue(30);
arrayQueue.print();
//数据出队
System.out.println("队列出队2次");
System.out.printf("%d \t %d \n", arrayQueue.outQueue(), arrayQueue.outQueue());
arrayQueue.print();
arrayQueue.inQueue(40);
arrayQueue.inQueue(50);
arrayQueue.inQueue(60);
arrayQueue.inQueue(70);
arrayQueue.inQueue(80);
arrayQueue.inQueue(90);
arrayQueue.inQueue(100);
arrayQueue.print();
//假溢出现象
arrayQueue.inQueue(110);
arrayQueue.print();
}
}
  • 执行结果

image.png

循环队列

在实际使用队列时,为了使队列空间能重复使用(解决假溢出现象),往往对队列的使用方法稍加改进:无论插入或删除,一旦rear指针增1或front指针增1 时超出了所分配的队列空间,就让它指向这片连续空间的起始位置。自己真从MaxSize-1增1变到0,可用取余运算rear%MaxSize和front%MaxSize来实现。这实际上是把队列空间想象成一个环形空间,环形空间中的存储单元循环使用,用这种方法管理的队列也就称为循环队列。值得注意的是,如果使用循环队列,则rear==front将不作为判断是否为空的标志,因为队列是一个环状结构,所以我们需要引入一个变量来确认队列中的数据数量。

image.png

代码实现

  • 封装类
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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
package ArrayQueue;

//循环队列
public class CircularArrayQueue {
//队头指针,指向的是下一个出队的数据的前一个位置
private int front = -1;
//队尾指针,指向的是下一个入队的数据的前一个位置
private int rear = -1;

//队列存储数据的最大值,因为是通过数组模拟队列,所以需要设置队列的最大值
private int maxSize;

//数据存储的位置
private int[] data;

//当前队列中的数据数量
private int count = 0;

public CircularArrayQueue(int maxSize) {
this.maxSize = maxSize;
this.data = new int[maxSize];
}

/**
* 队列是否为空
*
* @return boolean
**/
public boolean isEmpty() {
return 0 == this.count;
}

/**
* 队列是否已满
*
* @return boolean
**/
public boolean isFull() {
return this.count == this.maxSize;
}

/**
* 数据入队
*
* @param data int 数据值
* @return boolean
**/
public boolean inQueue(int data) {
if (this.isFull()) {
return false;
}
int index = (this.rear + 1) % this.maxSize;
this.data[index] = data;
this.rear = index;
this.count++;
return true;
}

/**
* 数据出队
*
* @return int
* @throws RuntimeException
**/
public int outQueue() {
if (this.isEmpty()) {
throw new RuntimeException("队列为空,出队失败");
}
int index = (this.front + 1) % this.maxSize;
int value = this.data[index];
this.front = index;
this.count--;
return value;
}

/**
* 偷看一下下一个将要出队的数据
*
* @return void
**/
public void peek() {
if (this.isEmpty()) {
return;
}
int index = (this.front + 1) % this.maxSize;
System.out.printf("下一个即将出队的数据为:%d \n", this.data[index]);
}

/**
* 打印队列数据
*
* @return void
**/
public void print() {
if (this.isEmpty()) {
return;
}
System.out.println("下面是队列中的所有数据");
int index;
for (int i = 1; i <= this.count; i++) {
index = (this.front + i) % this.maxSize;
System.out.printf("%d \t", this.data[index]);
}
System.out.println();
}
}
  • 代码执行
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
package ArrayQueue;

public class Main {
public static void main(String[] args) {
CircularArrayQueue arrayQueue = new CircularArrayQueue(10);
arrayQueue.inQueue(10);
arrayQueue.inQueue(20);
arrayQueue.inQueue(30);
arrayQueue.print();
//数据出队
System.out.println("队列出队2次");
System.out.printf("%d \t %d \n", arrayQueue.outQueue(), arrayQueue.outQueue());
arrayQueue.print();
arrayQueue.inQueue(40);
arrayQueue.inQueue(50);
arrayQueue.inQueue(60);
arrayQueue.inQueue(70);
arrayQueue.inQueue(80);
arrayQueue.inQueue(90);
arrayQueue.inQueue(100);
arrayQueue.print();
arrayQueue.inQueue(110);
arrayQueue.inQueue(120);
//数据量超过最大范围
arrayQueue.inQueue(130);
arrayQueue.print();
}
}
  • 执行结果

image.png