zoukankan      html  css  js  c++  java
  • 数据结构(二)-队列(对数组的重复利用)

    数组模拟队列

    队列的一个应用场景

    银行叫号排队的案例

    队列介绍

    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] + "	");
    		}
    		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
    ", front); // 在打印队列的时候可以看出front和rear的变化
    				System.out.printf("rear=%d
    ", 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
    ", 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版)的笔记,如有整理疏漏或错误之处,请大家多多指出

  • 相关阅读:
    MySql数据库水平扩展过程
    (转)MySQL数据库水平切分的实现原理解析
    SVN安装使用
    servlet基础
    数据库读写分离的性能分析
    java的可变长参数
    java消息服务
    static/final成员与多态
    商业软件与开源软件
    托管堆
  • 原文地址:https://www.cnblogs.com/wsilj/p/13664803.html
Copyright © 2011-2022 走看看