单向队列和循环队列
前言
本文主要介绍队列这种数据结构,并介绍使用数组如何实现,供大家参考
一、队列是什么?
这里引用百度百科介绍 : 队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。
从上面一段介绍中我们可以得到什么信息呢
- 队列是一种特殊的线性表
- 队列只允许在表的前段(front)删除,在表的后段(rear)插入
一提到线性表大家首先会想到什么呢?对!数组,队列是一种特殊的线性表,换句话说,可以使用数组实现
二、实现思路和代码实现
实现思路
我们先来一张数组实现队列的示意图:
如图中的示例,实现思路如下
1)根据队列的定义,可以得知,队列有两个特点,只允许在队列头进行删除(出队),在队列尾进行插入(入队),所以我们定义两个指针分别标示队列头(front)和队列尾(rear)
2) 插入元素(入队),由百度百科的定义我们可以得知,队列只能在尾端(rear)插入,插入元素(入队)的时候我们需要把尾指针+1,即rear+1,当然这里也要考虑队列满的情况,如果 rear+1=MaxSize-1,说明数组满了,不能再插入数据了
3) 删除元素,也是一样的,删除元素只能在队列头部进行,就是是头部指针front , 首先要考虑是否是空对列,如图所示,把指针初始化成-1,即front = rear的时候,队列是取不出元素的,如果不是队列不是空的话,我们把头指针+1,即front +1的位置,即可完成出队的操作
代码如下(示例):
package arr;
import java.util.Arrays;
public class MyQueueTest {
public static void main(String[] args) {
//初始化队列
MyQueue queue = new MyQueue(4);
System.out.println(queue.toString());
//添加第一个元素
queue.addQueue("1");
System.out.println(queue.toString());
//把队列加满
queue.addQueue("2");
queue.addQueue("3");
queue.addQueue("4");
System.out.println(queue.toString());
//继续添加会抛出异常
// queue.addQueue("5");
//取出第一个元素
System.out.println(queue.takeQueue());
System.out.println(queue.toString());
//取出所有元素
System.out.println(queue.takeQueue());
System.out.println(queue.takeQueue());
//继续取出会提示队列为空
// System.out.println(queue.takeQueue());
System.out.println(queue.toString());
//取出所有元素之后再添加元素,仍会抛出异常
queue.addQueue("5");
}
}
class MyQueue{
private static final int DEFAULT_SIZE = 16;
private int front;
private int rear;
private String[] arr;
private int size;
/**
* 初始化默认大小的队列
*/
public MyQueue(){
this.front = -1;
this.rear = -1;
this.arr = new String[DEFAULT_SIZE];
}
/**
* 初始化制定大小的队列
*/
public MyQueue(int maxSize){
this.size = maxSize;
this.front = -1;
this.rear = -1;
this.arr = new String[size];
}
/**
* 判断对列是否为空
*/
public boolean isEmpty(){
return this.front == this.rear;
}
/**
* 判断队列是否满
*/
public boolean isFull(){
return rear == arr.length -1;
}
/**
* 入队操作
*/
public void addQueue(String element){
if(isFull()){
throw new RuntimeException("队列已满! 无法继续添加元素!");
}
this.arr[++ rear] = element;
}
/**
* 出队操作
*/
public String takeQueue(){
if(isEmpty()){
throw new RuntimeException("队列为空!");
}
String element = this.arr[++ front];
this.arr[front] = null;
return element;
}
@Override
public String toString() {
return "MyQueue{" +
"front=" + front +
", rear=" + rear +
", arr=" + Arrays.toString(arr) +
", size=" + size +
'}';
}
}
遗留问题
大家运行上面代码可以比较清晰的看到数组实现队列,模拟入队,出队操作。但是在代码的最后一行,把队列元素全部取出之后,还是没法继续再添加元素。也就是说上面实现的这个队列是一次性的。还需要继续优化,这样就有了环形队列,可以重复使用的队列。
三、环形队列
环形队列实现思路
环形队列是可以循环使用的,是上面实现队列的一个加强版,话不多说,直接上图 :
如图中的示例,是环形队列的逻辑结构,内存结构还是普通队列的图示,为了方便理解,把首尾连接,看到就是这个样子。下面开始分析实现思路
1) 对环形队列而言,会遇到第一个问题,怎么判断队列空和队列满?从图中可以想象的出来,队列空和满如果都是rear = front, 这样肯定有问题。这里有两种判断思路,一种是空出一个位置,不存放元素,头指针front和尾指针rear只要有元素存在就不会重合,这也是最常用的一种方式;另外一种是重新引入一个变量,可以通过计算队列中元素的个数加上指针位置来判断。这里的实现是采用第一种方式,先对指针含义做一个调整:front指向第一个元素;rear指向数组最后一个元素的后一个位置,这样我们就可以空出一个位置,判断队列是否满
2)当队列为空时(左图) front == rear; 当队列满时(右图),代码如下
if(rear + 1 == maxSize){
rear = 0;
|else{
rear = rear + 1;
}
使用模运算转换一下 : ( rear + 1) % maxSize = front;
3) 队列中元素的个数也根据图示何以推算出来 (rear - front + maxSize) % maxSize
4)出队和入队操作也是一样的可以使用模运算来处理
代码如下(示例):
package arr;
import java.util.Arrays;
public class MyCircularQueueTest {
public static void main(String[] args) {
//初始化队列
MyCircularQueue queue = new MyCircularQueue(4);
System.out.println(queue.toString());
//添加第一个元素
queue.addQueue("1");
System.out.println("队列的长度 : "+queue.getLength() + " 队列 : " + queue.toString());
//把队列加满
queue.addQueue("2");
queue.addQueue("3");
System.out.println("队列的长度 : "+queue.getLength() + " 队列 : " + queue.toString());
//继续添加会抛出异常
// queue.addQueue("5");
//取出第一个元素
System.out.println(queue.takeQueue());
System.out.println("队列的长度 : "+queue.getLength() + " 队列 : " + queue.toString());
//取出所有元素
System.out.println(queue.takeQueue());
System.out.println(queue.takeQueue());
//继续取出会提示队列为空
// System.out.println(queue.takeQueue());
System.out.println("队列的长度 : "+queue.getLength() + " 队列 : " + queue.toString());
//取出所有元素之后再添加元素,仍会抛出异常
queue.addQueue("5");
System.out.println("队列的长度 : "+queue.getLength() + " 队列 : " + queue.toString());
}
}
class MyCircularQueue{
private static final int DEFAULT_SIZE = 16;
private int front;
private int rear;
private String [] arr;
private int maxSize;
public MyCircularQueue(int maxSize) {
this.front = 0;
this.rear = 0;
this.maxSize = maxSize;
arr = new String[maxSize];
}
/**
* 队列是否为空
*/
public boolean isEmpty(){
return front == rear;
}
/**
* 对列是否已满
*/
public boolean isFull(){
return (rear + 1) % maxSize == front;
}
/**
* 环形队列中数组的元素
*/
public int getLength(){
return (rear - front + maxSize) % maxSize;
}
/**
* 入队
*/
public void addQueue(String element){
if(isFull()){
throw new RuntimeException("队列已满!");
}
arr[rear] = element;
rear = (rear + 1) % maxSize ;
}
/**
* 出队
*/
public String takeQueue(){
if(isEmpty()){
throw new RuntimeException("队列为空!");
}
String result = arr[front];
arr[front] = null;
front = ( front + 1 ) % maxSize;
return result;
}
@Override
public String toString() {
return "MyCircularQueue{" +
"front=" + front +
", rear=" + rear +
", arr=" + Arrays.toString(arr) +
", maxSize=" + maxSize +
'}';
}
}