【问题标题】:Implementing an efficient queue in Python在 Python 中实现一个高效的队列
【发布时间】:2018-01-23 03:20:50
【问题描述】:

我一直在尝试在 Python 中实现一个队列,但我遇到了一个问题。

我正在尝试使用列表来实现 Queue 数据结构,但是我不太清楚如何进行 enqueuedequeue O(1) 操作。

我在网上看到的每个示例似乎都只是附加了enqueue 操作,并从dequeue 操作的列表中删除了第一个元素。但这会使dequeue 操作 O(n)(其中 n 是列表的大小)正确吗?

我错过了什么基本的东西吗?或者你必须使用 LinkedLists 来有效地实现队列?

import unittest

class Queue:
    def __init__(self):
        self._queue = []
        self.size = 0
        self.maxSize = 10

    def enqueue(self, item):
        if self.size < self.maxSize:
            self._queue.append(item)

    def dequeue(self):
        '''
        Removes an item from the front of the list. Remove first element of
        the array
        '''
        first = self._queue[0]
        del self._queue[0]
        return first

【问题讨论】:

  • 为什么不使用collections.deque

标签: python queue


【解决方案1】:

作为Uri Goren 精明的noted above,Python 标准库已经代表您实现了一个高效的队列:collections.deque

什么不该做

避免自己动手重新发明轮子:

  • Linked list implementation。虽然这样做可以将 dequeue()enqueue() 方法的最坏情况时间复杂度降低到 O(1),但 collections.deque 类型已经这样做了。鉴于其基于 C 的传统,它也是线程安全的,并且可能更节省空间和时间。
  • Python list implementation。正如我note below 一样,根据 Python 列表实现 enqueue() 方法会将其最坏情况的时间复杂度增加到 O(n)。 因为从基于 C 的数组中删除最后一项并且因此 Python 列表是一个恒定时间操作,根据 Python 列表实现 dequeue() 方法保留了相同的最坏情况时间复杂度 O(1)。但谁在乎? enqueue() 仍然慢得可怜。

引用official deque documentation

虽然list 对象支持类似的操作,但它们针对快速固定长度操作进行了优化,并且对于pop(0)insert(0, v) 操作会产生 O(n) 内存移动成本,这些操作会同时改变基础数据的大小和位置表示。

更重要的是,deque 通过在初始化时传递的 maxlen 参数提供了对最大长度的开箱即用支持,无需手动尝试限制队列大小(由于 if 条件中隐含的竞争条件,这不可避免地会破坏线程安全)。

做什么

相反,按照标准collections.deque 类型实现您的Queue 类,如下所示:

from collections import deque

class Queue:
    '''
    Thread-safe, memory-efficient, maximally-sized queue supporting queueing and
    dequeueing in worst-case O(1) time.
    '''


    def __init__(self, max_size = 10):
        '''
        Initialize this queue to the empty queue.

        Parameters
        ----------
        max_size : int
            Maximum number of items contained in this queue. Defaults to 10.
        '''

        self._queue = deque(maxlen=max_size)


    def enqueue(self, item):
        '''
        Queues the passed item (i.e., pushes this item onto the tail of this
        queue).

        If this queue is already full, the item at the head of this queue
        is silently removed from this queue *before* the passed item is
        queued.
        '''

        self._queue.append(item)


    def dequeue(self):
        '''
        Dequeues (i.e., removes) the item at the head of this queue *and*
        returns this item.

        Raises
        ----------
        IndexError
            If this queue is empty.
        '''

        return self._queue.pop()

证据就在地狱布丁中:

>>> queue = Queue()
>>> queue.enqueue('Maiden in Black')
>>> queue.enqueue('Maneater')
>>> queue.enqueue('Maiden Astraea')
>>> queue.enqueue('Flamelurker')
>>> print(queue.dequeue())
Flamelurker
>>> print(queue.dequeue())
Maiden Astraea
>>> print(queue.dequeue())
Maneater
>>> print(queue.dequeue())
Maiden in Black

一个人去很危险

实际上,也不要这样做。

您最好只使用原始的deque 对象,而不是尝试手动将该对象封装在Queue 包装器中。上面定义的Queue作为deque API 的通用实用程序的简单演示。

deque 类提供significantly more features,包括:

...迭代、酸洗、len(d)reversed(d)copy.copy(d)copy.deepcopy(d)、使用 in 运算符的成员资格测试以及下标引用,例如 d[-1]

只需在需要单端或双端队列的任何地方使用deque。就是这样。

