队列:顺序队列和循环队列

Posted 一张红枫叶

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了队列:顺序队列和循环队列相关的知识,希望对你有一定的参考价值。

和栈的先进后出不同,队列的形式是先进先出,队列的想法来自于生活中排队的策略, 顾客在付款结账的时候,按照到来的先后顺序排队结账。先来的顾客先结账,后来的顾客后结账。

队列有两种实现形式:1 顺序表实现 2 循环顺序表 

首先来看下顺序表的实现,在python中,队列的实现用list来写十分的方便。实现方式如下:

class line_queue():

    def __init__(self):

        self._elem=[]

    def push(self,elem):

        self._elem.append(elem)

    def pop(self):

        elem=self._elem.pop(0)

        return elem

    def queue_length(self):

        return len(self._elem)

和栈唯一的区别是,这里pop是pop(0),也就是首先进队列的数据出列。这个实现很简单,但是有一个问题,每次有元素出队列的时候,元素都必须要进行前移。这就带来了一个问题,它的操作复杂度为O(n),而不是O(1)。只有从尾部弹出元素也就是先进后出的时候复杂度为O(1).

那么如何才能满足O(1)的出列复杂度呢。我们可以考虑记住队头和队尾的位置。每次出队的时候直接将队头位置的元素弹出就可以了。具体的实现可以参考下图

下面来看下代码的实现:

class line_queue_update():

    def __init__(self):

        self._elem=[]

        self.head=self.rear=0

    def push(self,elem):

        self._elem.append(elem)

        self.rear+=1

    def pop(self):

        elem=self._elem[self.head]

        self.head+=1

        return elem

    def queue_length(self):

        return len(self._elem)

    def get_elem(self):

        print self._elem

 

if __name__=="__main__":

    q=line_queue_update()

    for i in range(10):

        q.push(i)

    print \'The length is %d\' % q.queue_length()

    q.pop()

    q.pop()

    q.push(90)

    q.push(100)

    q.push(200)

print \'The length is %d\' % q.queue_length()

运行结果如下:

/usr/bin/python2.7 /home/zhf/py_prj/data_struct/chapter5.py

The length is 10

The length is 13

这个方法的实现出队列的复杂度就是O(1)。但是我们同时也注意到另外一个问题,那就是尽管我们曾经pop元素。但是整个list的长度在不断的增加。这是为什么呢,是因为队头/对尾位置变量的值将随着操作移动。从操作效率来看,每个操作都在O(1)时间完成。但另一方面,表中元素序列随着操作向表尾方向移动,这就导致了在表的前端留下来了越来越多的空间,这些空间就是那些已经出队的元素曾经占据的空间。在c语言中,表元素存储的大小是固定的,经过反复的入队和出队操作。一定会在某次入队时出现队尾溢出也就是表满的情况。对于python而言,由于list是自动增长的,随着操作进行,表前端留下了越来越大的空区,而且这片空区永远也不会用到,完全浪费了。

前面介绍了顺序队列的两种实现方式,第一种时间效率无法满足,空间效率满足。 第二种时间效率满足,空间效率无法满足。两种策略都有各自的缺点,那么是否有一种方式既能满足时间效率也能满足空间效率呢。这就需要用到队列的第二种实现方式:循环顺序表

循环顺序表是在顺序表的第二种方式进行的变种。前面介绍过,通过head,rear记录表头和表尾元素的方式可以做到时间效率为O(1)但是会导致大量的空间浪费。但是如果我们将顺序表看做一种环形结构。那么空间浪费的问题就可以解决了,结构如下所示。

那么在这个结构里面,数据就保存在q.front到q.rear的位置里面。两个变量的差就是队列里面的元素个数。这种结构下元素始终在一个环里进行轮转。空位置可以被新插入的元素占据。

那么在环形结构中,需要注意以下3点:

1当队列为空或者为满的时候,q.front=q.rear。由于q.front=q.rear对应队空和队满两种情况,因此为了区分,可以通过队列长度来判断,当q.front=q.rear且q.len=q.max的时候认为为队满,否则为队空。

2 出队的时候q.front=(q.front+1)%q.len

3 入队的时候q.rear=(q.rear+1)%q.len

第2点和第3点的实现方式和顺序表不一样,在更新队头和队尾位置的时候需要进行求余操作,这是因为循环队列的空间大小固定,队头和队尾的位置是相对增长的,不是绝对增长的。下面来看下具体的实现代码

class squeue():

    def __init__(self,init_len=8):

        self._len=init_len

        self._elem=[0]*init_len

        self._head=0

        self._rear=0

        self._num=0

    def is_empty(self):

        return self._num == 0

    def is_full(self):

        return self._num == self._len

    def dequeue(self):

        if self.is_empty():

            raise BaseException(\'queue is empty\')

        e=self._elem[self._head]

        self._head=(self._head+1) % self._len

        self._num-=1

        return e

 

    def enqueue(self,elem):

        if self.is_full():

            raise BaseException(\'queue is full\')

        self._elem[self._rear] = elem

        self._rear=(self._rear+1) % self._len

        self._num+=1

    def get_elem(self):

        print self._elem

 

if __name__=="__main__":

    q=squeue()

    for i in range(8):

        q.enqueue(i)

    q.get_elem()

    q.dequeue()

    q.enqueue(100)

q.get_elem()

首先插入8个元素,然后出队一个元素

/usr/bin/python2.7 /home/zhf/py_prj/data_struct/chapter5.py

[0, 1, 2, 3, 4, 5, 6, 7]

[100, 1, 2, 3, 4, 5, 6, 7]

当继续插入元素的时候,q.enqueue(100)会提示队列已满

/usr/bin/python2.7 /home/zhf/py_prj/data_struct/chapter5.py

Traceback (most recent call last):

  File "/home/zhf/py_prj/data_struct/chapter5.py", line 196, in <module>

    q.enqueue(100)

  File "/home/zhf/py_prj/data_struct/chapter5.py", line 173, in enqueue

    raise BaseException(\'queue is full\')

BaseException: queue is full

我们可以添加一个扩充队列的操作,防止队列满导致插入失败

    def expand(self):

        old_len=self._len

        self._len*=2

        new_elems=[0]*self._len

        for i in range(old_len):

            new_elems[i] = self._elem[(self._head+i-1) % old_len]

        self._elem,self._head,self._rear=new_elems,0,self._head+self._num

if __name__=="__main__":

    q=squeue()

    for i in range(8):

        q.enqueue(i)

    q.get_elem()

    q.dequeue()

    q.enqueue(100)

    q.get_elem()

    q.enqueue(200)

q.get_elem()

运行结果如下:

/usr/bin/python2.7 /home/zhf/py_prj/data_struct/chapter5.py

[0, 1, 2, 3, 4, 5, 6, 7]

[100, 1, 2, 3, 4, 5, 6, 7]

[100, 1, 2, 3, 4, 5, 6, 7, 0, 200, 0, 0, 0, 0, 0, 0]

 

以上是关于队列:顺序队列和循环队列的主要内容,如果未能解决你的问题,请参考以下文章

队列:顺序队列和循环队列

队列(循环队列)

9.顺序队列循环队列链队列

数据结构学习笔记——顺序存储结构实现循环队列

数据结构学习笔记——顺序存储结构实现循环队列

循环队列