队列与环形队列使用数组模拟

队列

该文是观看尚硅谷韩老师视频学习自己总结学习得,有的是来源于网络收集

队列引入

队列与环形队列使用数组模拟_第1张图片
进的一端称为队尾(rear),出的一端称为队头(front)。队列可以用顺序存储,也可以用链式存储。

队列介绍

  1. 队列是一个**有序列表**,可以用数组或是链表来实现。
  2. 遵循先入先出的原则。即:先存入队列的数据,要先取出。后存入的要后取出
  3. 示意图:(使用数组模拟队列示意图)

队列与环形队列使用数组模拟_第2张图片

数组模拟队列

队列本身是有序列表,若使用数组的结构来存储队列的数据,则队列数组的 如上图,其中 maxSize 是该队列的最大容量。
队列与环形队列使用数组模拟_第3张图片

思路分析
添加数据的时候将尾指针往后移:rear+1 , 当 取出的数据front+1,若front == rear 【所明队列数据为空,并非数组数据空,】
若尾指针 rear 小于队列的最大下标 maxSize-1,则将数据存入 rear 所指的数组元素中,否则无法存入数据。
rear = = maxSize - 1[队列满]

代码模拟数组实现队列

package com.fs.demo_2020_07_11_queueArray;

import java.util.Scanner;

/**
 * 队列
 * 队列是一个有序列表,可以用数组或者链表来实现
 * 遵循先入先出的原则,先存入的数据先取出来,后存入的后取出来
 *
 * 数组模拟队列
 */
public class QueueArray {
    public static void main(String[] args) {
        //测试一把
        //创建一个队列
        SimulationQueueArray queue = new SimulationQueueArray(3);
        char key = ' '; //接收用户输入
        Scanner scanner = new Scanner(System.in);//
        boolean loop = true;
        //输出一个菜单
        while (loop) {
            System.out.println("s(show): 显示队列");
            System.out.println("e(exit): 退出程序");
            System.out.println("a(add): 添加数据到队列");
            System.out.println("g(get): 从队列取出数据");
            System.out.println("h(head): 查看队列头的数据");
            key = scanner.next().charAt(0);//接收一个字符
            switch (key) {
                case 's':
                    queue.showQueue();
                    break;
                case 'a':
                    System.out.println("输出一个数");
                    int value = scanner.nextInt();
                    queue.addQueue(value);
                    break;
                case 'g': //取出数据
                    try {
                        int res = queue.getQueue();
                        System.out.printf("取出的数据是%d\n", res);
                    } catch (Exception e) {
                        System.out.println(e.getMessage());
                    }
                    break;
                case 'h': //查看队列头的数据
                    try {
                        int res = queue.headQueue();
                        System.out.printf("队列头的数据是%d\n", res);
                    } catch (Exception e) {
                    //   System.out.println(e.getMessage());
                        e.printStackTrace();
                    }
                    break;
                case 'e': //退出
                    scanner.close();
                    loop = false;
                    break;
                default:
                    break;
            }
        }
        System.out.println("程序退出~~");
    }
}

//创建一个类来模拟队列
class SimulationQueueArray {
    private int maxSize;//表示数字的最大容纳量
    private int front;//模拟指针,默认指向数组有效数据索引的前一个位置
    private int rear;//模拟指针指向尾部数据,默认初始化指向和front一个位置,因为没有数据
    private int[] arr;

    /**
     * 创建队列数组的构造器
     *
     * @param arrMaxSize 初始化这个队列数组的容量
     */
    public SimulationQueueArray(int arrMaxSize) {
        maxSize = arrMaxSize;
        arr = new int[maxSize];//初始化数组的容量
        front = -1;
        rear = -1;
    }

    //判断队列数组是否满了
    public boolean isFull() {
        //假设数组最大索引为2,那么容量就为3,当添加3个数据后,rear就加了3次,就是-1+3=2
        //所以-1+3 = 2 = 3 -1 = 2,所以就满了
        return rear == maxSize - 1;
    }

    //判断队列是否为空
    public boolean isEmpty() {
        //为空有两中情况,
        //1.就是数组中的元素取完了,我还是假设容量为3,要么一条数据都没有添加,front和rear都为-1,表示容量为空
        //2.数组中的元素添加3个,那么front为2,然后我们又取了3个元素,那么front也为2,那么rear=front,说明数组中为空
        return rear == front;
    }

