数据结构与算法 ------ 打卡第二天 队列

1. 队列

1.1 生活实例

公路上有一条单行隧道, 所有通过隧道的车辆只允许从隧道入口驶入, 从
隧道出口驶出, 不允许逆行。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WQCAcp5O-1601193007450)(数据结构与算法img/队列1.png)]

因此, 要想让车辆驶出隧道, 只能按照它们驶入隧道的顺序, 先驶入的车辆先驶出, 后驶入的车辆后驶出, 任何车辆都无法跳过它前面的车辆提前驶出。

1.2 队列的介绍

队列是一个有序列表,可以用 数组 或是 链表 来实现。

  • 遵循 先入先出 的原则 (First In First Out,简称
    FIFO) :
    • 先存入队列的数据,要先取出
    • 后存入的要后取出
  • 队列的出口端叫作队头( front) , 队列的入口端叫作队尾( rear)

示意图:

◀ 队列的数组实现如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FDFobZU7-1601193007457)(数据结构与算法img/队列2.png)]

用数组实现时, 为了入队操作的方便, 把队尾位置规定为最后入队元素的下一个位置。

◀ 队列的链表实现如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WMYmkx7W-1601193007461)(数据结构与算法img/队列3.png)]

1.3 队列的基本操作

1.3.1 入队

入队( enqueue) 就是把新元素放入队列中, 只允许在队尾的位置放入元素,新元素的下一个位置将会成为新的队尾。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-K26vAb4I-1601193007465)(数据结构与算法img/队列4.png)]

1.3.2 出队

出队( dequeue) 就是把元素移出队列, 只允许在队头一侧移出元素, 出队元素的后一个元素将会成为新的队头。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dJAxkVHp-1601193007467)(数据结构与算法img/队列5.png)]

1.3.3 数组队列实现代码
import java.util.Scanner;

public class ArrayQueue {
    public static void main(String[] args) {
        // 创建一个队列
        ArrayQueueContainer queue = new ArrayQueueContainer(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());
                    }
                    break;
                case 'e':  // 退出
                    scanner.close();
                    loop=false;
                    break;
                default:
                    break;
            }
        }
        System.out.println("程序退出~~");
    }
}

// 使用数组模拟队列----编写一个 ArrayQueue类
class ArrayQueueContainer{
    private int maxSize;  // 表示数组的最大容量
    private int front;  //  队列头
    private int  rear;  //  队列尾
    private int[] arr;  //  该数据用于存放数据,模拟队列

    // 创建队列的构造器
    public ArrayQueueContainer(int arrMaxSize){
        maxSize=arrMaxSize;
        arr=new int[maxSize];
        front=-1;  // 指向队列头部,分析出 front 是指向队列头的前一个位置
        rear=-1;  // 指向队列尾,指向队列尾的数据(即就是队列最后一个数据)
    }

    //  判断队列是否满
    public  boolean isFull(){
        return rear==maxSize-1;
    }

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

    //  添加数据到队列
    public void addQueue(int n){
        // 判断队列是否满
        if(isFull()){
            System.out.println("队列满,不能加入数据~~");
            return;
        }
        rear++;  // 让 rear 后移
        arr[rear]=n;
    }

    // 获取队列的数据,出队列
    public  int getQueue(){
        //  判断队列是否空
        if(isEmpty()){
            //  通过抛出异常
            throw new RuntimeException("队列空,不能取数据");
        }
        front++;  // front 后移
        return arr[front];
    }

    // 显示队列的所有数据
    public  void showQueue(){
        // 遍历
        if(isEmpty()){
            System.out.println("队列空的,没有数据~~");
            return;
        }
        for (int i=0;i<arr.length;i++){
            System.out.printf("arr[%d]=%d\n",i,arr[i]);
        }
    }

    // 显示队列的头数据,注意不是取出数据
    public  int headQueue(){
        // 判断
        if(isEmpty()){
            throw new RuntimeException("队列空的,没有数据~~");
        }
        return  arr[front+1];
    }
}
★ 产生的问题

█ 问题:如果不断出队, 队头左边的空间失去作用, 那 队列的容量 就会越来越小了。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DgbG8iEc-1601193007470)(数据结构与算法img/队列6.png)]

█ 解决办法:用数组实现的队列可以采用循环队列的方式来维持队列容量的恒定

1.3.4 循环队列

假设一个队列经过反复的入队和出队操作, 还剩下2个元素, 在“ 物理 ” 上分布于数组的末尾位置。 这时又有一个新元素将要入队。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2uOgJL6X-1601193007471)(数据结构与算法img/循环队列1.png)]

