web-dev-qa-db-ja.com

Python=)の両端キューはどのように実装されますか。リストよりも悪いのはいつですか?

最近、コードをより効率的にするために、Pythonでさまざまなデータ構造がどのように実装されているかを調査しました。リストと両端キューの動作を調査すると、私はリスト内のO(n)からdeques内のO(1)(リストは固定長配列として実装されているリスト何かが前面に挿入されるたびに完全にコピーする必要があります...)。見つけることができないと思われるのは、両端キューの実装方法の詳細と、その欠点とリストの詳細です。これら二つの質問に私?

61
Eli

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

dequeobjectは、blockノードの二重リンクリストで構成されます。

そうです、別の答えが示唆するように、dequeは(二重に)リンクされたリストです。

詳細:これは、Pythonリストは、スライスを含むランダムアクセスおよび固定長の操作にははるかに優れているが、両端キューは、インデックス作成(ただし、興味深いことにスライスはできません)は可能ですが、リストよりも遅くなります。

61
JAB

チェックアウト - collections.deque 。ドキュメントから:

Dequeは、スレッドセーフでメモリ効率の良い追加とポップをサポートし、どちらの方向でもほぼ同じO(1)パフォーマンスと同じです。

リストオブジェクトは同様の操作をサポートしますが、高速固定長操作と、両方を変更するpop(0)およびinsert(0、v)操作のメモリ移動コストO(n))に最適化されています基になるデータ表現のサイズと位置。

言うように、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
44
zeekay

deque objects のドキュメントエントリは、あなたが知る必要のあるもののほとんどを綴っていると思います。注目すべき引用符:

Dequeは、スレッドセーフでメモリ効率の良い追加とポップをサポートし、どちらの方向でもほぼ同じO(1)パフォーマンスと同じです。

だが...

インデックスアクセスは、両端でO(1)ですが、途中でO(n)になります。高速ランダムアクセスの場合は、代わりにリストを使用します。

ソースを見て、実装がリンクリストか他の何かかを判断する必要がありますが、dequeは二重リンクリストとほぼ同じ特性を持っているように聞こえます。

14
senderle

他のすべての有用な回答に加えて、 here は、Pythonリスト、両端キュー、セット、および辞書これは、特定の問題に適切なデータ構造を選択するのに役立つはずです。

9
PythonJin

一方、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 __== '__main__':
    main()

Cの実装ほど高速ではありませんが、同じロジックを使用します。

0
TheRevanchist