    //添加数据到队列
    public void addQueue(int data) {
        //添加前先判断队列是否满了
        if (isFull()) {
            System.out.println("队列满,不能加入数据~~~");
            return;
        }
        rear++;//让指针向后移一下
        //在rear索引下添加这个数据,假设第一次添加,就是0索引添加data数据
        arr[rear] = data;
    }

    //获取队列的数据,出队列
    public int getQueue() {
        //先判断队列是否为空
        if (isEmpty()) {
            //为空就抛出异常,我们这里结束方法使用抛出异常方式
            throw new RuntimeException("队列空,不能获取数据~~~");
        }
        front++;//先将指针后移,
        //这里的取出并不是吧数组中的数据真正的取出,而且将指针后移,取出指针后移的这位数据
        //提现了先进先出的队列思想
        return arr[front];
    }

    //显示队列的所有数据
    public void showQueue() {
        //同样先判断队列是否为空
        if (isEmpty()) {
            System.out.println("队列空,没有这个数据~~~");
            return;
        }
        //遍历
        for (int i = 0; i < arr.length; i++) {
            System.out.println("arr[" + i + "]=" + arr[i]);
        }
    }

    //显示队列的头数据,不是取数剧
    public int headQueue() {
        if (isEmpty()) {
            //为空就抛出异常,我们这里结束方法使用抛出异常方式
            throw new RuntimeException("队列空,没有数据~~~");
        }
        //显示是front指针当前指的哪位数据,因为假如还没有从列队中取出数据,那么当前就指向索引为0的位子,故为front+1 为0
        //若已经取了索引为0的数据,那么当前指的就是索引为1的位子,故front+1 为1
        //若数据已经取完,那么就判断直接抛出异常不会到这一步,假设到了这一步,也会索引越界异常
        return arr[front + 1];
    }

}

若看不懂,将代码拷贝到idea中DEBUG看数据变化自然会懂

上面是线性的队列,不能循环复用其中的排队逻辑,当队列满后,第一个走后,后来的不能加入子第一个位置,所以没有达到复用效果
使用一些取模的方式达到让线性的队列转换成环形队列,从而实现先进先出,后进后出,循环环形队列

数组模拟环形队列

上述到达尾部又向前存储的队列称为循环队列,为了避免"假溢出",我们通常采用循环队列。

分析

一 尾索引的下一个为头索引时表示队列满,即将队列容量空出一个作为约定,这个在做判断队列满的时候需要注意 (rear + 1) % maxSize == front 满]

二 rear == front [空]
队列与环形队列使用数组模拟_第4张图片

代码实现环形队列

package com.fs.demo_2020_07_11_queueArray;

import java.util.Scanner;

/**
 * 模拟环形队列数组
 *
 * 1.头尾部指针取模重新计算指针值:(rear/front + 1) % maxSize
 * 2.队列空的判断逻辑:rear == front
 * 3.队列满的判断逻辑:(rear + 1) % maxSize == front
 * 4.队列内有效数据个数:(rear - front + maxSize) % maxSize
 *
 */
public class CircleQueueArray {
    public static void main(String[] args) {
        //测试一把
        //创建一个队列
        SimulationCircleQueueArray queue = new SimulationCircleQueueArray(4);

        char key = ' '; //接收用户输入
        Scanner scanner = new Scanner(System.in);//
        boolean loop = true;
        //输出一个菜单
        while (loop) {
            /**
             * 遍历打印下每次执完后数组中的数据
             */
            int[] arr = queue.getArr();
            System.out.println("-------------------------------------------------");
            for (int i = 0; i < arr.length; i++) {
                int i1 = arr[i];
                System.out.println("arr数组索引:"+i+"位子当前的数据为:"+i1+".");
            }
            System.out.println("-------------------------------------------------");
            System.out.println("s(show): 显示队列");
            System.out.println("e(exit): 退出程序");
            System.out.println("a(add): 添加数据到队列");
            System.out.println("g(get): 从队列取出数据");
            System.out.println("h(head): 查看队列头的数据");
            key = scanner.next().charAt(0);//接收一个字符
            switch (key) {
                case 's':
                    queue.showQueue();
                    break;
                case 'a':
                    System.out.println("输出一个数");
                    int value = scanner.nextInt();
                    queue.addQueue(value);
                    break;
                case 'g': //取出数据
                    try {
                        int res = queue.getQueue();
                        System.out.printf("取出的数据是%d\n", res);
                    } catch (Exception e) {
                        System.out.println(e.getMessage());
                    }
                    break;
                case 'h': //查看队列头的数据
                    try {
                        int res = queue.headQueue();
                        System.out.printf("队列头的数据是%d\n", res);
                    } catch (Exception e) {
                        //   System.out.println(e.getMessage());
                        e.printStackTrace();
                    }
                    break;
                case 'e': //退出
                    scanner.close();
                    loop = false;
                    break;
                default:
                    break;
            }
        }
        System.out.println("程序退出~~");
    }
}

