Как двухсторонних очередей в 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, но он использует ту же логику.