从1开始学Java数据结构与算法——用数组实现队列与环形队列

从1开始学Java数据结构与算法——用数组实现队列与环形队列

    • 队列概念
    • 用数组实现简单队列
    • 方法分析
    • 简单数组队列代码实现
    • 存在的问题分析:
    • 解决方法:
    • 思路分析
    • 变量以及公式调整
    • 用数组实现环形队列代码实现
    • 写在最后

队列概念

队列是一个有序列表,可以用数组或链表来实现。遵循先入先出的原则

用数组实现简单队列

编写一个类,里面有一个成员变量Queue是数组,一个MaxSize表示该数组队列的最大容量,一个rear和front分表表示队尾和对头,且初始值都为-1。
当有数据入队的时候,先判断是否队满,即判断队尾是否和MaxSize-1相等(rear == MaxSize-1?),如果不等即不满,队尾+1,往上移动一位,然后放入数据
当有数据出队的时候,先判断是否队空,即判断队头和队尾是否相等(rear == front?),不等即不为空,队头数据出队,则队头+1,往上移动一位。
从1开始学Java数据结构与算法——用数组实现队列与环形队列_第1张图片

方法分析

根据上面的分析,我们可以大致确定用数组去实现队列至少需要一下方法:
一个构造队列的方法
一个入队方法
一个出队方法
一个判断队列是否为空的方法
一个判断是否为满的方法
还可以适当的加一下打印队列的检测方法

简单数组队列代码实现

/**
 * @author centuowang
 * @param
 * 		MaxSize:用数组实现队列,数组的最大长度
 * 		front:队头
 * 		rear:队尾
 * 		arr[]:用于存放数据
 */
class ArrayQueue {
	
	private int MaxSize;//表示数组的最大长度
	private int front;//队头
	private int rear;//队尾
	private int[] arr;//用于存放数据
	
	//创建队列构造器,构造的时候传值确定数组大小(即队列最大长度)
	public ArrayQueue(int arrMaxSize){
		
		MaxSize = arrMaxSize;
		arr = new int[arrMaxSize];
		front = -1;//初始化,队头赋-1的值,表示指向队列头的前一个位置,因为初始化队列为空,所以指向-1
		rear = -1;//初始化,队尾赋-1的值,表示指向队列的最后一个数据,因为初始化队列为空,所以先赋值-1
	}
	
	//判断是否队满
	public boolean is_Full() {
		//如果队尾已经指到了MaxSize-1,表示已经指到了数组的最后一个位置,因为数组下标从0开始
		return rear == MaxSize - 1;
	}
	
	//判断是否队空
	public boolean is_Empty() {
		//队头front和队尾rear相等,则表示为空
		return rear == front;
	}
	
	//入队
	public void enterQueue(int number) {
		//先判断是否队满
		if(is_Full()){
			//抛出队满不能入队异常
			throw new RuntimeException("队列已满,不能入队");
		}else {
			//队尾移动+1
			rear++;
			//输入进入数组
			arr[rear] = number;
		}
	}
	
	//出队
	public void leaveQueue() {
		//先判断是否队空
		if(is_Empty()) {
			throw new RuntimeException("队列为空,不能出队");
		}else {
			//队头移动+1
			front++;
			System.out.println("出队数据为:"+arr[front]);
			arr[front] = 0;//这里因为是数组,默认值为零,所以置零表示为空即可
		}
	}
	
	//打印检测
	//打印队列数据
	public void showQueue() {
		//从队头开始,到队尾结束
		if(is_Empty()) {
			System.out.println("队列为空");
		}else {
			for(int i=front+1; i<=rear; i++) {
				System.out.println(arr[i]);
			}
		}
	}
	
	//打印队头数据
	public void showQueueFront() {
		if(is_Empty()) {
			System.out.println("队列为空");
		}else {
			System.out.println("队头数据为:"+arr[front+1]);
		}
	}
	
}

存在的问题分析:

这样用数组实现的队列,基本功能看似都实现了,入队、出队、判断队空、判断队满、打印队列、打印队头。
但是仔细分析一下我们上面的代码,如果该数组入队满了之后,队头front指向MaxSize-1,队尾指向-1,这没什么问题。但当我们开始组个出队了呢?全部出队完毕之后,这时候队头front依然指向MaxSize-1,而队尾也指向MaxSize-1,队头=队尾(front=rear),判断为队空,没问题,但是这个时候如果要再入队,就会发生错误,因为队头front=MaxSize-1,判断为队满!

解决方法:

环形队列

思路分析