//创建一个类来模拟环形队列数组
class SimulationCircleQueueArray{
    private int maxSize;//表示数字的最大容纳量
    private int front;//模拟指针指向头部数据,对于普通的队列做了调整,现在默认指向第一个索引位子,也就是0
    private int rear;//模拟指针指向尾部数据,同样也是默认指向第一个索引的位子,也就是0
    private int[] arr;//数组用于存放数据,模拟队列

    //提供一个get方法观察数组中数据的变化
    public int[] getArr() {
        return arr;
    }

    /**
     * 创建环形队列数组的构造器
     *
     * @param arrMaxSize 初始化这个环形队列数组的容量
     */
    public SimulationCircleQueueArray(int arrMaxSize) {
        maxSize = arrMaxSize;
        arr = new int[maxSize];//初始化数组的容量
        front = 0;
        rear = 0;
    }

    //判断队列数组是否满了
    public boolean isFull() {
        /*
        判断队列是否满
        环形队列,判断队列满,不仅仅在通过rear = maxSize -1 来判断
        因为是环形,尾部指针会回到之前的位子,所以通过取模来重新计算指针的值(rear+1)%maxSize == front;

        假设队列内部数组arr的最大容量为4
        队列添加数据,rear指针需取模重新计算指针指向值:rear = (rear + 1) % maxSize,并通过(rear + 1) % maxSize == front来判断队列是否已满
            1.队列添加数据10,arr[0]=10,rear重新指向1,(1+1)%4!=0,队列未满
            2.队列添加数据20,arr[1]=20,rear重新指向2,(2+1)%4!=0,队列未满
            3.队列添加数据30,arr[2]=30,rear重新指向3,(3+1)%4==0,队列满,不能再添加数据
            此时,队列内所有数据为:arr[0]=10,arr[1]=20,arr[2]=30,队列有效数据个数:(3-0+4)%4=3

         */
        return (rear+1)%maxSize == front;
    }

    //判断队列是否为空
    public boolean isEmpty() {
        /*
            队列当取得指针和添加的指针一致的时候,所明去完了,没有数据
         */
        return rear == front;

    }

    //添加数据到队列
    public void addQueue(int data) {
        /*
        队列的添加:
            一般线性队列,尾部指针直接递增
            而环形队列,尾部指针可能在次指向索引为0的位子,因此不能单一的通过递增来处理,需要取模重新计算尾部的指针位置
         */
        //添加前先判断队列是否满了
        if (isFull()) {
            System.out.println("队列满,不能加入数据~~~");
            return;
        }
        arr[rear] = data;//在尾部指针指向的位子添加传递的数据

        //添加值后,尾部指针的走向不能在++递增来实现,需要从新取模运算
        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;
        }
        // 遍历数组
        // 不能单一的对角标递增处理,会出现角标越界异常
        // 应对环形队列内部的有效数据,从头部数据开始遍历依次取出
        // 有效数据的下标:i % maxSize
        for (int i = front; i < front + size(); i++) {
            System.out.printf("arr[%d] = %d\n", i % maxSize, arr[i % maxSize]);
        }
    }

    /**
     * 获取队列有效数据的个数
     * @return 队列有效数据的个数
     */
    private int size() {
        return (rear - front + maxSize) % maxSize;
    }


    //显示队列的头数据,不是取数据
    public int headQueue() {
        /*
        获取列队头部数据
            就是获取头部指针指向的位子
         */
        if (isEmpty()) {
            //为空就抛出异常,我们这里结束方法使用抛出异常方式
            throw new RuntimeException("队列空,没有数据~~~");
        }
        return arr[front];
    }
}

代码运行控制台测试

