《玩转数据结构 从入门到进阶》 循环数组实现队列

本文来源于liuyubobobo的“玩转数据结构 从入门到进阶”视频教程

队列是一种特殊的线性表,特殊之处在于它只允许在表的前端进行删除操作,而在表的后端进行插入操作。进行插入操作的端称为队尾,进行删除操作的端称为队头。

假设我们使用数组实现队列,请看下图

入队操作很快速,只需要在队尾添加元素即可。但是,出队操作就耗时了,把队头元素删除并返回给调用者后,还需要把数组中的所有元素往前移动,这一步非常耗时。

a出队后,b、c、d要移动到下标为0、1、2的位置

有办法做到出队时,数组元素不移动吗?有滴,使用循环数组

循环队列有几个特点:

1、front变量指向队头(有元素),tail变量指向队尾(无元素)。

2、出队时front往后移动一个下标(“往后”这种说法不是完全正确,后面再给出精确定义;扩容后面会讲)

3、入队则是在队尾添加元素,队尾往后移动一个下标

4、front==tail表示队列为空,所以队列满的时候,不是front、tail都指向同一个元素,而是tail在front的前一个位置。

上图是队列满的情况,数组的长度是8,但队列只能储存7个元素,必须让一个元素为空,tail指向这个元素。

是想下,如果非要用完数组的所有存储空间,此时front==tail;而队列为空是也是front==tail,在代码实现上要区分何时队列为空、何时队列满就复杂了。干脆就“浪费”一个元素内存,front==tail就只有队列为空这一种情况。

5、定义数组长度data.length(图中数组长度是8),队列满的时候判断条件是  (tail+1) % data.length == front ,大家照着图好好理解这个判断条件。

6、入队,tail移动方式是 tail = (tail + 1) % data.length

     出队,front移动方式是 front = (front + 1) % data.length

用上图举个例子

入队操作,tail = (1+1) % 8 = 2,tail变成2

出队操作,front = (3+1) % 8 = 4,front变成4

当front在7位置,出队,fornt = (7+1) % 8 = 0,此时front指向下标0的位置

7、扩容,如下图。满足条件(tail + 1) % data.length == front,此时需要扩容

实现代码如下

// 基于循环数组实现队列
public class LoopQueue<E>{
    private E[] data;  // 数组
    private int front, tail;  //队头、队尾
    private int size;  //队列元素大小

    public LoopQueue(){
        data = (E[]) new Object[11];  //初始化队列的数组
        // 初始化时队头、队尾下标都为0,队列大小也是0
        front = 0;
        tail = 0;
        size = 0;
    }

    // 队列容量是数组长度减1,因为要空一个元素,以便区别队列是否为空、队列是否满了需要扩容
    public int getCapacity(){
        return data.length -1;
    }

    //队列为空
    public boolean isEmpty(){
        return front == tail;
    }

    //队列大小
    public int getSize(){
        return size;
    }

    //扩容、缩容
    private void resize(int newCapacity){
        // 数组长度要比容量多一个
        E[] newData = (E[])new Object[newCapacity+1];
        //将旧数据从队头开始,添加到新数组中
        for (int i=0; i<size; i++){
            newData[i] = data[(i+front) % data.length];
        }
        front = 0;  //添加完成,队头在新数组下标为0的位置
        tail = size;  //添加完成,队列大小就是队尾的下标
        data = newData;  //队列的数组data指向扩容后的数组
    }

    //入队
    public void enqueue(E e){
        //如果队列满了,则扩容
        if ((tail + 1) % data.length == front){
            resize(getCapacity() * 2);
        }
        data[tail] = e;
        //队尾下标移动
        tail = (tail + 1) % data.length;
        //队列大小加一
        size++;
    }

    //出队
    public E dequeue(){
        if (isEmpty())
            throw new RuntimeException("没数据了");
        //队头数据存储到变量中
        E ret = data[front];
        data[front] = null;
        //队头下标移动
        front = (front + 1) % data.length;
        //队列大小减一
        size--;

        //这是缩容,如果队列大小小于容量的1/4且容量的1/2不为0,队列缩容为原来的1/2
        if (size < getCapacity()/4 && getCapacity()/2 != 0){
            resize(getCapacity()/2);
        }
        //返回队头数据
        return ret;
    }



    @Override
    public String toString(){

        StringBuilder res = new StringBuilder();
        res.append(String.format("Queue: size = %d , capacity = %d\n", size, getCapacity()));
        res.append("front [");
        for(int i = front ; i != tail ; i = (i + 1) % data.length){
            res.append(data[i]);
            if((i + 1) % data.length != tail)
                res.append(", ");
        }
        res.append("] tail");
        return res.toString();
    }

    public static void main(String[] args){

        _2_LoopQueue.LoopQueue<Integer> queue = new _2_LoopQueue.LoopQueue<>();
        for(int i = 0 ; i < 20 ; i ++){
            queue.enqueue(i);
            System.out.println(queue);

            if(i % 10 == 0){
                queue.dequeue();
                System.out.println(queue);
            }
        }
        for(int i = 0 ; i < 18 ; i ++){
            queue.dequeue();
            System.out.println(queue);
        }
    }

}
发布了51 篇原创文章 · 获赞 14 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/u010606397/article/details/98173224