这篇文章我们再来介绍一个基本的数据结构:队列
使用场景:
要认识一个数据结构,先来认识一下这个数据结构的应用场景:队列主要是用于排队等场景,像是在买东西排队时,如下图
队列介绍:
认识了队列,我们看一下队列在实现时需要遵循什么原则:
- 队列是一个有序列表,可以用数组或链表来实现(本文我们先用数组来实现,后面我们再介绍链表的代码实现)
- 遵循先入先出的原则。即:先存入队列的数据,要先取出。后存入的要后取出
- 示意图:
(我们先来介绍简易的队列,后面介绍高级一点的循环队列)
代码实现:
下面我们来介绍一下这个数据结构用数组实现的思路分析以及代码实现
队列本本身是有序的,如果用数组模拟,则遵循上图的演示。
maxSize是队列的最大空间,front指向数组的第一个元素的前一个位置,rear指向第一个元素,front会随着数据的输出而改变,rear会随着数据的增加而改变
下面我们边写代码边分析
package java.arrayqueu;
import java.util.Scanner;
/**
* 使用数组模拟队列,此程序不是循环队列
* @author ZhuQiPeng
*
* 1.先编写Queue类,其中的属性等要体现封装性
* 2.用构造器初始化队列,此队列的front和rear初始化为-1
* 3.其中的方法包括isFull、isEmpty、addQueue、getQueue、showQueue、headQueue
*
* 然后编写main函数
* main函数中写测试
* 提供用户界面
* 创建一个字符用于接受用户输入
* 通过用户的选择调用Queue类中的方法
*/
public class ArrayQueue {
public static void main(String[] args) {
//main函数里面写测试
//创建一个队列
Queue queue = new Queue(3);
char key = ' ';
Scanner scanner = new Scanner(System.in);
boolean loop = true;
//输出一个菜单
while(loop) {
System.out.println("s(show): 显示队列");
System.out.println("e(exit): 退出程序");
System.out.println("a(add): 添加数据到队列");
System.out.println("g(get): 从队列取出数据");
System.out.println("h(head): 查看队列头的数据");
key = scanner.next().charAt(0);//接受一个字符
switch(key) {
case 's':
queue.showQueue();
break;
case 'a':
System.out.println("输入一个数");
int value = scanner.nextInt();
queue.addQueue(value);
break;
case 'g':
try {
int res = queue.getQueue();
System.out.printf("取出的数据是%d\n",res);
}
catch(Exception e) {
System.out.println(e.getMessage());
}
break;
case 'h':
try {
int res = queue.headQueue();
System.out.printf("队列的头数据为%d\n",res);
}catch(Exception e) {
System.out.println(e.getMessage());
}
break;
case 'e':
scanner.close();
loop = false;
break;
default:
break;
}
}
System.out.println("程序退出~~");
}
}
class Queue{
private int maxSize;//数组的最大容量
private int front;//头部
private int rear;//尾部
private int[] arr;//该数据用于存放数据, 模拟队列
//用构造器初始化队列
public Queue(int maxSize) {
this.maxSize = maxSize;
arr = new int[maxSize];
front = -1;
rear = -1;
}
//判断队列是否满
public boolean isFull() {
return rear == maxSize - 1;
}
//判断队列是否满
public boolean isEmpty() {
return rear == front;
}
//添加数据到队列
public void addQueue(int n) {
//先判断队列是否满
//如果队列满则提示信息并退出方法
if(isFull()) {
System.out.println("队列已满,不能加入数据");
return;
}
//因为front指向的是头元素的前一个位置
arr[++rear] = n;//先让rear后移再添加
}
//获取元素(出队)
public int getQueue() {
//先判断队列是否为空
if(isEmpty()) {//如果队列为空
//通过抛出异常结束程序
throw new RuntimeException("队列为空,不能取数据");
}
return arr[++front];//先后移front,理由同上
}
//显示队列所有元素
public void showQueue() {
//遍历
//如果队列满则提示信息并退出方法
if(isEmpty()) {
System.out.println("队列为空,没有数据!");
return;
}
for(int i = front+1;i<arr.length;i++) {
System.out.printf("arr[%d]=%d\n",i,arr[i]);
}
}
//显示队列头元素,注意不是取数据
public int headQueue() {
//如果是空没法显示
if(isEmpty()) {
throw new RuntimeException("队列为空,没有数据!");
}
return arr[front+1];//由于不是取数据,所以front不需要++
}
}
问题分析并优化:
- 目前数组只能使用一次,达到长度后没法复用
- 改进成为一个环形队列
由于该数组可反复取放元素,所以需要用到取模(%)运算,将数组下标运算到相应的位置,具体的改变见代码分析。
思路分析:
当我们用一般队列时,判断队列是否满的条件是rear=front,现在,因为不知道rear和front的相对位置,所以需要取模。
由于是环形队列,所以rear会一直增加,但是最大只能是maxSize-1,为了不超过这个值,需要取模获得rear的真实位置。
代码实现:
package Java.arrayqueu;
import java.util.Scanner;
/**
* 用数组模拟的循环队列
* @author ZhuQiPeng
*
* 与普通队列的不同点:
* 1.front和rear从0开始,front指向队列的第一个元素,rear指向最后一个元素
* 2.front和rear后移时,要考虑取模
* 3.遍历时要计算长度
* 4.rear后面留出一个
* 5.增加了一个方法,用来计算队列的长度,为遍历时用
*
* 后移以及计算长度时的公式
* 1.判断是否满:(rear + 1)%maxSize == front,当rear<maxSize-1时,取模后,仍然为rear本身,
* 当超过时,取模后,rear又回到了0~maxSize-1,然后与front相比,确定是否重合,从而确定是否满
* 2.判断是否为空时,仍然为rear==front;
* 3.front 和 rear 自增时,判断下一个位置,(front+1)%maxSize或(rear+1)%maxSize
*
*
*/
public class CircleArrayQueue {
public static void main(String[] args) {
//创建一个环形队列
CircleQueue queue = new CircleQueue(4);//由于预留一个位置,有效元素是3
char key = ' '; // 接收用户输入
Scanner scanner = new Scanner(System.in);
boolean loop = true;
// 输出一个菜单
while(loop) {
System.out.println("s(show): 显示队列");
System.out.println("e(exit): 退出程序");
System.out.println("a(add): 添加数据到队列");
System.out.println("g(get): 从队列取出数据");
System.out.println("h(head): 查看队列头的数据");
key = scanner.next().charAt(0);// 接收一个字符
switch(key) {
case 's':
queue.showQueue();
break;
case 'a':
System.out.println("输入一个数");
int value = scanner.nextInt();
queue.addQueue(value);
break;
case 'g':
try {
int res = queue.getQueue();
System.out.printf("取出的数据是%d\n", res);
} catch (Exception e) {
System.out.println(e.getMessage());
}
break;
case 'h':
try {
int res = queue.headQueue();
System.out.printf("队列头的数据是%d\n", res);
} catch (Exception e) {
System.out.println(e.getMessage());
}
break;
case 'e':
scanner.close();
loop = false;
break;
default:
break;
}
}
System.out.println("程序退出~~~");
}
}
class CircleQueue{
private int maxSize;//数组的最大容量
//front变量做一个调整: front就指向队列的第一个元素,也就是说arr[front]就是队列的第一个元素
//front的初始值为0
private int front;//头部
//rear 的初始值为0
private int rear;//队列尾
private int[] arr;//该数据用于存放数据, 模拟队列
public CircleQueue(int maxSize) {
this.maxSize = maxSize;
arr = new int[maxSize];
//int型变量默认初始化为0,所以这里可以不写
}
// 判断队列是否满
public boolean isFull(){
return (rear + 1) % maxSize == front;
}
//判断队列是否空
public boolean isEmpty() {
return rear == front;
}
//添加数据到队列
public void addQueue(int index) {
// 判断队列是否满
if (isFull()) {
System.out.println("队列满,不能加入数据~");
return;
}
//直接将数据加入
arr[rear] = index;
//rear后移,要考虑取模
rear = (rear + 1) % maxSize;
}
//获取队列的数据,出队列
public int getQueue() {
//判断队列是否空
if(isEmpty()) {
throw new RuntimeException("队列空,不能取数据");
}
//出队,front后移,需要考虑取模
//先把front保存到临时变量
//front后移(考虑取模)
//将临时变量的值返回
int val = arr[front];
front = (front+1) % maxSize;
return val;
}
//显示队列所有元素
public void showQueue() {
//先判断是否为空
if(isEmpty()) {
System.out.println("队列为空!");
return;
}
//由于是循环队列,所以遍历时需要考虑取模
//由于不知道front和rear的相对位置,所以需要计算遍历多少个元素
for(int i = front;i < front + size(); i++) {
System.out.printf("arr[%d]=%d\n",i % maxSize,arr[i % maxSize]);
}
}
//求出当前队列的有效数据的个数
private int size() {
return (rear + maxSize - front)%maxSize;
}
// 显示队列的头数据, 注意不是取出数据
public int headQueue() {
// 判断
if (isEmpty()) {
throw new RuntimeException("队列空的,没有数据~~");
}
return arr[front];
}
}
思路分析在代码里面有了~大家有什么疑问尽管问,我尽量为大家作答
关于队列,这篇文章就到这里了,后面我还会写关于面向对象的文章~
往期推荐:
一篇文章搞定Java稀疏数组!