D:\Java\JDK\java1.8
-------------------------------------------------
arr数组索引:0位子当前的数据为:0.
arr数组索引:1位子当前的数据为:0.
arr数组索引:2位子当前的数据为:0.
arr数组索引:3位子当前的数据为:0.
-------------------------------------------------
s(show): 显示队列
e(exit): 退出程序
a(add): 添加数据到队列
g(get): 从队列取出数据
h(head): 查看队列头的数据
a
输出一个数
10
-------------------------------------------------
arr数组索引:0位子当前的数据为:10.
arr数组索引:1位子当前的数据为:0.
arr数组索引:2位子当前的数据为:0.
arr数组索引:3位子当前的数据为:0.
-------------------------------------------------
s(show): 显示队列
e(exit): 退出程序
a(add): 添加数据到队列
g(get): 从队列取出数据
h(head): 查看队列头的数据
a
输出一个数
20
-------------------------------------------------
arr数组索引:0位子当前的数据为:10.
arr数组索引:1位子当前的数据为:20.
arr数组索引:2位子当前的数据为:0.
arr数组索引:3位子当前的数据为:0.
-------------------------------------------------
s(show): 显示队列
e(exit): 退出程序
a(add): 添加数据到队列
g(get): 从队列取出数据
h(head): 查看队列头的数据
a
输出一个数
30
-------------------------------------------------
arr数组索引:0位子当前的数据为:10.
arr数组索引:1位子当前的数据为:20.
arr数组索引:2位子当前的数据为:30.
arr数组索引:3位子当前的数据为:0.
-------------------------------------------------
s(show): 显示队列
e(exit): 退出程序
a(add): 添加数据到队列
g(get): 从队列取出数据
h(head): 查看队列头的数据
s
arr[0] = 10
arr[1] = 20
arr[2] = 30
-------------------------------------------------
arr数组索引:0位子当前的数据为:10.
arr数组索引:1位子当前的数据为:20.
arr数组索引:2位子当前的数据为:30.
arr数组索引:3位子当前的数据为:0.
-------------------------------------------------
s(show): 显示队列
e(exit): 退出程序
a(add): 添加数据到队列
g(get): 从队列取出数据
h(head): 查看队列头的数据
g
取出的数据是10
-------------------------------------------------
arr数组索引:0位子当前的数据为:10.
arr数组索引:1位子当前的数据为:20.
arr数组索引:2位子当前的数据为:30.
arr数组索引:3位子当前的数据为:0.
-------------------------------------------------
s(show): 显示队列
e(exit): 退出程序
a(add): 添加数据到队列
g(get): 从队列取出数据
h(head): 查看队列头的数据
s
arr[1] = 20
arr[2] = 30
-------------------------------------------------
arr数组索引:0位子当前的数据为:10.
arr数组索引:1位子当前的数据为:20.
arr数组索引:2位子当前的数据为:30.
arr数组索引:3位子当前的数据为:0.
-------------------------------------------------
s(show): 显示队列
e(exit): 退出程序
a(add): 添加数据到队列
g(get): 从队列取出数据
h(head): 查看队列头的数据
g
取出的数据是20
-------------------------------------------------
arr数组索引:0位子当前的数据为:10.
arr数组索引:1位子当前的数据为:20.
arr数组索引:2位子当前的数据为:30.
arr数组索引:3位子当前的数据为:0.
-------------------------------------------------
s(show): 显示队列
e(exit): 退出程序
a(add): 添加数据到队列
g(get): 从队列取出数据
h(head): 查看队列头的数据
s
arr[2] = 30
-------------------------------------------------
arr数组索引:0位子当前的数据为:10.
arr数组索引:1位子当前的数据为:20.
arr数组索引:2位子当前的数据为:30.
arr数组索引:3位子当前的数据为:0.
-------------------------------------------------
s(show): 显示队列
e(exit): 退出程序
a(add): 添加数据到队列
g(get): 从队列取出数据
h(head): 查看队列头的数据
h
队列头的数据是30
-------------------------------------------------
arr数组索引:0位子当前的数据为:10.
arr数组索引:1位子当前的数据为:20.
arr数组索引:2位子当前的数据为:30.
arr数组索引:3位子当前的数据为:0.
-------------------------------------------------
s(show): 显示队列
e(exit): 退出程序
a(add): 添加数据到队列
g(get): 从队列取出数据
h(head): 查看队列头的数据
g
取出的数据是30
-------------------------------------------------
arr数组索引:0位子当前的数据为:10.
arr数组索引:1位子当前的数据为:20.
arr数组索引:2位子当前的数据为:30.
arr数组索引:3位子当前的数据为:0.
-------------------------------------------------
s(show): 显示队列
e(exit): 退出程序
a(add): 添加数据到队列
g(get): 从队列取出数据
h(head): 查看队列头的数据
h
java.lang.RuntimeException: 队列空,没有数据~~~
	at com.fs.demo_2020_07_11_queueArray.SimulationCircleQueueArray.headQueue(CircleQueueArray.java:203)
	at com.fs.demo_2020_07_11_queueArray.CircleQueueArray.main(CircleQueueArray.java:60)
