美文网首页数据结构与算法
数据结构学习笔记:循环队列原理及其实现

数据结构学习笔记:循环队列原理及其实现

作者: ChArLiE__X | 来源:发表于2019-08-10 12:31 被阅读0次

    在存在大量数据的情况中,数组队列由于某一元素出队时都需要把之后所有队列中的元素向前移动一个位置,复杂度为O(n),所以效率较低,对此,我们用一种新的方法实现队列——循环队列。

    所谓循环队列,就是队列首尾相连,把存储队列元素的表从逻辑上看成一个环。我们定义front(头)、tail(尾,最后一个元素的后一个位置)和size三个变量,当元素出列时,数组无需整体向前移,只需维护front变量即可。

    初始状态

    定义一个长度为capacity的数组,初始状态front=tail。

    元素入队 索引为0的元素出队

    当元素出队时,我们只需维护front+1就可以,无需将数组整体前移。

    但在元素入队时,会发生以下情况。

    “数组满了”

    此时,循环队列的“循环”就派上用场。

    我们在此时将新元素放入index=0的位置。

    新元素入队

    我们知道,front和tail相等时,也就是初始状态,数组为空,如果我们继续向数组中添加元素,front和tail又会相等,这里我们有两种方式处理:

    1.留出一个空间,当 (tail + 1) % capacity== front 时判断队列已满,front==tail 时判断数组为空。

    2.利用一个变量size储存数组实际元素数量,size==0时数组为空,size==capacity时数组为满。(无需浪费空间)

    *capacity=data.length


    下面我们用Java来实现。

    第一种版本:

    public boolean isEmpty(){

        return front==tail;

    }

    private void resize(int newCapacity){

        //扩容后数组front为0,旧数组依次由front到tail从新数组索引为0的位置开始存储

        E[] newData = (E[])new Object[newCapacity + 1]; //首先先初始化一个容量为newCapacity + 1的泛型数组,多出来的一个用于判断队列是否已满

        for (int i=0;i<size;i++)

        newData[i]=data[(i+front)%data.length];

        data=newData;

        front=0;

        tail=size;

    }

    public void enqueue(E e) {

        if ((tail + 1) % data.length == front) //如果数组满了则进行扩容

        resize(data.length *2);

        data[tail]=e;

        tail=(tail + 1) % data.length;

        size ++;

     }

    public E dequeue(){

        if(isEmpty())

        throw new IllegalArgumentException("Cannot dequeue from an empty queue.");//数组为空则抛出异常

        E ret = data[front];//存储即将出队的元素

        data[front]=null;

        front = (front + 1) % data.length;

        size--;

        if(size == getCapacity() / 4 && getCapacity() / 2 != 0)    //如果数组实际元素小于总容量的四分之一,则进行缩容,详见均摊复杂度

        resize(getCapacity()/2);

        return ret;

    }

    第二种版本:

    public boolean isEmpty(){

    //注意,我们不再使用front和tail之间的关系来判断队列是否为空,而直接使用size

        return size==0;

        }

    private void resize(int newCapacity){

    //扩容后数组front为0,旧数组依次由front到tail从新数组索引为0的位置开始存储

    E[] newData = (E[])new Object[newCapacity]; //首先先初始化一个容量为newCapacity的泛型数组

        for (int i=0;i<size;i++)

        newData[i]=data[(i+front)%data.length];

    data=newData;

        front=0;

    tail=size;

    }

    public void enqueue(E e) {

    if (size==data.length) //如果数组满了则进行扩容

    resize(data.length *2);

        data[tail]=e;

        tail=(tail + 1) % data.length;

        size ++;

     }

    public E dequeue(){

        if(isEmpty())

        throw new IllegalArgumentException("Cannot dequeue from an empty queue.");//数组为空则抛出异常

        E ret = data[front];//存储即将出队的元素

        data[front]=null;

        front = (front + 1) % data.length;

        size--;

        if(size == getCapacity() / 4 && getCapacity() / 2 != 0)    //如果数组实际元素小于总容量的四分之一,则进行缩容,详见均摊复杂度

        resize(getCapacity()/2);

        return ret;

    }


    实际上,我们还可以不用size变量来完成循环队列。

    此时,当循环队列没有开始“循环”时,size变量的值就是tail - front;当tail<front时,size变量的值就是tail+data.length-front。

    我们要得到size的值,用Java语言表示出来就是:return tail >= front ? tail - front : tail - front + data.length;

    其他操作均和以上一样。

    相关文章

      网友评论

        本文标题:数据结构学习笔记:循环队列原理及其实现

        本文链接:https://www.haomeiwen.com/subject/argjittx.html