Как двухсторонних очередей в Python реализованы, и когда они хуже, чем списки?

недавно я начал исследовать, как различные структуры данных реализованы в Python, чтобы сделать мой код более эффективным. Исследуя, как работают списки и деки, я обнаружил, что могу получить преимущества, когда хочу сдвинуть и отменить смещение, сократив время от O(n) в списках до O(1) в деках (списки реализуются как массивы фиксированной длины, которые должны быть полностью скопированы каждый раз, когда что-то вставлено спереди и т. д...). То, что я не могу найти, - это особенности того, как реализован deque, и специфика его недостатков v. s. списки. Может кто-нибудь просветить меня на эти два вопроса?

5 ответов


https://hg.python.org/cpython/file/3.5/Modules/_collectionsmodule.c

A dequeobject состоит из дважды связанного списка block узлы.

Так что да,deque это (дважды)связанный список, как еще один ответ напрашивается.

разработка: это означает, что списки Python намного лучше для операций с произвольным доступом и фиксированной длиной, включая нарезку, в то время как deques намного полезнее для нажатия и выскакивания вещи с концов, с индексированием (но не нарезкой, что интересно) возможно, но медленнее, чем со списками.


проверить collections.deque. Из документов:

поддержка двусторонней очередью потокобезопасным, памяти эффективные добавления и соз сторона deque с приблизительно та же производительность O(1) в любом направление.

хотя объекты списка поддерживают подобное операции, они оптимизированы для быстрые деятельности фиксированной длины и incur O (n) расходы на перемещение памяти для pop(0) и вставить операции (0, v), которые изменить размер и положение базовое представление данных.

как говорится, использование pop(0) или insert (0, v) влечет за собой большие штрафы с объектами списка. Вы не можете использовать операции среза / индекса на deque, но вы можете использовать popleft/appendleft, которые являются операциями deque оптимизирован для. Вот простой тест, чтобы продемонстрировать это:

import time
from collections import deque

num = 100000

def append(c):
    for i in range(num):
        c.append(i)

def appendleft(c):
    if isinstance(c, deque):
        for i in range(num):
            c.appendleft(i)
    else:
        for i in range(num):
            c.insert(0, i)
def pop(c):
    for i in range(num):
        c.pop()

def popleft(c):
    if isinstance(c, deque):
        for i in range(num):
            c.popleft()
    else:
        for i in range(num):
            c.pop(0)

for container in [deque, list]:
    for operation in [append, appendleft, pop, popleft]:
        c = container(range(num))
        start = time.time()
        operation(c)
        elapsed = time.time() - start
        print "Completed %s/%s in %.2f seconds: %.1f ops/sec" % (container.__name__, operation.__name__, elapsed, num / elapsed)

результаты на моей машине:

Completed deque/append in 0.02 seconds: 5582877.2 ops/sec
Completed deque/appendleft in 0.02 seconds: 6406549.7 ops/sec
Completed deque/pop in 0.01 seconds: 7146417.7 ops/sec
Completed deque/popleft in 0.01 seconds: 7271174.0 ops/sec
Completed list/append in 0.01 seconds: 6761407.6 ops/sec
Completed list/appendleft in 16.55 seconds: 6042.7 ops/sec
Completed list/pop in 0.02 seconds: 4394057.9 ops/sec
Completed list/popleft in 3.23 seconds: 30983.3 ops/sec

запись документация deque объекты излагает большая часть того, что вам нужно знать, я подозреваю. Примечательные цитаты:

Deques поддерживает потокобезопасную, эффективную память, добавляет и всплывает с обеих сторон deque с примерно одинаковой производительностью O(1) в любом направлении.

но...

индексированный доступ-O(1) на обоих концах, но замедляется до O (n) посередине. Для быстрого произвольного доступа используйте списки вместо.

мне нужно взглянуть на источник, чтобы сказать, является ли реализация связанным списком или чем-то еще, но для меня это звучит как deque имеет примерно те же характеристики, что и двусвязный список.


в дополнение ко всем другим полезным ответам,здесь - это дополнительная информация, сравнивающая временную сложность (Big-Oh) различных операций над списками Python, deques, наборами и словарями. Это должно помочь в выборе правильной структуры данных для конкретной задачи.


хотя я не совсем уверен, как Python реализовал его, здесь я написал реализацию очередей, используя только массивы. Он имеет ту же сложность, что и очереди Python.

class ArrayQueue:
""" Implements a queue data structure """

def __init__(self, capacity):
    """ Initialize the queue """

    self.data = [None] * capacity
    self.size = 0
    self.front = 0

def __len__(self):
    """ return the length of the queue """

    return self.size

def isEmpty(self):
    """ return True if the queue is Empty """

    return self.data == 0

def printQueue(self):
    """ Prints the queue """

    print self.data 

def first(self):
    """ Return the first element of the queue """

    if self.isEmpty():
        raise Empty("Queue is empty")
    else:
        return self.data[0]

def enqueue(self, e):
    """ Enqueues the element e in the queue """

    if self.size == len(self.data):
        self.resize(2 * len(self.data))
    avail = (self.front + self.size) % len(self.data) 
    self.data[avail] = e
    self.size += 1

def resize(self, num):
    """ Resize the queue """

    old = self.data
    self.data = [None] * num
    walk = self.front
    for k in range(self.size):
        self.data[k] = old[walk]
        walk = (1+walk)%len(old)
    self.front = 0

def dequeue(self):
    """ Removes and returns an element from the queue """

    if self.isEmpty():
        raise Empty("Queue is empty")
    answer = self.data[self.front]
    self.data[self.front] = None 
    self.front = (self.front + 1) % len(self.data)
    self.size -= 1
    return answer

class Empty(Exception):
""" Implements a new exception to be used when stacks are empty """

pass

и здесь вы можете проверить его с помощью некоторого кода:

def main():
""" Tests the queue """ 

Q = ArrayQueue(5)
for i in range(10):
    Q.enqueue(i)
Q.printQueue()    
for i in range(10):
    Q.dequeue()
Q.printQueue()    


if __name__ == '__main__':
    main()

Он не будет работать так же быстро, как реализация C, но он использует ту же логику.