【讨论】:

  • 在您的示例中,Maiden in Black 不应该排在第一位吗?我认为您的dequeue 方法将使用self._queue.popleft()popleft 而不是pop)。再说一次,这只是表明滚动自己的另一个问题。 :)
  • 与@lindes 所写的内容相关,此答案中实现的内容有时被称为后进先出 (LIFO) 队列,但通常称为堆栈。 “队列”一词本身被广泛用于指代先进先出 (FIFO) 队列,因此会造成混淆。
  • 您的代码注释说 collections.dequeue 是“内存高效的”,但每个节点包含的指针数量是此处所需的两倍(因为只需要一个单链表,因此您不需要反向链接)。这就像推荐使用二叉树数据结构来实现单链表(只使用左子指针​​而忽略右子指针),不是吗?
【解决方案2】:

您可以在queue class中保留头尾节点而不是队列列表

class Node:
    def __init__(self, item = None):
        self.item = item
        self.next = None
        self.previous = None


class Queue:
    def __init__(self):
        self.length = 0
        self.head = None
        self.tail = None

    def enqueue(self, value):
        newNode = Node(value)
        if self.head is None:
            self.head = self.tail = newNode
        else:
            self.tail.next = newNode
            newNode.previous = self.tail
            self.tail = newNode
        self.length += 1

    def dequeue(self):
        item = self.head.item
        self.head = self.head.next 
        self.length -= 1
        if self.length == 0:
            self.tail = None
        return item

