数据结构和算法之数组队列

一、概念

顾名思义,就是用数组(array)去模拟队列(queue)。数组是有界的,所以会有数组容量(队列大小)和队列中实际元素个数两个变量,加上队列自带的队首和队尾指针,一共四个必要元素。

二、图解

1、描述

定义一个数组容量为4的数组队列。

2、入队

2.1、定义空队列

在这里插入图片描述

2.2、元素A入队

在这里插入图片描述

2.3、元素B入队

在这里插入图片描述

2.4、元素C入队

在这里插入图片描述

2.5、元素D入队

在这里插入图片描述

不难发现,队列满了,无法再添加了,这时候怎么办呢?两个思路:
1.入队的时候判断队列是否满,满了的话则抛出异常
2.入队的时候判断队列是否满,满了的话进行扩容(比如二倍扩容)

2.6、元素E入队

队列满,需扩容
在这里插入图片描述

2.7、入队小结

很简单,每次入队的时候只需要将队尾指针+1,然后将元素放入队列中即可。需要注意的就是每次入队需要判断队列是否满,满了的话可以扩容。下面一起看下出队示意图。

3、出队

3.1、描述

上述入队中的顺序是A->B->C->D-E,那么出队的时候也应该是A->B->C->D->E。先进先出嘛。

3.2、待出队的队列

在这里插入图片描述

3.3、元素A出队

在这里插入图片描述

3.4、元素BCDE出队

在这里插入图片描述

不难发现有个严重问题,空间浪费了,具体含义是索引位置为01234的空间都无法存放元素,这导致每次再入队的时候都会无脑扩容,却没利用上前面出队的空间。

3.5、出队小结

依旧很简单,每次出队的时候只需要将队首指针+1即可。问题:造成空间浪费。

4、阶段小结

综上所述(上述的出入队图解),可以总结以下几点:
1.队列为空判断条件:队首等于队尾
2.队列满判断条件:队尾等于capacity-1
3.入队:队尾++
4.出队:队首++
5.出队会造成空间浪费

三、coding

package com.chentongwei.struct.queue;

/**
 * Description:
 *
 * @author TongWei.Chen 2019-11-21 15:42:29
 */
public class MyArrayQueueDemo1 {

    public static void main(String[] args) {
        ArrayQueue2 arrayQueue2 = new ArrayQueue2(2);
        arrayQueue2.addAutoCapacity(1);
        arrayQueue2.addAutoCapacity(2);
        arrayQueue2.addAutoCapacity(3);
        arrayQueue2.addAutoCapacity(4);
        arrayQueue2.addAutoCapacity(5);
        arrayQueue2.addAutoCapacity(6);

        int value1 = arrayQueue2.removeAndGet();
        System.out.println(value1);
        int value2 = arrayQueue2.removeAndGet();
        System.out.println(value2);
        int value3 = arrayQueue2.removeAndGet();
        System.out.println(value3);
        int value4 = arrayQueue2.removeAndGet();
        System.out.println(value4);
        int value5 = arrayQueue2.removeAndGet();
        System.out.println(value5);
        arrayQueue2.show();
    }

}

class ArrayQueue2 {
    // 数组容量
    private int capacity;
    // 存放数据的数组
    private int[] data;
    // 队首指针
    private int front;
    // 队尾指针
    private int tail;

    public ArrayQueue2(int capacity) {
        this.capacity = capacity;
        data = new int[capacity];
        // 我这里队首队尾初始化为-1,并不是说队列必须为-1,我个人习惯而已。
        front = tail = -1;
    }

    /**
     * 判断队列是否已满
     *
     * @return
     */
    public boolean isFull() {
        return tail == capacity - 1;
    }

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

    /**
     * 入队操作,为实现扩容机制
     * @param value:需要入队的值
     */
    public void add(int value) {

        if (isFull()) {
            // 打log或throw new runtimeException
            System.out.println("满了,没实现自动扩容");
            return;
        }
        data[++ tail] = value;
    }

    /**
     * 入队操作,已实现自动扩容机制
     * @param value:需要入队的值
     */
    public void addAutoCapacity(int value) {
        if (isFull()) {
            System.out.println("满了,将自动为您扩容~");
            resize(2 * capacity);
        }
        data[++ tail] = value;
    }

    /**
     * 出队操作,出队且返回出队的元素
     * @return: 出队的元素
     */
    public int removeAndGet() {
        if (isEmpty()) {
            throw new RuntimeException("都空了,别remove了");
        }
        return data[++ front];
    }

    /**
     * 显示队列中的元素
     */
    public void show() {
        if (isEmpty()) {
            System.out.println("空的");
            return;
        }
        for (int value : data) {
            System.out.printf("value=%d\n", value);
        }
    }


    /**
     * 数组扩容或者缩容(2倍/一半)
     *
     * @param newCapacity 新的容量
     */
    private void resize(int newCapacity) {
        // 生成新的数组
        int[] newData = new int[newCapacity];
        // 将原来数组中的元素,赋值到新的数组中
        for (int i = 0; i < capacity; i++) {
            newData[i] = data[i];
        }
        // 将原来的数组的引用指向新数组
        data = newData;
        // 将原来的数组的容量大小改为最新大小
        capacity = newCapacity;
    }

}

输出结果:

满了,将自动为您扩容~
满了,将自动为您扩容~
1
2
3
4
5
value=1
value=2
value=3
value=4
value=5
value=6
value=0
value=0

四、如何解决空间浪费的问题?

1、方案

  • 方案一、出队时,将队列的数据整体向队首方向移动一位。
  • 方案二、出队逻辑不变,而是再入队的时候进行移动。

2、图解方案一

2.1、现有如下队列

在这里插入图片描述

2.2、假设AB要出队

在这里插入图片描述

2.3、小结

每一次出队都要整体向前移动一次,意味着每一次出队的时间复杂度都为O(n),我们方案二更佳,所以直接舍弃此lowb的方法。

3、图解方案二

3.1、现有如下队列

在这里插入图片描述

3.2、假设A要入队

在这里插入图片描述

入队发现队列满了,所以向前移动tail-front次,若没满,则无须移动,效率远高于方案一。

2.4、小结

在这里插入图片描述

2.5、方案二coding

/**
     * 入队操作,已实现自动扩容机制
     * @param value:需要入队的值
     */
    public void addAutoCapacity2(int value) {
        if (isFull()) {
            if (front == -1) {
                System.out.println("满了,将自动为您扩容~");
                resize(2 * capacity);
            } else {
                // 若后面没空间了,但是队首指针不在起始位,代表队首方向有位置可以填充。
                // 数据搬移 tail - front 次
                for (int i = front; i < tail; ++ i) {
                    // 整体向前移动,为什么是data[i + 1],而不是data[i]?因为i是front,front下一个是有效数据。可以看我3.1的图示
                    // 这还不明白的话自己画图梳理下,还不明白的话留言给我。
                    data[i - front] = data[i + 1];
                    // 赋值初始值,就是说你将A元素往前移动了一位,那么A元素原始位置要清空
                    data[i + 1] = 0;
                }
                // 数据版已完成后需要更新head和tail
                // 为什么要-1?因为else结束后有++tail,所以这里需要先-1,否则越界了。
                tail = tail - front - 1;
                front = -1;
            }
        }
        ++ tail;
        data[tail] = value;
    }
发布了28 篇原创文章 · 获赞 33 · 访问量 8307

猜你喜欢

转载自blog.csdn.net/ctwctw/article/details/103373994
今日推荐