前言
本章用于主要讲述 队列 的介绍与使用,阅读时长约 7min 。
队列
介绍:
队列(Queue) 是一种先进先出FIFO ,只允许在一端进行插入操作,在另一端进行删除操作的线性表
规格:
在顺序队列的存储结构中,需要分配一块地址连续的存储区域来依次存放队列中从队首到队尾的所有元素;队列是由n(n>=0) 个具有相同类型的数据元素所构成的有限序列;和栈一致,队列也一种操作受限制的线性表;允许进行插入的一端称为队尾(rear),允许进行删除的一端称为队首(front);队列中没有元素时,称之为空队列
特点:
- 先进先出,先进的在队列前,后进的在队列后;
- 限制了只能在队首进行数据的插入与以及队尾进行数据的删除
复杂度分析
队列属于常见的一种线性结构,对于入队与出队而言,时间复杂度都为 O(1)
前奏准备
本文章讲解所用的编程语言为 Java
基本方法
在队列中首先我们需要声明队首 front 与队尾 rear,以及队列存储空间 datas;在非空队列中,front 指向队首元素,rear 指向队尾元素;
方法实现代码不多相对简单,难点是 入队出队思想 以及 循环队列解决假溢出
public class Queue {
public Object[] datas; // 队列存储空间
public int front; // 声明队首
public int rear; // 声明队尾
public Queue(int maxSize) {
front = rear = 0; // 默认为0 (空队列)
datas = new Object[maxSize]; // 为队列分配存储单元
}
// 队列是否为空
public boolean isEmpty() {}
// 清空栈
public void clear() {}
// 添加入队
public void offer(Object e) {}
// 删除出队
public Object poll() {}
// 取出队首元素
public Object peek() {}
// 队列长度
public int length() {}
// 输出从队首到队尾的所有元素
public void display() {}
}
假溢出
在实现方法之前,我们先来弄清楚假溢出是什么以及如何解决假溢出的问题;
先看一组图:
- 创建一个初始的空队列,队首与队尾都为0,默认最大存储空间为5;
- 将ABC元素依次入队,队首不变,队尾向后移三步;(ps: 因为队尾始终指向队列末的下一个元素)
- 将AB元素出队,队尾不变,队首移动到2;
- 最后再将DE元素入队,队首不变,但队尾达到最大长度5了,因为越界被提出队列了;这里就会问,可是我队首还有两个元素的存储空间啊为什么就溢出了,因此这种溢出并不是由于数组空间不够而产生的溢出;这种因顺序队列的多次入队和出队操作后出现有存储空间,但不能进行入队操作的溢出现象称为"假溢出";
要解决这个问题,我们可以把顺序队列所使用的存储空间看成是一个逻辑上首尾相连的循环队列,即循环顺序队列
循环顺序队列
循序顺序队列可用于解决单队列假溢出的现象,后续队列都按照这个为规范基准;
我们先来看一张图片:
- 首先初始化一个·空的循环顺序队列,
front与rear都为0,最大长度为6; - 将abc入队,队首不变,队尾
rear移动到3; - 将a出队,队尾不变,队首
front移动到1; - 依次将defg入队,由于是循环队列,rear不会因为越界而超出,而是将剩余的存储空间都补上,就会使
rear与front都在同一位置,从而造成 无法区分对空和队满的状态;(因为判空的条件为rear == front)
想要解决这个问题,我们可以使用以下三种方法:
-
设置一个标志变量
首先设置一个初始值为0的标志变量
flag,每当入队操作成功后就设置为1,而每当出队操作成功就设置为0;这种方法判空条件为:front==rear && flag == 0,队满的判断条件为:front == rear && flag == 1; -
设置一个计数器
首先设置一个初始值为0的计数变量
num,每当入队操作成功后就让num的值加1,而每当出队操作成功就就让num的值减1;这种方法判空条件为:num == 0,队满的判断条件为:num > 0 && front == rear; -
少用一个存储单元(推荐)
当顺序存储空间的容量为
ma1xSize时,让其最多只允许存放maxSize - 1个数据元素;这种方法判空条件为:
front == rear,队满的判断条件为:front = (rear + 1) % maxSize;
解决了队列的基本问题,接下来可以开始实现方法了
isEmpty(判空)与 clear(清空)
老规矩,先来两个简单的;isEmpty 用于判断队列是否为空,clear 用于清空队列
思路:
判空:因为每次只有队列为空的情况下 队首才等于队尾
清空:将队首队尾赋值为0(空状态)
代码示例
// 队列是否为空
public boolean isEmpty() {
return front == rear;
}
// 清空栈
public void clear() {
front = rear = 0;
}
offer(入队)
入队操作是将新的数据元素插入到队列的尾部,使其成为新的队尾元素,类型为 Object
思路:
- 判断队列是否已满
- 将新的数据元素存入到
rear指向的存储位置中,使其成为新的队尾元素; - 再将
rear加一,使其始终指向队尾元素的下一个存储位置
代码示例
// 添加入队
public void offer(Object e) {
// 1. 判断是否已满
if ((rear + 1) % datas.length == front) {
throw new RuntimeException("队列已满");
} else {
// 2. 将元素存入对应数组存储位置中
datas[rear] = e;
// 3. 更新 rear 让其始终指向队尾的下一个存储元素
// ps: 这里无法直接加1,需要使用取余的方式使其始终是循环填补的状态
rear = (rear + 1) % datas.length;
}
}
poll(出队)
出队操作是将队首元素从队列中移去,并返回被移去的队首元素的值
思路:
- 判断队列是否为空,若为空,返回空值
- 先取出
front所值的队首元素的值 - 再将
front循环加1
代码示例
// 删除出队
public Object poll() {
// 1. 判断是否为空
if (front == rear) {
return null;
}
// 2. 将队首保存
Object cur = datas[front];
// 3. 更新 front 指向下一个存储元素
front = (front + 1) % datas.length;
// 4. 返回出队队首
return cur;
}
peek(取出)
peek 用于取出队首元素
思路:
- 判断队列是否为空,若为空,返回空值
- 若不为空,返回当前队首元素
代码示例
// 取出队首元素
public Object peek() {
// 1. 判断是否为空
if (front == rear) {
return null;
} else {
// 2. 返回队首
return datas[front];
}
}
length(长度)
length 用于返回当前队列长度
因为考虑到循环队列的因素,不能单纯的用 rear - front 来返回队列长度;必须采用 加上队列容量长度再去取余才能达到最准确的队列长度;
代码示例
// 队列长度
public int length() {
// 队尾 - 队首 + 队列长度 % 队列长度
return (rear - front + datas.length) % datas.length;
}
display(输出)
display 用于从队首到队尾的所有元素
代码示例
// 输出从队首到队尾的所有元素
public void display() {
if (isEmpty()) {
System.out.println("队列为空");
} else {
// 起始为队首,队首不等于队尾;最后附加取余条件,使对首不重合
for (int i = front; i != rear; i = (i + 1) % datas.length) {
System.out.println(datas[i]);
}
}
}
打印示例
最后我们将所有方法测试一下~
以上就是循环顺序队列的介绍与方法实现了 o( ̄▽ ̄)d