【讨论】:

    【解决方案3】:

    在 Python 中使用 list 实现队列,按照 inbuild 队列数据结构处理 enqueue 和 dqueue:

    class queue:
    
        def __init__(self, max_size, size=0, front=0, rear=0):
            self.queue = [[] for i in range(5)] #creates a list [0,0,0,0,0]
            self.max_size = max_size
            self.size = size
            self.front = front
            self.rear = rear
        
    
        def enqueue(self, data):
            if not self.isFull():
                self.queue[self.rear] = data
                self.rear = int((self.rear + 1) % self.max_size)
                self.size += 1
            else:
                print('Queue is full')
    
        def dequeue(self):
            if not self.isEmpty():
                print(self.queue[self.front], 'is removed')
                self.front = int((self.front + 1) % self.max_size)
                self.size -= 1
            else:
                print('Queue is empty')
    
        def isEmpty(self):
            return self.size == 0
    
        def isFull(self):
            return self.size == self.max_size
    
        def show(self):
            print ('Queue contents are:')
            for i in range(self.size):
                print (self.queue[int((i+self.front)% self.max_size)])
    
            # driver program
            q = queue(5)
            q.enqueue(1)
            q.enqueue(2)
            q.enqueue(3)
            q.enqueue(4)
            q.enqueue(5)
            q.dequeue()
            q.show()
    

    【讨论】:

      【解决方案4】:

      这是我使用数组实现的队列,enqueuedequeue 都是 O(1) 操作。该实现基于 CLRS。

      class Queue:
          def __init__(self, length):
              """a queue of at most n elements using an array of n+1 element size"""
              self.length = length
              self.queue = [None]*(length+1)
              self.head = 0
              self.tail = 0
      
          def enqueue(self, x):
              if self.is_full():
                  return 'Overflow'
              self.queue[self.tail] = x
              if self.tail == self.length:
                  self.tail = 0
              else:
                  self.tail = self.tail + 1
      
          def dequeue(self):
              if self.is_empty():
                  return 'Underflow'
              x = self.queue[self.head]
              if self.head == self.length:
                  self.head = 0
              else:
                  self.head = self.head + 1
              return x
      
          def is_empty(self):
              if self.head == self.tail:
                  return True
              return False
      
          def is_full(self):
              if self.head == self.tail+1 or (self.head == 0 and self.tail == self.length):
                  return True
              return False
      

      【讨论】:

        【解决方案5】:
        # Linear Queue Implementation using Array
        class Queue:
            """Class with List as Array """
            def __init__(self):
                self.v_list=[]
            """Storing the in FIFO order"""
            def enqueue(self,value):
                self.v_list.append(value)
           """Removing Element from Queue in FIFO order"""
            def dequeue(self):
                if len(self.v_list)==0:
                    print('Queue is Empty')
                    return
                self.v_list.pop(0)
        
            def print_queue(self):
                print(self.v_list)
        
            def size_of_queue(self):
                return print(len(self.v_list))
        
        object_queue=Queue()
        object_queue.enqueue(0)
        object_queue.enqueue(1)
        object_queue.enqueue(2)
        object_queue.enqueue(3)
        object_queue.enqueue(4)
        object_queue.enqueue(5)
        object_queue.print_queue()
        object_queue.dequeue()
        object_queue.print_queue()
        object_queue.dequeue()
        object_queue.print_queue()
        object_queue.size_of_queue()
        
        #Circular Queue Implementation using Array
        class CircularQueue():
            def __init__(self):
                """Class to hold the Postions for insert and delete"""
                self.start_pointer=0
                self.end_pointer=-1
                self.queue_list=[]
            """Storing the element in Circular order, with circular we can remove empty Block"""
            def enqueue(self,value):
                if len(self.queue_list)>10:
                    print("Circular Queue is Full")
                    return
                """Checking for Empty Block in Array and storing data and reseting the stat end point to process the element"""
                if 'None' in self.queue_list:
                    self.queue_list[self.end_pointer]=value
                    self.end_pointer+=1
                else:
                    self.queue_list.append(value)
                    self.end_pointer+=1
            """Removing element In FIFO order and reseting start ending point"""
            def dequeue(self):
                #self.queue_list.replace(self.queue_list[self.start_pointer],None)
                self.queue_list = [str(sub).replace(str(self.queue_list[self.start_pointer]),'None') for sub in self.queue_list] 
                self.start_pointer+=1
                for i ,j in enumerate(self.queue_list):
                    if j=='None':
                        self.end_pointer=i
                        break
            """For Printing Queue"""            
            def print_cq(self):
                if len(self.queue_list)>10:
                    print("Circular Queue is Full")
                    return
                print(self.queue_list,self.start_pointer,self.end_pointer)
        
        
        cir_object=CircularQueue()
        cir_object.enqueue(0)
        cir_object.enqueue(1)
        cir_object.enqueue(2)
        cir_object.enqueue(3)
        cir_object.enqueue(4)
        cir_object.enqueue(5)
        cir_object.enqueue(6)
        cir_object.enqueue(7)
        cir_object.enqueue(8)
        cir_object.enqueue(9)
        #cir_object.print_cq()
        cir_object.dequeue()
        cir_object.dequeue()
        cir_object.print_cq()
        cir_object.enqueue(15)
        cir_object.enqueue(20)
        cir_object.print_cq()
        

        【讨论】:

          【解决方案6】:
          class Queue:
              def __init__(self,no):
                  self.no = no 
                  self.SQueue = []
                  self.front = -1
                  self.rear = -1
              def insert(self):
                  if self.rear == self.no -1:
                      print("Queue is Full.....")
                  else:           
                      if self.front == -1:
                          self.front = 0
                          self.rear = 0
                      else :
                          self.rear += 1
                      n = int(input("enter an element :: "))
                      self.SQueue.insert(self.rear, n)
          
              def delete(self):
                  if self.front == -1 and self.front == no - 1:
                      print("Queue is Empty.....")
                  else:
                      self.SQueue.pop(self.front)
                      self.front +=1
              def disp(self):
                  if self.front == -1 and self.front == no - 1:
                      print("Queue is Empty.....")
                  else:
                      print("REAR \tELEMENT")
                      for i in range(len(self.SQueue)):
                          print(i," \t",self.SQueue[i])
          
          no = int(input("ENTER  Size :: "))
          q = Queue(no)
          while(True):
              print(" 1: INSERT ")
              print(" 2: DELETE ")
              print(" 3: PRINT ")
              print(" 4: EXIT ")
              option = int(input("enter your choice :: "))
          
              if option == 1:
                  q.insert()
           
              elif option == 2:
                  q.delete()
          
              elif option == 3:
                  q.disp()
          
              elif option == 4:
                  print("you are exit!!!!!")
                  break
              else:
                  print("Incorrect option")
           
          

          【讨论】:

            【解决方案7】:

            在出队方法中没有任何循环。您只需要做列表操作。因此出队的时间复杂度也是 O(n)(linear)。

            class Queue:
                def __init__(self):
                   self.items=[]
                def enqueue(self,item):
                   self.items.append(item)
                def dequeue(self):
                   return self.items.pop(0)
                def isEmpty(self):
                   return self.items==[]
                def __len__(self):
                   return len(self.items)
            

            【讨论】:

            • No. Python 列表在内部实现为C arrays,而不是链接列表。虽然附加到 Python 列表的平均和摊销最坏情况时间 O(1),但上述self.items.append(item) 调用的非摊销最坏情况时间是O(n) (即线性而不是常数)。因此,您的enqueue 实现确实 有效地包含了一个隐式循环。虽然该循环可能是通过高效的基于程序集的连续内存副本实现的,但需要 no 此类副本的有效替代方案是众所周知的。
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2014-04-21
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多