这篇博文用来使用数组模拟环形队列
这个项目是根据B站尚硅谷韩老师的数据结构与算法课程学习总结的,在此非常感谢!
队列的基本介绍:
- 队列是一个有序列表,可以用数组或是链表来实现。
- 遵循先入先出的原则。即:先存入队列的数据,要先取出。后存入的要后取出
- 示意图:(使用数组模拟队列示意图)
思路分析
使用数组模拟环形队列,一定要考虑数组的下标越界异常,如下思路分析:
- 记头指针为front,尾指针为rear,队列容量为capacity;
- 尾索引的下一个为头索引时表示队列满,即将队列容量空出一个作为约定,这个在做判断队列满的时候需要注意 (rear + 1) % capacity== front 【队列满】;
- rear == front [队列空];
- front指针指向队列的第一个元素,front初始值为0
- rear指针指向队列的最后一个元素的后一个位置【因为希望空出一个容量,保证环形队列】,rear的初始值为0;
- 此时队列中有效的数据的个数为(rear + capacity- front) % capacity【队列中有效的数据的个数】;
代码实现
因为队列可以通过数组或者链表进行模拟,且有多种类型的数组,这里我们先创建一个数组接口类Queue
以此来抽取出数组的抽象代码,将具体实现交由子类,使用者只需要通过规范的接口通过不同的实现子类创建不同结构的队列,Queue接口类
的代码如下所示:
package edu.hebeu.queue;
/**
* 这个接口用来定义队列
* @author 13651
*
*/
public interface Queue {
/**
* 这个方法用来获取队列的头,注意该方法只是显示队列的头,并非取数据
* @return
*/
int head();
/**
* 这个方法用来入队列
* @param data
*/
void inQueue(int data);
/**
* 这个方法用来出队列
* @return
*/
int outQueue();
/**
* 这个方法用来判断队列是否为空
* @return
*/
boolean isEmpty();
/**
* 这个方法用来判断队列是否为满
* @return
*/
boolean isFull();
/**
* 这个方法用来显示队列
*/
void showQueue();
}
编写队列接口实现类:环形队列类ArrayCricleQueue
,具体代码如下所示:
package edu.hebeu.queue;
/**
* 这个类用来通过数组模拟一个环形队列,相比于ArrayQueue类实现的错误队列,该队列能够实现空间的重复利用,
* 该类能够正确的使用数组实现队列
* @author 13651
*
*/
public class ArrayCricleQueue implements Queue{
/**
* 该数组用来存放队列的数据,即队列就是该数组模拟的
*/
private int[] array;
/**
* 这个变量表示队列的头指针
*/
private int front;
/**
* 这个变量表示队列的尾指针
*/
private int rear;
/**
* 这个变量表示队列的最大容量
*/
private int capacity;
/**
* 这个构造器用来初始化模拟的队列
* @param capacity
*/
public ArrayCricleQueue(int capacity) {
this.capacity = capacity + 1; // 初始化容量【需要注意底层数组的容量要比队列的容量大1个,以便再指针后移取模保证不会出现数组越界,实现环形队列】
array = new int[this.capacity]; // 初始化数组
front = 0; // 将头指针初始值为0,即指向队列头部,【即指向头元素的位置】
rear = 0; // 将头指针初始值为0,即指向队列尾部的后一个位置,【指向尾元素的后一个位置】
}
@Override
public int head() {
if(isEmpty()) {
return -1;
}
return array[front]; // 将头结点的元素返回,因为front指向的是队列的头元素的位置
}
@Override
public void inQueue(int data) {
if(isFull()) { // 如果队列满了
System.err.println("队列满,不能加入!");
return;
}
array[rear] = data; // 先向当前的尾指针rear位置处添加数据
rear = (rear + 1) % capacity; // 再将尾指针rear使用对应的算法取模后移,但是为了保证数组不会越界,需要考虑取模
}
@Override
public int outQueue() {
if(isEmpty()) {
System.err.println("队列空!");
return -1;
}
int value = array[front]; // 先将当前front指针指向的元素取出保存
front = (front + 1) % capacity; // 再将front指针后移,但是为了保证数组不会出现越界,需要考虑取模
return value; // 将之前front指向的数据返回出去
}
@Override
public boolean isEmpty() {
// TODO Auto-generated method stub
return front == rear;
}
@Override
public boolean isFull() {
// TODO Auto-generated method stub
return (rear + 1) % capacity == front;
}
@Override
public void showQueue() {
if(isEmpty()) { // 如果队列为空
System.err.println("队列空!");
return;
}
for(int i = front; i < front + size(); i++) { // 遍历所有的有效数据
System.out.printf("queue[%d] = %d;\n", i % capacity, array[i % capacity]);
}
}
/**
* 这个方法用来计算环形数组内有效数据的个数
* @return
*/
private int size() {
// TODO Auto-generated method stub
return (rear + capacity- front) % capacity;
}
}
测试类Test
的编写,该类主要能够合理的调用队列类中的方法,保证程序的逻辑和连贯性,如下代码所示:
package edu.hebeu;
import java.util.Scanner;
import edu.hebeu.queue.ArrayCricleQueue;
import edu.hebeu.queue.ArrayQueue;
import edu.hebeu.queue.Queue;
public class Test {
private static Queue QUEUE;
private static final Scanner SCANNER = new Scanner(System.in);
private static boolean IS_EXIT = false;
public static void main(String[] args) {
System.out.print("请输入队列的初始化容量:"); int capacity = SCANNER.nextInt();
QUEUE = new ArrayCricleQueue(capacity); // 创建指定容量的环形队列
menu();
}
private static void menu() {
while(!IS_EXIT) {
System.out.println();System.out.println();System.out.println();
System.out.println("请选择:");
System.out.println("i(in)元素入队列");
System.out.println("o(out)元素出队列");
System.out.println("h(head)显示队列头元素");
System.out.println("s(show)显示队列元素");
System.out.println("e(exit)退出程序");
char keyword = SCANNER.next().charAt(0);
switch(keyword) {
case 'i':
System.out.print("请输入要入队列的元素:"); int inData = SCANNER.nextInt();
QUEUE.inQueue(inData);
break;
case 'o':
int outData = QUEUE.outQueue();
System.out.println("出队列元素:" + outData);
break;
case 'h':
int headData = QUEUE.head();
System.out.println("队列头元素:" + headData);
break;
case 's':
QUEUE.showQueue();
break;
case 'e':
IS_EXIT = true;
break;
default:
break;
}
if(IS_EXIT) {
System.out.println("bye~~~");
break;
}
}
}
}
测试
创建容量为3的队列,加入如下元素:
再次加入元素,会提示:
我们可以显示队列中所有的元素:
当然也可以查看头元素的值:
当然,也能实现出队列操作,此时队列头也会发生相应的变化,如下:
如果一直出队列,当队列中没有元素时会有如下提示:
当然该环形队列的容量是可以重复使用的,如下: