java数据结构——概念,以及用数组模拟实现队列(包括环形队列)

一、 队列的概念

首先要知道队列的一个特性:有序性。

我们在接触数据结构的时候,会很轻松发现不同的数据结构之间的不同点之一:存储和使用数据的方式不同。
比如上一篇介绍的稀疏数组,其本质是一个数组,存储时指定索引然后赋值,获取时指定索引取值。
而这次介绍的队列不同,队列的储存取值特点是:先入先出

即先存入的值会被先取出,后存入的值只能在先存入的值取出之后取出

概念比较简单,那么接下来让我们用数组模拟实现一下队列。

二、 用数组模拟队列

1. 简单模拟

模拟示意图

在这里插入图片描述

设置几个变量
maxSize :队列的最大容量
front :指向第一个数据的前一个地址,如果队列为空则指向 -1(因为第一个元素没有)
rear :指向最后一个数据(队列尾)
思路分析

设置了变量之后,考虑几个问题:

1 . 什么时候队列为空?

答:front == rear 的时候

2 . 什么时候队列满

答:当 rear == maxSize - 1 的时候,因为数组是用0开头的,而maxSize是从1开始算的,所以要 -1
代码实现

1 . 首先用数组实现一个ArrayQueue类,模拟实现队列。
因为没什么难点,所以主要的过程以注释的形式解释。

// 用数组模拟一个队列
class ArrayQueue {
    private int maxSize;// 最大的存储长度
    private int front;// 头部指针
    private int rear;// 尾部指针
    private int[] queue;// 模拟队列的数组

    // 构造函数,初始化最大存储量、头尾指针及queue
    public ArrayQueue(int maxSize) {
        this.maxSize = maxSize;
        this.front = -1;
        this.rear = -1;
        this.queue = new int[maxSize];
    }

    // 判断队列是否为空
    private boolean isEmpty(){
        if (this.front == this.rear){
            return true;
        }
        return false;
    }

    // 判断队列是否为满
    private boolean isFull(){
        if (this.rear == this.maxSize-1){
            return true;
        }
        return false;
    }

    // 向队列中添加数据
    public void add(int data){
        // 先判断是不是满的
        if(this.isFull()){
            return;
        }
        // 如果不是满的,继续逻辑
        rear++;
        // 先++是因为,rear指向的是最后一个元素,如果rear后移则指向后一个空闲的位置
        queue[rear] = data;
    }

    // 从队列中获取数据
    public int get(){
        // 先判断是不是空的
        if (this.isEmpty()){
            throw new RuntimeException("队列为空,无法取出数据");
        }
        // 如果不是满的,要遵守先入先出的规则
        // 因为输入存放是从数组头部开始存放的
        // 所以取的时候要利用front取值
        front++;
        return queue[front];
    }

    // 显示队列所有数据
    public void showAll(){
        if (this.isEmpty()){
            throw new RuntimeException("队列为空,无法展示数据");
        }
        for (int data:queue){
            if (data == 0){
                break;
            }
            System.out.print(data + "  ");
        }
        System.out.println();
    }

    // 显示队列头部数据(第一个数据)
    public void showHead(){
        if (this.isEmpty()) {
            throw new RuntimeException("队列为空,无头部数据!");
        }
        System.out.println(queue[front + 1]);// front指向的是第一个数据的前一个位置
    }

    // 测试用
    public void show(){
        System.out.println(this.maxSize);
        System.out.println(this.front);
        System.out.println(this.rear);
        System.out.println(this.queue);
    }
}

1. 环形队列模拟

上面用数组简单模拟了一个队列,但是相信仔细的朋友,或者重复测试了一些次数后的朋友,肯定会发现,上面这个队列有个很严重的问题:它不能重复用

这就不好了,难道每次用完还得再 new 一个吗?太麻烦了,所以思考一下解决方案。

想要解决一个问题,首先肯定得弄明白是什么导致的这个问题。

当我们用上面的例子,把队列填满,再把队列清空,发现不能继续填入数据了,很容易会发现,指针没有回退

没有回退,第一个思路是,当满了之后,让指针重置回退,但这样会影响队列最大长度maxSize的波动,而且很不好弄,所以,需要另外提出一个方案:

将队列做成环形

那么难点是,怎么把一个线性的数组模拟成环形的???

关于将线性数组拟合成环形,不止一种算法,下面是我使用的一种。

思路分析