我们对front和rear两个变量的含义进行调整,当队尾rear指到最后的时候,去判断front是否还在第一个位置,如果在的话,说明已满,如果不在的话,说明有数据出队,那么新的需要入队的数据,就可以放到出队数据的位置上,以达到数组复用的目的
从1开始学Java数据结构与算法——用数组实现队列与环形队列_第2张图片

变量以及公式调整

front:之前是指向第一个元素的前一个位置,现在我们改为就另它指向第一个元素,初始值=0
rear:之前是指向最后一个元素,现在我们另它指向队尾的后一个位置,初始值=0
判断队空的条件就是:front==rear
此外,我们引入模除的概念%,就是除法取余
那么我们判断队满的条件就是:(rear+1)%MaxSize == front
这里需要注意,既然rear指向了队尾的后一个位置,那么我们数组的有效位置也就成了MaxSize-1个
那么数组中有效数据的个数就是:(rear-front+MaxSize)%MaxSize个
这里我们来分析一下为什么要引入模除%的原因:
因为在实际的情况下,MaxSize=4,但是随着入队和出队的高频率操作,rear是有可能会大于4的,那么我们又希望达到复用的目的,当rear大于4的时候且front≠0的情况下,可以复用前面的数组位置。那么我们就对rear的值以MaxSize为一组去划分,也就是rear的值在区间[1,MaxSize]为一组,在[MaxSize+1,2MaxSize]为一组。在每一组上对rear进行模除,也就是rear%MaxSize取得的余数,永远都在0-3之间那么也就达到了对数组位置进行复用的目的
从1开始学Java数据结构与算法——用数组实现队列与环形队列_第3张图片

用数组实现环形队列代码实现

/**
 * @author centuowang
 * @param
 * 		MaxSize:用数组实现循环队列,数组的最大长度
 * 		front:队头,指向第一个数据
 * 		rear:队尾,指向最后一个数据的后一个位置
 * 		arr[]:用于存放数据
 */
class CircleArray{
	
	private int MaxSize;
	//因为int默认值就是0,所以可以不用对front和rear进行初始化
	private int front;
	private int rear;
	private int[] arr;
	
	//循环数组构造器
	public CircleArray(int arrMaxSize) {
		MaxSize = arrMaxSize;
		arr = new int[arrMaxSize];
	}
	
	//判断队满
	public boolean is_Full() {
		return (rear+1)%MaxSize == front;
	}
	
	//判断队空
	public boolean is_Empty() {
		return front == rear;
	}
	
	//入队
	public void enter_Queue(int number) {
		//先判断是否队满
		if(is_Full()) {
			throw new RuntimeException("队列已满,不能入队");
		}else {
			//因为rear指向的就是最后一个数据的后一个位置,所以先加入数据,再后移
			arr[rear] = number;
			//这里的后移注意做个循环数组的模除判断
			rear = (rear+1)%MaxSize;
		}
	}
	
	//出队
	public void leave_Queue() {
		//先判断是否队空
		if(is_Empty()) {
			throw new RuntimeException("队列为空,不能出队");
		}
		//这里用置零表示出队,先出队,再后移动
		arr[front] = 0;
		front = (front+1)%MaxSize;
	}
	
	//打印队头
	public void show_Head() {
		if(is_Empty()) {
			System.out.println("队列为空");
		}
		System.out.printf("队头arr[%d]=%d\n",front,arr[front]);
	}
	
	//打印队列(置打印有效数据)
	public void show_Queue() {
		//先判断空
		if(is_Empty()) {
			System.out.println("队列为空");
		}
		//这里对有效数据的循环,起始点就是front,因为front就指向队头数据
		//因为有效数据是(rear-front+MaxSize)%MaxSize个,所以循环到front+(rear-front+MaxSize)%MaxSize为终点
		else{
			for(int i=front; i<front+(rear-front+MaxSize)%MaxSize; i++) {
				//这里为什么是arr[i%MaxSize]
				//这里的i就是front到rear之间的数字,那么这之间的数在前面已经分析过了是有可能超出MaxSize的
				//所以这里用模除%来达到数组位置复用的目的
				System.out.printf("arr[%d]=%d\n", i%MaxSize, arr[i%MaxSize]);
			}
		}
		
	}
	
}

写在最后

很多人会说,你这个环形数组如果设置数组长度为1,那不就没用了吗,那环形队列,环形队列,一个数据怎么成环???不要杠了哈孩子们

下一篇:: 从1开始学Java数据结构与算法——单链表与双链表

你可能感兴趣的:(Java数据结构与算法)