数据结构(二)-队列(对数组的重复利用)

数组模拟队列

队列的一个应用场景

银行叫号排队的案例

队列介绍

  1. 队列是一个有序列表,可以用数组链表来实现。
  2. 遵循先入先出的原则。例如:银行先叫号的人先于后叫号的人办理业务。谁先叫号谁先办理,谁后叫号谁后办理。即:先进入队列的数据先取出,后进入队列的数据后取出
  3. 示意图(使用数组模拟队列示意图)

数组模拟队列思路

  1. 队列本身是有序列表,若使用数组的结构来存储队列的数据,则队列数组的声明如上图,其中, maxSize 为队列最大容量。

  2. 队列的输出、输入分别是从队列的头部和尾部来处理,因此需要两个变量 front 和 rear 来记录队列前后端的位置。front 随着数据的取出而改变,rear 随着数据的输入而改变。

    front:指向队列的头部数据的前一个位置
    rear:指向队列的尾部数据

  3. 当我们将数据存入队列时称为"addQueue",addQueue 有两个步骤
    ① 将队列的尾部指针往后移一位:rear + 1;
    ② 当 rear == maxSize - 1;则队列已满,无法添加数据到队列中。反之,arr[++rear] = value注意,添加一个数据只需要队尾后移一位即可

  4. 当我们将数据从队列中取出时称为"getQueue",getQueue 有两个步骤
    ① 将队列的头部指针后移一位:front + 1;
    ② 当 front == rear;则队列为空,没有数据可取,否则,返回arr[++front]

代码实现

public class ArrayQueueTest {
	public static void main(String[] args) {
		ArrayQueue queue = new ArrayQueue(3);
		Scanner scanner = new Scanner(System.in);
		boolean loop = true;
		char selection = ' ';
		while(loop) {
			System.out.println("s(show)打印队列");
			System.out.println("a(add)添加数据");
			System.out.println("g(get)获取数据");
			System.out.println("p(peek)查看队列的头部");
			System.out.println("q(exit)退出程序");
			System.out.print("请输入你的选择:");
			selection = scanner.next().trim().charAt(0);
			switch (selection) {
			case 's':
				queue.showQueue();
				break;
			case 'a':
				System.out.print("请输入要添加的数据:");
				int value = scanner.nextInt();
				queue.addQueue(value);
				break;
			case 'g':
				try {
					int value1 = queue.getQueue();
					System.out.printf("获取到的数据为:%d", value1);
					System.out.println();
				} catch (Exception e) {
					System.out.println(e.getMessage());
				} 
				break;
			case 'p':
				try {
					queue.peek();
				} catch (Exception e) {
					System.out.println(e.getMessage());
				}
				break;
			case 'q':
				scanner.close();
				loop = false;
				break;
			default:
				break;
			}
		}
	}
	
}
class ArrayQueue{
	
	private int maxSize;
	private int front;
	private int rear;
	private int[] arr;
	
	public ArrayQueue(int maxSize) {
		this.maxSize = maxSize;
		front = -1;
		rear = -1;
		arr = new int[maxSize];
	}
	
	public boolean isFull() {
		return rear == maxSize - 1;
	}
	
	public boolean isEmpty() {
		return front == rear;
	}
	
	public void addQueue(int value) {
		if(isFull()) {
			throw new RuntimeException("队列已满,无法添加");
		}
		arr[++rear] = value;
		System.out.println("添加成功");
	}
	
	public int getQueue() {
		if(isEmpty()) {
			throw new RuntimeException("队列为空~~");
		}
		return arr[++front];
	}
	
	public void showQueue() {
		if(isEmpty()) {
			System.out.println("队列为空~~");
		}
		for(int i = 0; i < arr.length; i++) {
			System.out.print(arr[i] + "\t");
		}
		System.out.println();
	}
	
	public void peek() {
		if(isEmpty()) {
			throw new RuntimeException("队列为空~~");
		}
		System.out.println(arr[front + 1]);
	}
}

数组模拟环形队列

针对上述数组模拟队列,在测试的时候发现,当队列数据已满时,无法添加数据,但是当取出一个数据时,还是提示队列已满。

问题分析并优化

目前数组模拟的队列的只能使用一次,没有达到复用的效果。
将这个数组使用一个算法,改进成 "环形队列":取模 %