首先,需要重新定义一些东西:

  1.   front不再指向第一个元素的前一个位置了,而令front直接指向第一个元素。 也就是说,queue[front] 就是队列中的一个元素。
    
  2.   rear 不再指向最后一个数据,而是指向最后一个元素的后一个位置,这样就会在队列的最后空出一个位置,空出的最后一个位置,之后用到会说。rear的初值为0
    
  3. 那么,这样,队列满的时候,是什么条件??

     答:当队列满时,条件是:( rear + 1 ) % maxSize = front 
    

    这个条件判别式是怎么得出来的?

    先介绍一下取模运算:取模运算实际上相当于我们小学的时候学过的求余数。比如 5 % 3 = 2 , 3 % 5 = 3

    • 举个例子:
      如果队列的最大长度为5,那么索引为:0 1 2 3 4,空出一个,则实际上能够存储数据的索引为:0 1 2 3,
      当 front 为0,rear为4(0123装满数据,rear根据定义需要指向最后一个数据的后一个位置,即为4)
      rear+1 为 5 ,5模上5为0,等于front,此时队列为满。

    • 在环形的情况下,举个例子:
      如果队列的最大容量为11,那么索引为从0到10,假如 front 为6,队列满,由于队尾空了一个位置,所以5被空出来了,4是队尾,即最后一个数组,而我们定义 rear 是队尾的后一个位置,所以 rear 为5。(5+1)% 11 = 6 = front。所以可以看出,判别式成立。

    • 在其中有几个小问题:

      • 为什么要取模?
        因为队列为环形,按照线性的思维,rear 总是在 front 的后面,但是环形情况下,rear 有可能比 front 更小 ,取模可以兼顾两种情况。
      • 为什么定义的时候,要空出队尾的一个位置?
        因为,如果不空出来,不管队列为空或者为满,都满足 rear == front ,这样就会出现判断的重合,导致冲突。(可以自己试试调整rear的定义然后不空出最后那一个空,最后都会导致队列空和队列满的条件冲突)
      • 取模还有什么别的好处?
        不用重置rear和front的值。如果,队列满的条件使用:rear + front = maxSize ,那么就需要,在每次rear到达临界值的时候重新将rear的值从0开始排起。而取模就不一样了,取模可以不用每次超出范围之后从新确定索引。
  4. 队列为空的时候,是什么条件?

     rear == front  这个时候为什么不取模?为什么不考虑rear比front大一整个maxSize?不太好说,想知道的话自己想想。
    
  5. 队列中的有效数据个数是多少?

     ( rear + maxSize - front )% maxSize 计算结果即为有效数据的个数
    
    • 为什么要 (rear - front)加上maxSize之后模上maxSize?
      因为如果rear比front小,减出来是负数,加上maxSize可以手动将rear提比front大。
代码实现
class Queue {
    private int front;
    private int rear;
    private int maxSize;
    private int[] queue;

//    各个判断方法的结论条件在分析里面都有

    public Queue(int maxSize) {
//        初始化
        front = 0;
        rear = 0;
        this.maxSize = maxSize;
        queue = new int[maxSize];
    }

    // 查看队列是否满了
    public boolean isFull() {
        return (rear + 1) % maxSize == front;
    }

    // 查看队列是否为空
    public boolean isEmpty() {
        return rear == front;
    }

    // 添加数据到队列
    public void add(int data) {
        if (isFull()) {
            System.out.println("抱歉,队列已满,不可添加数据!");
            return;
        }
        queue[rear] = data;
        // 为什么要这么做?
        // 当front不是0,而rear要超过maxSize的时候,+1,就会超出范围
        // 如果按照环形的思路,当rear跨过尾端的时候,就应该从首端继续接上
        // 此时应该重置rear的值,但是有个更科学的算法:取模,相当于减去一个或多个maxSize。
        rear = (rear + 1) % maxSize;
    }

    // 获取数据,即出队列
    public int get() {
        if (isEmpty()) {
            throw new RuntimeException("队列存储为空!无数据可取!");
        }
        int ValueForReturn = queue[front];
        // 为什么取模?和上面一样,都是考虑到数值超出了maxSize后需要重置
        front = (front + 1) % maxSize;
        return ValueForReturn;
    }

    // 显示队列的所有元素
    public void showAll() {
        if (isEmpty()) {
            throw new RuntimeException("队列为空!");
        }
        for (int i = front; i < front + (rear + maxSize - front) % maxSize; i++) {
            System.out.print(queue[i] + "  ");
        }
    }

    // 显示队列的头数据
    public void showHead() {
        if (isEmpty()) {
            throw new RuntimeException("队列为空!");
        }
        System.out.print(queue[front]);
    }
}

三、封装类的测试

上面分别实现了线性的队列实现和环形队列的实现,都是用一个类进行的封装,为了方便大家测试,给出主函数的测试程序。

以测试环形队列为例:

public class CircleArrayQueue {
    public static void main(String[] args) {
        System.out.println("利用数组模拟一个环形数组");

        Scanner scanner = new Scanner(System.in);

        System.out.println("请设置队列的长度!");
        int size = scanner.nextInt();
        Queue queue = new Queue(size);
        
        while (true) {
            System.out.println("Type a to add data into the queue");
            System.out.println("Type g to get data from the queue");
            System.out.println("Type s to show all of the data in the queue");
            System.out.println("Type h to show head data of the queue");
            System.out.println("请输入指令操作队列");

            char key = scanner.next().charAt(0);
            switch (key) {
                case 'a': {
                    System.out.println("请输入需要添加到队列的数据");
                    int input = scanner.nextInt();
                    queue.add(input);
                    break;
                }
                case 'g': {
                    System.out.println("获取到数据: " + queue.get());
                    break;
                }
                case 's': {
                    System.out.println("队列中的所有数据: ");
                    queue.showAll();
                    break;
                }
                case 'h': {
                    System.out.print("队列中的头数据: ");
                    queue.showHead();
                    System.out.println();
                    break;
                }
            }
        }
    }
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

沧州刺史

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值