-------------------------------------------------
arr数组索引:0位子当前的数据为:10.
arr数组索引:1位子当前的数据为:20.
arr数组索引:2位子当前的数据为:30.
arr数组索引:3位子当前的数据为:0.
-------------------------------------------------
s(show): 显示队列
e(exit): 退出程序
a(add): 添加数据到队列
g(get): 从队列取出数据
h(head): 查看队列头的数据
s
队列为空,无法显示队列的全部数据
-------------------------------------------------
arr数组索引:0位子当前的数据为:10.
arr数组索引:1位子当前的数据为:20.
arr数组索引:2位子当前的数据为:30.
arr数组索引:3位子当前的数据为:0.
-------------------------------------------------
s(show): 显示队列
e(exit): 退出程序
a(add): 添加数据到队列
g(get): 从队列取出数据
h(head): 查看队列头的数据
a
输出一个数
40
-------------------------------------------------
arr数组索引:0位子当前的数据为:10.
arr数组索引:1位子当前的数据为:20.
arr数组索引:2位子当前的数据为:30.
arr数组索引:3位子当前的数据为:40.
-------------------------------------------------
s(show): 显示队列
e(exit): 退出程序
a(add): 添加数据到队列
g(get): 从队列取出数据
h(head): 查看队列头的数据
a
输出一个数
50
-------------------------------------------------
arr数组索引:0位子当前的数据为:50.
arr数组索引:1位子当前的数据为:20.
arr数组索引:2位子当前的数据为:30.
arr数组索引:3位子当前的数据为:40.
-------------------------------------------------
s(show): 显示队列
e(exit): 退出程序
a(add): 添加数据到队列
g(get): 从队列取出数据
h(head): 查看队列头的数据
a
输出一个数
60
-------------------------------------------------
arr数组索引:0位子当前的数据为:50.
arr数组索引:1位子当前的数据为:60.
arr数组索引:2位子当前的数据为:30.
arr数组索引:3位子当前的数据为:40.
-------------------------------------------------
s(show): 显示队列
e(exit): 退出程序
a(add): 添加数据到队列
g(get): 从队列取出数据
h(head): 查看队列头的数据

环形队列的解释

下面解释来源于网络收集
作者:rainchxy
链接:https://www.jianshu.com/p/9ba8a65464dd
来源:简书

为什么要%Maxsize呢?

主要是为了处理临界状态,即Q.rear向后移动一个位置Q.rear+1后,很有可能超出了数组的下标,这时它的下一个位置其实是0,如果将一维数组画成环形图,如图所示:
队列与环形队列使用数组模拟_第5张图片
上图中最大空间Maxsize,当Q.rear=Maxsize-1时,(Q.rear+1)%Maxsize=0,而且Q.front=0,正好满足队满的条件:(Q.rear+1) %Maxsize= Q.front,此时为队满。

因此无论是front还是rear向后移动一个位置时,都要加1与最大空间Maxsize取模运算,处理临界问题。

总结:

队空:Q.front=Q.rear; // Q.rear和Q.front指向同一个位置

队满: (Q.rear+1) %Maxsize=Q.front; // Q.rear向后移一位正好是Q.front

入队:

Q.base[Q.rear]=x; //将元素放入Q.rear所指空间,

Q.rear =( Q.rear+1) %Maxsize; // Q.rear向后移一位

出队:

e= Q.base[Q.front]; //用变量记录Q.front所指元素,

Q.front=(Q.front+1) %Maxsize // Q. front向后移一位

你可能感兴趣的:(数据结构与算法(学习记录),队列)