私はプログラムを書いています。これは、データのリストの前または後ろのいずれかで多くの削除を行いますが、途中ではありません。
最後の要素の削除は安価だと理解していますが、最初の要素の削除はどうですか?たとえば、リストA
のアドレスが4000
にあり、要素0
が4000
にあり、要素1
が4001
にあるとします。
要素0
を削除してから、コンパイラに4001
のリストA
のアドレスを追加させるか、または1
の要素4001
を4000
の位置にシフトし、他のすべての要素を1
だけ下にシフトしますか?
いいえ、安くはありません。リストの先頭から要素を削除する(list.pop(0)
を使用するなど)ことはO(N)
操作であり、は避ける必要があります。同様に、(list.insert(0, <value>)
を使用して)最初に要素を挿入することも同様に非効率的です。
これは、リストのサイズを変更した後、要素をシフトする必要があるためです。 CPythonの場合、l.pop(0)
の場合、 これはmemmove
を使用して行われますが、l.insert(0, <value>)
の場合は シフトは、格納されたアイテムを介したループで実装されます 。
リストは、高速ランダムアクセスおよびO(1)
操作用にend。
ただし、この操作は一般的に行っているので、deque
モジュールの collections
を使用することを検討する必要があります (@ayhanがコメントで提案したように)。 deque
のドキュメントでは、list
オブジェクトがこれらの操作に適していない方法についても説明しています。
リストオブジェクトは同様の操作をサポートしますが、高速固定長操作およびincur
O(n)
基になるデータ表現のサイズと位置の両方を変更するpop(0)
およびinsert(0, v)
操作のメモリ移動コスト。
(エンファシス鉱山)
deque
データ構造は、開始と終了にそれぞれappendleft
/popleft
およびappend
/pop
メソッドを使用して、両側(開始と終了)にO(1)
の複雑さを提供します。
もちろん、サイズが小さいと、(deque
の構造に起因して)余分なスペース要件が発生します。これは、リストのサイズとして一般に問題にならない(そして@juanpaがコメントに記載しているように、常に保持されるとは限らない)育ちます。最後に、@ ShadowRangerの洞察に満ちたコメントノートのように、シーケンスサイズが非常に小さいと、前面からのポップまたは挿入の問題は簡単になり、本当に問題になることはありません。
つまり、簡単に言うと、多くのアイテムを含むリストで、両側から高速の追加/ポップが必要な場合はdeque
を使用し、そうでない場合はランダムにアクセスして末尾に追加する場合はlist
sを使用します。
PythonはO(n)のリストの先頭から要素を削除しますが、 collections.deque の末尾から要素を削除するのはO(1)のみです。結果として、両端キューは目的にとって素晴らしいものになりますが、両端キューの途中からアクセスまたは追加/削除すると、リストの場合よりもコストがかかることに注意してください。
O(n)削除のコストは、CPythonのリストはポインタの配列として実装されているだけなので、各要素のシフトコストに関する直感は正しいからです。
これは、Wikiの Python TimeComplexityページ で確認できます。