队列
1.队列是什么
一种特殊的线性表,特点是先进先出:
- 先进,表示队列的数据新增操作只能在队尾进行
- 先出,表示队列的数据删除操作只能在队头进行
存储方式可以分为:链式存储和顺序存储
顺序存储会存在“假溢出”的现象,即数组中元素并未存满(队首移除了元素,但是队尾指针已指向最后一个数组下标)
两个粗暴的解决方案为:
- 消耗O(n)的时间复杂度去移动数据(向队头方向)
- 开辟足够大的空间保证数组不会越界
还有通过队列的一个特殊变种来解决,叫做循环队列。
循环队列进行新增数据元素操作时,首先判断队列是否为满。如果不满,则可以将新元素赋值给队尾,然后让 rear 指针向后移动一个位置。如果已经排到队列最后的位置,则 rear指针重新指向头部。
循环队列进行删除操作时,即出队列操作,需要判断队列是否为空,然后将队头元素赋值给返回值,front 指针向后移一个位置。如果已经排到队列最后的位置,就把 front 指针重新指向到头部。
样就能在不开辟大量存储空间的前提下,不采用 O(n) 的操作,也能通过移动数据来完成频繁的新增和删除数据。
但是这样又存在一个问题,当 front 指针和 rear 指针相等时,无法判断队满还是队空。解决方案有三:
-
牺牲一个的单元,约定当front指向rear后的一个单元则代表队满,如图3-7(d2)
队满条件为:(Q.rear+1)%MaxSize == Q.front
队空条件: Q.rear==Q.front
队列长度: (Q.rear - Q.front + MaxSize ) % MaxSize -
新增一个数据成员(size)来表示元素个数
则 Q.size = 0为空 Q.size = MaxSize 为满 -
新增一个数据成员(flag)来表示情况,flag=1下删除导致的rear == front则为队空,flag=2下插入导致的rear==front则为队满
链式存储就是一个单链表,同时增加了 front 指针和 rear 指针。链式队列和单链表一样,通常会增加一个头结点,并另 front 指针指向头结点。头结点不存储数据,只是用来辅助标识。这主要是为了防止删除最后一个有效数据结点后, front 指针和 rear 指针变成野指针,导致队列没有意义了。有了头结点后,哪怕队列为空,头结点依然存在,能让 front 指针和 rear 指针依然有意义。
双端队列
2.队列的案例
约瑟夫环问题。约瑟夫环是一个数学的应用问题,具体为,已知 n 个人(以编号 1,2,3…n 分别表示)围坐在一张圆桌周围。从编号为 k 的人开始报数,数到 m 的那个人出列;他的下一个人又从 1 开始报数,数到 m 的那个人又出列;依此规律重复下去,直到圆桌周围的人全部出列。
解析:
这个问题的输入变量就是 n 和 m,即 n 个人和数到 m 的出列的人。输出的结果,就是 n 个人出列的顺序。
这个问题,用队列的方法实现是个不错的选择。它的结果就是出列的顺序,恰好满足队列对处理顺序敏感的前提。因此,求解方式也是基于队列的先进先出原则。解法如下:
1.先把所有人都放入循环队列中。注意这个循环队列的长度要大于或者等于 n。
2.从第一个人开始依次出队列,出队列一次则计数变量 i 自增。如果 i 比 m 小,则还需要再入队列。
3.直到i等于 m 的人出队列时,就不用再让这个人进队列了。而是放入一个用来记录出队列顺序的数组中。
4.直到数完 n 个人为止。当队列为空时,则表示队列中的 n 个人都出队列了,这时结束队列循环,输出数组内记录的元素。
代码实现:
package cn.iyhome;
import java.util.LinkedList;
public class Josephus {
public static void main(String[] args) {
Jose(10,5);
}
public static void Jose(int n, int m) {
// 创建链式队列
LinkedList<Integer> queue = new LinkedList<Integer>();
for (int i = 1; i < n + 1; i++) {
queue.add(i);
}
// 从编号2开始报数
int k = 2;
int element = 0;
for (int i = 1; i < k; i++) {
element = queue.poll();//从队首出队
queue.add(element);//将该元素从队尾入队
}// 至此,队列已满足题目中从编号2开始报数的要求
//
int count = 1;
while (queue.size() > 0) {
element = queue.poll();//出队
if (count < m) {//没有数到m
queue.add(element);//入队到队尾
count++;//继续报数
} else {//数到m,不入队
count = 1;//重新从1开始报数
System.out.println(element);
}
}
}
}