队列(循环队列的顺序实现)

 

一、概述

与栈相反,队列是先进先出(FIFO),后进后出的数据结构。插入的一端叫做队尾,而出去的一端则称为队头或队首。但是队列(Queue)有一种扩展形式,称为双端队列(Deque),即可以在两端都进行插入和删除的操作,看起来双端队列似乎更加使用,但在实际应用中却并不常见。同样的,队列也有两种实现形式,即顺序队列和链队列。链队列可以参考链栈,直接将出栈操作改成删除头节点即可,插入删除方便,适合栈和队列。

顺序队列当然是数组实现,顺序队列的问题在于出队时前面空出的位置是否由后面的元素补充,如果不补充,那么会造成空间极度的浪费,如果补充,那么需要将每个元素都向前移,时间复杂度此时来到O(N),为了解决这个问题,循环队列应运而生,即将补充的元素放到前面由于出队而造成的空缺位置。这样就可以最大限度的利用已申请的空间。

 

 

 

 

 

 

 

 

 

二、顺序实现循环队列

数据域:

    private int Capacity;
    private int front;
    private int rear;
    private int [] data;
    static int DEFAULT_SIZE = 6;
    public Queue()
    {
        front = rear = 0;
        Capacity = DEFAULT_SIZE;
        data = new int [Capacity];
    }

由于JAVA并不支持泛型数组,因此我们以int型的队列作为示例。

这里简单描述一下循环队列的结构,一个空的循环队列如下图:

 

 当入队3个元素时变为:

 再出队1个元素

接下来是求长度和判满空

    public int getSize()
    {
        return (rear - front + Capacity) % Capacity;
    }

    public boolean isEmpty()
    {
        return (rear == front);
    }

    public boolean isFull()
    {
        return ((rear + 1) % Capacity == front);
    }

先说求元素个数(长度),从图中看起来好像直接返回rear-front即可,但是因为是循环队列,考虑下列情况:

 

 显然不能用简单的减法,必须将两种算法统一起来,因此(rear - front + Capacity) % Capacity正是起到这样的作用。

 

再看判满,如果将元素完全装满,情况机会变为

 

与空的情况一样都是两者重合,因此不能使用front==rear同时判断满和空,官方处理这种情况有两种办法,一是设置Flag进行标记,二是认为还剩一个空位时就认为队列已满。

这里我们采用第二种策略。即下面的情况表示队列已满,请扩容再进行插入,不要完全占满。

 

接下来分析入队出队

出队比较简单,只需判断队列是否为空,如果不为空则将front向前移一位(注意front处于末尾怎么移)。

入队则稍微比较复杂,首先判断是否满,如果不满,将新数据赋给当前rear所在的位置,然后rear将向后移一位(注意)。如果队列满了,则需进行扩容,乍一想,好像非常困难(我比较菜),后来仔细一想,你不需要再考虑旧队列的排列,直接相当于一次遍历并按顺序赋给新队列。以下为扩容代码:

    public void Enlarge()
    {
        int newCapacity = Capacity * 2 + 1;
        int [] newData = new int [newCapacity];
        for (int i = front, j = 0; i != rear; j++, i = (i + 1) % Capacity)
        {
            newData[j] = data[i];
        }
        front = 0;
        rear = getSize();
        data = newData;
        Capacity = newCapacity;
    }

完整的数据结构代码及主函数测试代码可以查看我的github

 

posted @ 2019-10-23 12:49  LeftBody  阅读(1243)  评论(0编辑  收藏  举报