第二章 队列

1 基本概念

队列是一种先进先出(First in First Out)的线性表,简称FIFO。队列中,只允许在一端进行插入操作,而在另一端进行删除操作。允许插入的一端称为队尾,允许删除的一端称为队头。

如下图所示,假设队列q=(a1,a2,…,an),那么a1就是队头元素,而an是队尾元素。这样删除元素时,总是从a1开始,而插入时,总是在队列尾部插入。

1507784-20190716140330407-1176335956

2 数组模拟简单队列

 队列本身是有序列表,若使用数组来存储队列的数据,则队列数组的声明如下图所示,其中 maxSize是该队列的最大容量。因为队列的输出、输入是分别从前后端来处理,因此需要两个变量front及 rear分别记录队列前后端的下标,front 会随着数据出队列而改变,而rear则是随着数据入队列而改变,如下图所示:

image-20200824233209504

上图中,变量front与rear的初始值均为0,这表明front与rear相等时,队列为空。当rear==MaxSize成立时,队列为满。需要注意的是,front指向队列头部元素位置,而rear指向队列尾部后一个元素的位置。

简单队列的java实现

package com.victor.queue;
import java.util.Scanner;

//数组模拟简单队列
public class ArrayQueueDemo {

	public static void main(String[] args) {
		ArrayQueue aq = new ArrayQueue(3);
		char key = ' '; //接收用户输入
		Scanner scanner = new Scanner(System.in);
		boolean loop = true;
		//输出一个菜单栏
		while(loop){
			System.out.println("s(show): 打印队列");
			System.out.println("a(add): 入队列");
			System.out.println("g(get): 出队列");
			System.out.println("h(head): 打印队头");
			System.out.println("e(exit): 退出程序");
			key = scanner.next().charAt(0);
			switch (key) {
			case 's':
				aq.showQueue();
				break;
			case 'a': //入队列
				try {
					System.out.println("请输入一个整数");
					int value = scanner.nextInt();
					aq.addQueue(value);  //最好判断一下value是不是整数
				} catch (Exception e) {
					// TODO: handle exception
					System.out.println(e.getMessage());
				}
				break;
			case 'g': //出队列
				try {
					int res = aq.getQueue();
					System.out.printf("出队列的整数为%d\n", res);
				} catch (Exception e) {
					// TODO: handle exception
					System.out.println(e.getMessage()); //输出getQueue()方法中定义好的异常信息
				}
				break;
			case 'h': //打印队头
				try {
					int res = aq.headQueue();
					System.out.printf("队列头的整数为%d\n", res);
				} catch (Exception e) {
					// TODO: handle exception
					System.out.println(e.getMessage());
				}
				break;
			case 'e': //退出
				scanner.close();
				loop = false;
				break;
			default:
				break;
			}
		}
		System.out.println("程序退出");
	}
}


 //使用数组模拟队列,ArrayQueue类
class ArrayQueue{
	private int maxSize; //数组的最大容量
	private int front;   //队列头
	private int rear;   //队列尾,指向队列尾部后一个元素的位置
	private int[] arr;  //该数组用于存放数据,模拟队列
	
	//队列的构造方法
	public ArrayQueue(int arrMaxSize){
		maxSize = arrMaxSize;
		arr = new int[maxSize];
		front = 0;  //队列头部
		rear = 0;  //队列尾部,指向队列尾部后一个元素的位置,front=rear时队列为空
	}
	
	//判断队列是否为满队列
	public boolean isFull(){
		return rear == maxSize;
	}
	
	//判断队列是否为空队列
	public boolean isEmpty(){
		return rear == front;
	}
	
	//入队列
	public void addQueue(int n){
		if(isFull()){
			throw new RuntimeException("队列满,不能添加数据了");
		}
		arr[rear] = n;
		rear++; //rear后移
	}
	
	//出队列
	public int getQueue(){
		if(isEmpty()){
			//抛出异常
			throw new RuntimeException("队列空,不能出队列了");
		}
		int value = arr[front];
		front++; //front后移
		return value;

	}
	
	//打印队列
	public void showQueue(){
		if(isEmpty()){
			System.out.println("队列空");
			return;
		}
		// 打印队列中的元素,不是打印数组中的所有元素
		for (int i = front; i < rear; i++){
			System.out.printf("arr[%d]=%d\n",i, arr[i]); //格式化输出
		}
	}
		
	
	//打印队头,不是出队列
	public int headQueue(){
		if (isEmpty()){
			//抛出异常
			throw new RuntimeException("队列空,不能打印头");
		}
		return arr[front];  
	}
}

3 数组模拟循环队列

 如下图a所示,当front指向下标为2的数组位置,rear指向下标为4的数组位置时,若向队列尾部再添加一个元素\(a_{5}\),则rear指针会越界,如下图b所示。但此时队列前部仍然有空间可以存储,所以简单队列的弊端就是不能充分利用数组空间。

image-20200825173213969

图a

image-20200825172934389

图b