在数组不做扩容的前提下, 如何让新元素入队并确定新的队尾位置呢? 我们可以利用已出队元素留下的空间, 让队尾指针重新指回数组的首位。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-38XziXFK-1601193007473)(数据结构与算法img/循环队列2.png)]

这样一来, 整个队列的元素就“循环” 起来了。 在物理存储上, 队尾的位置也可以在队头之前。 当再有元素入队时, 将其放入数组的首位, 队尾指针继续后移即可。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TXR8TS8n-1601193007474)(数据结构与算法img/循环队列3.png)]

一直到( 队尾下标+1) %数组长度 = 队头下标时, 代表此队列真的已经满了。
需要注意的是, 队尾指针指向的位置永远空出1位, 所以队列最大容量比数组长度小1

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ev7hsIPi-1601193007475)(数据结构与算法img/循环队列4.png)]

把循环队列当作周期,效果图如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qPUOgkZg-1601193007477)(C:\Users\12163\AppData\Roaming\Typora\typora-user-images\image-20200927135554583.png)]

  1. front 变量: 指向队列的第一个元素, 也就是说 arr[front] 就是队列的第一个元素,front 的初始值 = 0

  2. rear 变量:指向队列的最后一个空位置. 因为希望空出一个空间做为约定,rear 的初始值 = 0

  3. 当队列满时,条件是 (rear + 1) % maxSize == front 【满】

  4. 对队列为空的条件, rear == front 空

  5. 当我们这样分析, 队列中有效的数据的个数 (rear + maxSize - front) % maxSize,这里之所以 取模 是为了 循环,也就是为了把下标循环接起来,因为出队front后移,入队rear后移【这里需要细品,最好自己手动带入数据去试一下】

    要考虑 front 和 rear 的值不能超出 maxSize-1。

具体代码:

import java.util.Scanner;

public class CircleArrayQueue {
    public static void main(String[] args) {
        // 创建一个环形队列
        CircleArray queue = new CircleArray(4);   // 说明设置4,其队列的有效数据最大容量是3,rear要预留一个空位置
        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());
                    }
                    break;
                case 'e':  // 退出
                    scanner.close();
                    loop=false;
                    break;
                default:
                    break;
            }
        }
        System.out.println("程序退出~~");
    }
}

class CircleArray{
    private int maxSize;  // 表示数组的最大容量
    private int front;  // 队列头,front 变量指向队列的第一个元素, 也就是说 arr[front] 就是队列的第一个元素,front 的初始值 = 0
    private int rear;  //  队列尾,rear 变量指向队列的最后一个空位置. 因为希望空出一个空间做为约定,rear 的初始值 = 0
    private int[] arr;  // 该数据用于存放数据,模拟队列

    public CircleArray(int arrMaxSize){
        maxSize=arrMaxSize;
        arr=new int[maxSize];
    }

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

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

    // 添加数据到队列
    public void addQueue(int n){
        // 判断队列是否满
        if(isFull()){
            System.out.println("队列满,不能加入数据~~");
            return;
        }
        // 直接将数据加入
        arr[rear]=n;

        // 将 rear 后移,这里必须考虑取模
        rear=(rear+1)%maxSize;
    }

    //  获取队列的数据,出队列
    public int getQueue(){
        //  判断队列是否空
        if(isEmpty()){
            //  通过抛出异常
            throw new RuntimeException("队列满,不能取数据~~");
        }
        //  这里需要分析出  front 是指向队列的第一个元素
        //  1. 先把 front 对应的值保留到一个临时变量
        //  2. 将 front 后移,考虑取模
        //  3. 将临时保存的变量返回
        int value=arr[front];
        front=(front+1)%maxSize;
        return value;
    }

    //  显示队列的所有数据
    public void showQueue(){
        // 遍历
        if(isEmpty()){
            System.out.println("队列空的,没有数据~~");
            return;
        }

        //  思路:从 front 开始遍历,遍历多少个元素
        for(int i=front;i<front+size();i++){
            System.out.printf("arr[%d]=%d\n",i%maxSize,arr[i%maxSize]);
        }
    }

    // 求出当前队列有效数据的个数
    public int size(){
        return (rear+maxSize-front)%maxSize;
    }

    // 显示队列的头数据, 注意不是取出数据
    public int headQueue(){
        //  判读
        if (isEmpty()){
            throw new RuntimeException("队列空的,没有数据~~");
        }
        return arr[front];
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值