标签 : Java
数据结构
算法
作者 : Maxchen
版本 : V1.0.0
日期 : 2020/4/1
1.环形队列——概念
上篇《Java数据结构与算法02——队列》我们讲了基于数组的顺序队列实现方式,顺序队列每次出队操作都需要进行数据迁移,而数据迁移的代价比较大,每次数据迁移的时间复杂度为O(n),这样会极大的影响队列的使用性能。如果我们出队时,队头往后移动一位,这样我们就避免每次出队都进行数据迁移,我们只需要在只有在tail等于数组大小且head不等于0时,进行一次数据迁移,将已经出队留下的空间继续供入队时使用。下图是数据迁移的过程1
循环队列是对顺序队列的改进,因为顺序队列不可避免的数据迁移操作,数据迁移操作会导致队列的性能下降,为了避免这个问题,将队列改造成循环的,当tail到达数组的最大下标时,重新指回数组下标为0的位置,这样就避免了数据迁移。先来看看循环队列的出队、入队操作:
2.环形队列——代码实现
我们按照环形队列的原理,在顺序队列的基础上进行改造,整体运行流程如下图
第一步: 环形队列满的条件是(rear+1)%n== front
,环形队列为空的条件是rear == front
,n代表数组最大容量,将原来顺序队列判断满和判断空的代码
改为
//判断队列是否满
public boolean isFull(){
return (rear + 1) % maxSize == front;
}
//判断是否为空
public boolean isEmpty(){
return rear == front;
}
第二步: front需要指向队列的第一个元素,将原来的代码
改为
//添加队列数据,入队列
public void addQueue(int n){
……
rear++;
arr[rear] = n;
}
//获取队列数据,出队列
public int getQueue(){
……
//这里需要分析出 front 是指向队列的第一个元素
//1、先把front对应的值保留到一个临时变量
//2、将front后移,考虑取模
//3、将临时保存的变量返回
int value = arr[front];
front = (front + 1) % maxSize;
return value;
}
//显示队列的头数据
public int headQueue(){
……
//由于front始终指向第一个元素,队列的头数据直接从front中获取
return arr[front];
}
第三步: 修改显示队列的代码
// 求出当前队列有效数据个数
public int size(){
return (rear + maxSize - front) % maxSize;
}
//显示队列所有数据
public void showQueue(){
//遍历
if(isEmpty()){
System.out.println("队列空,无数据");
return;
}
for (int i=front; i< front + size(); i++){
System.out.printf("arr[%d]=%d\n", i % maxSize, arr[i % maxSize]);
}
}
3.环形队列——测试
第一步: a
添加队列数据,直到添加到队列满为止并点击s
查看目前的环形队列
第二步: g
获取队列数据,点击s
查看目前的环形队列并点击h
显示队列的头数据
第三步: 再一次点击a
添加队列数据,我们发现最后添加的队列数据又回到了第一个元素arr[0]
4.环形队列——整体代码
下面附上这次环境队列的所有代码
package com.maxchen.demo.sparsearray;
import java.util.Scanner;
/**
* @ClassName: CircleArrayDemo
* @Description: TODO
* @Author Maxchen
* @Date 2020/4/1 14:13
* @Version V1.0.0
*
*/
public class CircleArrayDemo {
public static void main(String[] args) {
//创建队列
//设置4表示队列的有效数据最大是3
CircleArray circleArray = new CircleArray(4);
//接收用户输入
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':
circleArray.showQueue();
break;
case 'a':
System.out.println("添加队列数据");
int value = scanner.nextInt();
circleArray.addQueue(value);
break;
case 'g':
try {
int res = circleArray.getQueue();
System.out.printf("获取队列数据:%d\n",res);
} catch (Exception e) {
System.out.println(e.getMessage());
}
break;
case 'h':
try {
int res = circleArray.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 CircleArray{
//数组最大容量
private int maxSize;
//front 指向队列的第一个元素,第一个元素的队列用arr[front]表示
//front初始值0
private int front;
//rear 指向队列的最后一个元素,空出一个空间作为约定
//rear初始值0
private int rear;
//存放数据并模拟队列
private int[] arr;
public CircleArray(int arrMaxSize){
maxSize = arrMaxSize;
arr = new int[maxSize];
}
//判断队列是否满
public boolean isFull(){
return (rear + 1) % maxSize == front;
}
//判断是否为空
public boolean isEmpty(){
return rear == front;
}
//添加队列数据,入队列
public void addQueue(int n){
//判断队列是否满,满了无法添加队列
if (isFull()){
System.out.println("队列满了无法添加队列");
return;
}
arr[rear] = n;
rear = (rear + 1) % maxSize;
}
//获取队列数据,出队列
public int getQueue(){
//队列空,不能取数据
if(isEmpty()){
throw new RuntimeException("队列空,不能取数据");
}
//这里需要分析出 front 是指向队列的第一个元素
//1、先把front对应的值保留到一个临时变量
//2、将front后移,考虑取模
//3、将临时保存的变量返回
int value = arr[front];
front = (front + 1) % maxSize;
return value;
}
// 求出当前队列有效数据个数
public int size(){
return (rear + maxSize - front) % maxSize;
}
//显示队列所有数据
public void showQueue(){
//遍历
if(isEmpty()){
System.out.println("队列空,无数据");
return;
}
for (int i=front; i< front + size(); i++){
System.out.printf("arr[%d]=%d\n", i % maxSize, arr[i % maxSize]);
}
}
//显示队列的头数据
public int headQueue(){
//判断
if(isEmpty()){
throw new RuntimeException("队列空的,没有数据");
}
return arr[front];
}
}