为了解决数组空间不够用的办法就是后面满了,就再从头开始,也就是头尾相接的循环。我们把队列的这种头尾相接的顺序存储结构称为循环队列。比如上图b中,我们把rear置为0即可解决问题。
 我们只需对前面的数组模拟简单队列进行优化,即可充分利用数组空间。 因此将数组看做是一个环形的,通过对front和rear指针取模实现指针循环。以下图为例进行说明:

image-20200824233209504

变化如下:

  • front变量:指向队列的第一个元素,初始值为0。移动情况为front = (front + 1)% maxSize

  • rear变量:指向队列的最后一个元素的后一个位置,因为希望空出一个空间做为约定,rear的初始值为0。移动情况为rear = (rear + 1)% maxSize

  • 当队列满时,条件是(rear +1)% maxSize == front成立。因为空出了一个位置,故需要rear+1;

  • 当队列为空时,rear == front成立;

  • 队列中有效数据的个数:这里有两种情况,分别讨论:

(1)当front < rear 时,如下图,队列中的有效数据个数 = rear - front,这里rear-front = 2。

image-20200825173213969

(2)当front > rear 时,如下图,队列中的有效数据个数=front右边的数据个数+rear左边的数据个数,又有:

  • front右边的数据个数 = maxSize - front,这里maxSize - front = 3,maxSzie为数组的最大容量;
  • rear左边的数据个数 = rear - 0 = rear,这里为0;

所以队列中的有效数据个数 = rear - front + maxSize

image-20200825204541913

综合两种情况可得:

队列中的有效数据个数 = (rear - front + maxSize) % maxSize

循环队列的java实现

package com.victor.queue;
import java.util.Scanner;

//数组模拟循环队列
public class CircleArrayQueueDemo {

	public static void main(String[] args) {
		CircleQueue cq = 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("a(add): 入队列");
			System.out.println("g(get): 出队列");
			System.out.println("h(head): 打印队头");
			System.out.println("e(exit): 退出程序");
			key = scanner.next().charAt(0);
			switch (key) {
			case 's':
				cq.showQueue();
				break;
			case 'a': //入队列
				try {
					System.out.println("请输入一个整数");
					int value = scanner.nextInt();
					cq.addQueue(value);  //最好判断一下value是不是整数
				} catch (Exception e) {
					// TODO: handle exception
					System.out.println(e.getMessage());
				}
				break;
			case 'g': //出队列
				try {
					int res = cq.getQueue();
					System.out.printf("出队列的整数为%d\n", res);
				} catch (Exception e) {
					// TODO: handle exception
					System.out.println(e.getMessage()); //输出getQueue()方法中定义好的异常信息
				}
				break;
			case 'h': //打印队头
				try {
					int res = cq.headQueue();
					System.out.printf("队列头的整数为%d\n", res);
				} catch (Exception e) {
					// TODO: handle exception
					System.out.println(e.getMessage());
				}
				break;
			case 'e': //退出
				scanner.close();
				loop = false;
				break;
			default:
				break;
			}
		}
		System.out.println("程序退出");
	}
}


 //使用数组模拟循环队列,CircleQueue类
class CircleQueue{
	private int maxSize; //数组的最大容量
	private int front;   //队列头
	private int rear;   //队列尾,指向队列尾部后一个元素的位置
	private int[] arr;  //该数组用于存放数据,模拟队列
	
	//队列的构造方法
	public CircleQueue(int arrMaxSize){
		maxSize = arrMaxSize;
		arr = new int[maxSize];
		front = 0;  //队列头部
		rear = 0;  //队列尾部,指向队列尾部后一个元素的位置,front=rear时队列为空
	}
	
	//判断队列是否为满队列,因为预留了一个空间,所以要rear+1
	public boolean isFull(){
		return front == (rear + 1) % maxSize;
	}
	
	//判断队列是否为空队列,跟简单队列的条件一样
	public boolean isEmpty(){
		return rear == front;
	}
	
	//入队列
	public void addQueue(int n){
		if(isFull()){
			throw new RuntimeException("队列满,不能添加数据了");
		}
		arr[rear] = n;
		rear = (rear + 1) % maxSize; //rear要循环
	}
	
	//出队列
	public int getQueue(){
		if(isEmpty()){
			//抛出异常
			throw new RuntimeException("队列空,不能出队列了");
		}
		int value = arr[front];
		front = (front + 1) % maxSize; //front要循环
		return value;

	}
	
	//打印队列
	public void showQueue(){
		if(isEmpty()){
			System.out.println("队列空");
			return;
		}
		// 打印队列中的有效元素,注意这里i的边界条件,size()方法见下面
		for (int i = front; i < front + size(); i++){
			int index = i % maxSize; //index为循环后的下标
			System.out.printf("arr[%d]=%d\n",index, arr[index]); //格式化输出
		}
	}
		
	
	//打印队头,不是出队列
	public int headQueue(){
		if (isEmpty()){
			//抛出异常
			throw new RuntimeException("队列空,不能打印头");
		}
		return arr[front];  
	}
	
	//求出当前队列的有效数据个数
	public int size(){
		return (rear - front + maxSize) % maxSize;
	}
}

reference

韩顺平数据结构

大话数据结构

posted @ 2020-08-25 22:31  老潇的摸鱼日记  阅读(149)  评论(0编辑  收藏  举报