思路分析1

  > 注意
        front:在数组模拟环形队列中指向数组头部的数据,初始值为0
        rear:在数组模拟环形队列中执行尾部数据的后一位(跟数据模拟队列有区别),因为希望空出一个空间作为约定,初始值为0
  1. 数组模拟队列之所以不能复用的原因是因为,一旦 rear == maxSize - 1; 就判断为队列已满,但是在数据添加满之后,这个 rear 在数组模拟队列中就不会再发生改变,没有形成一个环形结构。
  2. 思路就是如何让这个 rear 在满了之后还能再回到起点重新来过(操场跑圈),就需要在rear == maxSize - 1之后,手动将 rear 的值值为 0;front 随着队列中数据的取出也会逐渐趋向于 maxSize - 1,所以也需要手动将 front 的值置为0,判断队列已满的条件就是 rear 所处的位置的下一个位置就是 front 所处的位置:即 rear + 1 = front;
  3. 手动将达到最大值置为0的思路理解起来较容易,实现起来较繁琐。

思路分析2(取模)

  1. 首先判断已满的条件为(rear + 1) % maxSize == front;maxSize 为数组的长度,而实际队列存储数据的个数为 maxSize - 1 个

    这样取模的原因,如果向后面这样取,rear % maxSize == front;在还没向队列添加数据时,则等式成立,无法添加数据

  2. 判断为空的条件:rear == front;
  3. 当我们如上述分析时,这时队列中的有效数据个数为 (rear + maxSize - front) % maxSize;

    +maxSize 的原因,是因为,如果出现 rear 比 front 快一圈的情况(跑圈),rear - front 就会出现负数,与需求相悖。

代码实现

public class CircleArrayQueueTest {
	public static void main(String[] args) {
		CircleArrayQueue queue = new CircleArrayQueue(4);
		Scanner scanner = new Scanner(System.in);
		boolean loop = true;
		char selection = ' ';
		while (loop) {
			System.out.println("s(show)打印队列");
			System.out.println("a(add)添加数据");
			System.out.println("g(get)获取数据");
			System.out.println("p(peek)查看队列的头部");
			System.out.println("q(exit)退出程序");
			System.out.print("请输入你的选择:");
			selection = scanner.next().trim().charAt(0);
			
			switch (selection) {
			case 's':
				queue.showQueue();
				int front = queue.getFront();
				int rear = queue.getRear();
				System.out.printf("front=%d\n", front); // 在打印队列的时候可以看出front和rear的变化
				System.out.printf("rear=%d\n", rear); // 从而可以理解size()函数的写法
				break;
			case 'a':
				System.out.print("请输入要添加的数据:");
				int value = scanner.nextInt();
				queue.addQueue(value);
				break;
			case 'g':
				try {
					int value1 = queue.getQueue();
					System.out.printf("获取到的数据为:%d", value1);
					System.out.println();
				} catch (Exception e) {
					System.out.println(e.getMessage());
				}
				break;
			case 'p':
				try {
					queue.peek();
				} catch (Exception e) {
					System.out.println(e.getMessage());
				}
				break;
			case 'q':
				scanner.close();
				loop = false;
				break;
			default:
				break;
			}
		}
	}

}

class CircleArrayQueue {

	private int maxSize;
	private int front; // 指向队列头部的数据
	private int rear; // 指向队列尾部的后一个位置,预留一个空位,以方便下面的取余操作
	private int[] arr;
	

	public CircleArrayQueue(int maxSize) {// maxSize为数组长度,不是队列的长度
		this.maxSize = maxSize;
		arr = new int[maxSize];
	}

	public boolean isFull() {
		// 不写(rear+1)的话就会出现rear=0;front=0;还没添加数据就判断为队列为满
		return (rear + 1) % maxSize == front; // 队列实际存储的数据个数最多为maxSize-1个
	}

	public boolean isEmpty() {
		return front == rear;
	}

	public void addQueue(int value) {
		if (isFull()) {
			System.out.println("队列已满,无法添加");
			return;
		}
		arr[rear] = value;
		rear = (rear + 1) % maxSize; // 防止索引越界
	}

	public int getQueue() {
		if (isEmpty()) {
			throw new RuntimeException("队列为空~~");
		}
		int value = arr[front];
		front = (front + 1) % maxSize; // 防止索引越界
		return value;
	}

	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 size() {
		// 写rear + maxSize是为了防止出现rear在第二圈,front在第一圈的情况
		return (rear + maxSize - front) % maxSize;
	}

	public void peek() {
		if (isEmpty()) {
			throw new RuntimeException("队列为空~~");
		}
		System.out.println(arr[front]);
	}

	public int getFront() {
		return front;
	}

	public int getRear() {
		return rear;
	}

}

本篇随笔内容是来自于学习尚硅谷韩顺平老师的数据结构和算法课(java版)的笔记,如有整理疏漏或错误之处,请大家多多指出

posted @ 2020-09-14 10:32  wsilj  阅读(352)  评论(0编辑  收藏  举报