STLのベクターとリスト


238

効果的なSTLで、

vectorは、デフォルトで使用されるシーケンスのタイプです。

それはどういう意味ですか?効率を無視vectorしても何でもできるようです。

誰かが私にvector実現可能なオプションではないlistが使用する必要があるシナリオを提供できますか?


6
それはあなたが尋ねたものではありませんが、ベクトルはポインタの「従来の」動的配列の薄いラッパーであるため、ベクトルにデフォルト設定することで、古いコード、Cライブラリ、または非テンプレートライブラリと簡単にやり取りできることを指摘する価値があります。とサイズ。

18
Bjarne Strostrupは実際に乱数を生成し、それらをそれぞれリストとベクトルに追加するテストを行いました。挿入は、リスト/ベクターが常に順序付けられるように行われました。これは通常「リストドメイン」ですが、ベクターはリストを大きく上回っています。理由は、メモリアクセスが遅く、キャッシュがシーケンシャルデータに対してより適切に機能するためです。「GoingNative 2012」からの基調講演ですべて利用可能です
回避


1
@evadingが言及したBjarne Stroustrupによる基調講演を見たい場合は、ここで見つけました:youtu.be/OB-bdWKwXsU
t

回答:


98

シーケンスの最後以外の場所に多数のアイテムを繰り返し挿入したい状況。

さまざまなタイプのコンテナごとに、複雑さの保証を確認します。

標準コンテナの複雑さの保証は何ですか?


2
末尾に要素を挿入すると、メモリ割り当てや要素のコピーコストが発生する可能性があるため、これも重要です。また、ベクトルの初めにelenetsを挿入することは不可能に隣接して、list持っているpush_front
Notinlist

9
いいえ、ベクターの最後に要素を挿入すると、一定の時間で償却されます。メモリの割り当てはたまにしか発生せず、ベクターを事前に割り当ててそれを防ぐことができます。もちろん、一貫した一定時間の挿入を保証しなければならない場合、これはまだ問題だと思います。
ブライアン

16
@Notinlist-次は「不可能の隣」ですか?v.insert(v.begin()、i)
Manuel

5
@Notinlist-私はあなたに同意します、それは私がOPに(パフォーマンス)の足で自分を撃ちたい場合にインターフェースが存在しないと考えさせたくないだけです。
Manuel

16
Bjarne Strostrupは実際に乱数を生成し、それらをそれぞれリストとベクトルに追加するテストを行いました。挿入は、リスト/ベクターが常に順序付けられるように行われました。これは通常「リストドメイン」ですが、ベクターはリストを大きく上回っています。理由は、メモリアクセスが遅く、キャッシュがシーケンシャルデータに対してより適切に機能するためです。「GoingNative 2012」の彼の基調講演ですべて利用可能です
回避

409

ベクター:

  • 連続したメモリ。
  • 将来の要素にスペースを事前に割り当てるため、要素自体に必要なものを超える追加のスペースが必要です。
  • 各要素には、要素タイプ自体のスペースのみが必要です(追加のポインターは不要)。
  • 要素を追加するときはいつでも、ベクトル全体にメモリを再割り当てできます。
  • 最後の挿入は一定の償却時間ですが、他の場所への挿入はコストのかかるO(n)です。
  • ベクトルの最後の消去は一定の時間ですが、それ以外の場合はO(n)です。
  • その要素にランダムにアクセスできます。
  • イテレータは、ベクターに要素を追加またはベクターから削除すると無効になります。
  • 要素の配列が必要な場合は、基になる配列に簡単にアクセスできます。

リスト:

  • 不連続なメモリ。
  • 事前に割り当てられたメモリはありません。リスト自体のメモリオーバーヘッドは一定です。
  • 各要素には、リスト内の次の要素と前の要素へのポインタを含む、要素を保持するノード用に追加のスペースが必要です。
  • 要素を追加したからといって、リスト全体にメモリを再割り当てする必要はありません。
  • 挿入と消去は、リストのどこで発生しても安価です。
  • リストとスプライシングを組み合わせるのは安価です。
  • 要素にランダムにアクセスすることはできないため、リスト内の特定の要素にアクセスすると負荷がかかる可能性があります。
  • イテレータは、リストに要素を追加または削除した場合でも有効です。
  • 要素の配列が必要な場合は、基になる配列がないため、新しい配列を作成してすべてに追加する必要があります。

一般に、使用しているシーケンシャルコンテナーの種類が気にならない場合はベクターを使用しますが、コンテナーの最後以外の場所への挿入や消去を多数行っている場合は、リストを使用する。または、ランダムアクセスが必要な場合は、リストではなくベクトルが必要になります。それ以外にも、アプリケーションに基づいてどちらかが必要になる場合もありますが、一般的には、これらが適切なガイドラインです。


2
また、フリーストアからの割り当ては無料ではありません。:)ベクトルに新しいアイテムを追加すると、O(log n)の無料ストア割り当てが実行されますが、呼び出しreserve()てO(1)に減らすことができます。リストに新しいアイテムを追加する(つまり、それらをスプライスしない)と、O(n)フリーストアの割り当てが実行されます。
bk1e 2010

7
もう1つの考慮事項は、list要素を消去するときにメモリを解放しますが、解放vectorしないことです。トリックvectorを使用しない限り、サイズを小さくしてもAの容量は減少しませんswap()
bk1e 2010

@ bk1e:私は本当にreserve()とswap()であなたのトリックを知りたいです:)
Dzung Nguyen

2
@nXqd:ベクトルにN個の要素を追加する必要がある場合は、v.reserve(v.size()+ N)を呼び出して、1つの空きストア割り当てのみを実行します。swap()トリックはここにあります:stackoverflow.com/questions/253157/how-to-downsize-stdvector
bk1e

1
@simplenameいいえ。そのとおりです。vectorは、現在vectorにある要素のスペースを超えて追加のスペースを割り当てます。次に、その追加の容量を使用してベクターを成長させ、その成長が償却O(1)になるようにします。
ジョナサンMデイビス

35

要素を頻繁に挿入する必要がない場合は、ベクトルの方が効率的です。リストよりもCPUキャッシュの局所性がはるかに優れています。言い換えれば、1要素にアクセスすることになり非常に次の要素がキャッシュに存在すると遅いRAMを読まなく取得できる可能性が高いです。


32

ここでのほとんどの回答は、1つの重要な詳細を欠いています。

容器に何を入れておきたいですか?

それはの集まりである場合intの、そしてstd::listあなたは再割り当てすることができる場合に関係なく、あなただけなど、フロントから削除リストが遅くトラバースにあり、すべての挿入があなたのアロケータとの相互作用がかかり、すべてのシナリオに失うことになります。list<int>ビートする例を準備するのは非常に難しいでしょうvector<int>。そしてそれでも、deque<int>リストの使用を正当化するのではなく、より良いか近いかもしれません。これはより大きなメモリのオーバーヘッドを持ちます。

ただし、大規模で見苦しいデータのブロブを処理している場合(およびそれらのいくつか)、挿入時に割り当てを行いたくない場合、再割り当てが原因でコピーが失敗することになります。その場合は、list<UglyBlob>よりvector<UglyBlob>

それでも、vector<UglyBlob*>またはに切り替えるとvector<shared_ptr<UglyBlob> >、再び-リストは遅れます。

したがって、アクセスパターン、ターゲットエレメント数などは依然として比較に影響しますが、私の見解では、エレメントサイズ-コピーのコストなどです。


1
Meyersの "Effective STL"を読んだときのもう1つの反射:の特異な特性はO(1)list<T>の可能性です。一定時間のスプライシングが必要な場合は、listを選択することもできます;)splice
Tomasz

+1-それは必ずしもである必要はありませんUglyBlob-少数の文字列メンバーのみを含むオブジェクトでさえ、コピーするのは非常に高くつくため、再割り当てにコストかかります。またvector、サイズが数十バイトの保持オブジェクトの指数関数的増加によって引き起こされる可能性のあるスペースオーバーヘッドを無視しないでください(reserve事前にできない場合)。
Martin Ba

vector<smart_ptr<Large>>vsと同様list<Large>-要素へのランダムアクセスが必要な場合、それvectorは理にかなっています。ランダムアクセスが必要ない場合、listはより簡単に見え、同等に実行する必要があります。
Martin Ba

19

std :: listの特別な機能の1つはスプライシングです(リストの一部または全体を別のリストにリンクまたは移動する)。

または、コンテンツのコピーに非常に費用がかかる場合。このような場合、たとえば、コレクションをリストでソートする方が安上がりです。

また、コレクションが小さい場合(および内容をコピーするのに特にコストがかからない場合)、どこかに挿入および消去しても、ベクターはリストよりも優れている場合があります。リストは各ノードを個別に割り当てますが、いくつかの単純なオブジェクトを移動するよりもはるかにコストがかかる可能性があります。

非常に難しいルールはないと思います。これは、コンテナの主な目的、およびコンテナの大きさや含まれる型に依存します。内容は単一の連続したブロックとして割り当てられるため、ベクトルは一般にリストよりも優先されます(基本的には動的に割り当てられる配列であり、ほとんどの状況で配列は一連のものを保持する最も効率的な方法です)。


1
+1。スプライシングは見過ごされがちですが、残念ながら、希望どおりの一定時間ではありません。:(((list :: sizeが定数時間の場合はできません。)

list :: sizeは、まさにこの理由で線形であることは確かです。
UncleBens 2010

1
@ロジャー:list::size必ずしも一定の時間ではありません。stackoverflow.com/questions/228908/is-listsize-really-onおよびgcc.gnu.org/ml/libstdc++/2005-11/msg00219.html
Potatoswatter

@Potatoswatter:標準が曖昧であり、「準拠」実装に依存できないため、問題がさらに深刻になります。文字通りstdlibを避けて、移植可能で信頼できる保証を得なければなりません。

@ロジャー:はい、残念ながら。私の現在のプロジェクトが強く、スプライス操作に依存しており、その構造は、N3000シーケンスで、さらに残念なことに、ほぼまっすぐC.あるsplice線形複雑度として指定されている別のリストの間でsize、具体的に一定です。そのため、反復する初心者に対応するためにsize、アルゴリズムのクラス全体は、STL、または「準拠」コンテナ期間の対象外です。
Potatoswatter

13

私のクラスの生徒は、ベクターを使用する方が効果的である場合は説明できないようですが、リストを使用するようにアドバイスすると、とても幸せそうです。

これは私がそれを理解する方法です

リスト:各アイテムには次または前の要素へのアドレスが含まれているため、この機能を使用すると、アイテムがランダム化され、並べ替えられていなくても順序は変わりません。メモリが断片化されている場合は効率的です。ただし、これには他の非常に大きな利点もあります。項目を簡単に挿入/削除できるため、必要なのは一部のポインターを変更することだけです。欠点:ランダムな単一のアイテムを読み取るには、正しい住所が見つかるまでアイテム間をジャンプする必要があります。

ベクトル:ベクトルを使用する場合、メモリは通常の配列のようにはるかに編成されます。各n番目のアイテムは(n-1)番目のアイテムの直後と(n + 1)番目のアイテムの前に格納されます。リストよりも優れているのはなぜですか?ランダムアクセスが高速になるためです。方法は次のとおりです。ベクトル内のアイテムのサイズがわかっていて、それらがメモリ内で連続している場合、n番目のアイテムがどこにあるかを簡単に予測できます。必要なものを読むためにリストのすべての項目を参照する必要はありません。ベクトルを使用すると、直接読み取ることができますが、できないリストを使用できます。一方、ベクトル配列を変更したり、値を変更したりすると、はるかに時間がかかります。

メモリ内で追加/削除できるオブジェクトを追跡するには、リストがより適切です。大量の単一アイテムから要素にアクセスする場合は、ベクターがより適切です。

リストがどのように最適化されるのかはわかりませんが、高速な読み取りアクセスが必要な場合はベクターを使用する必要があることを知っておく必要があります。STLがリストを固定する速度は、ベクターよりも読み取りアクセスの速度が遅くなるためです。


「ベクトル配列を変更するか、値を変更するのははるかに遅い」-読んだように、これは、低レベルで隣接する性質のために、ベクトルが良好なパフォーマンスを発揮する傾向があることについて直前に言ったことと矛盾しているようです。サイズを変更することで原因の再割り当てが遅くなる可能性があることを意味しましたか?その後同意しましたが、使用できる場合は、これらの問題を回避します。vectorreserve()
underscore_d

10

基本的に、ベクトルは自動メモリ管理を備えた配列です。データはメモリ内で連続しています。途中でデータを挿入しようとすると、コストがかかります。

リストでは、データは無関係なメモリ位置に保存されます。真ん中に挿入することは、新しいデータのための場所を作るためにデータの一部をコピーすることを含みません。

より具体的にあなたの質問に答えるために、このページを引用します

ベクトルは通常、要素にアクセスし、シーケンスの最後に要素を追加または削除するのに最も効率的です。要素を末尾以外の位置に挿入または削除する操作の場合、デックやリストよりもパフォーマンスが低く、リストよりも反復子と参照の一貫性が低くなります。


9

イテレータを無効にすることはできません。


2
ただし、aへの永続的な参照でdeque十分かどうかを尋ねずに、反復子に関するその結論に決してジャンプしないでください
Potatoswatter

8

シーケンスの途中で多くの挿入または削除がある場合。例えば、メモリマネージャ。


したがって、それらの違いは機能上の問題ではなく、効率のみです。
スカイドア2010

もちろん、どちらも一連の要素をモデル化しています。@dirkgentlyによって言及されているように、使用法には小さな違いがありますが、「よく行われる」操作の複雑さを見て、選択するシーケンスを決定する必要があります(@Martinの回答)。
AraK

@skydoor-いくつかの機能的な違いがあります。たとえば、ベクトルのみがランダムアクセスをサポートします(つまり、インデックスを付けることができます)。
マヌエル

2
@skydoor:効率はパフォーマンスにつながります。パフォーマンスが低いと、機能が損なわれる可能性があります。結局のところ、パフォーマンスはC ++の利点です。
Potatoswatter 2010

4

イテレータの有効性を維持することは、リストを使用する1つの理由です。もう1つは、アイテムをプッシュするときにベクトルを再割り当てしたくない場合です。これは、reserve()のインテリジェントな使用によって管理できますが、場合によっては、リストを使用する方が簡単または実現可能です。


4

コンテナ間でオブジェクトを移動する場合は、を使用できますlist::splice

たとえば、グラフ分割アルゴリズムは、一定数のオブジェクトを、増加する数のコンテナ間で再帰的に分割できます。オブジェクトは一度初期化する必要があり、常にメモリ内の同じ場所に残ります。再割り当てを行うよりも、再リンクを行う方がはるかに高速です。

編集:ライブラリーがC ++ 0xを実装する準備をするにつれて、サブシーケンスをリストにスプライスする一般的なケースは、シーケンスの長さに比例して複雑になります。これは、splice(今)シーケンス内の要素の数をカウントするためにシーケンスを反復する必要があるためです。(リストはそのサイズを記録する必要があるためです。)リストを数え、再リンクするだけでも、他のどの方法よりも速く、リスト全体または単一の要素を接合することは、一定の複雑さを持つ特殊なケースです。ただし、スプライスする長いシーケンスがある場合は、古くて古くて非準拠のより良いコンテナーを探し回る必要があるかもしれません。


2

list使用する必要がある唯一の難しいルールは、コンテナーの要素へのポインターを配布する必要がある場合です。

とは異なりvector、要素のメモリは再割り当てされません。それが可能であれば、未使用のメモリへのポインタがある可能性があります。これは、せいぜい大きなno-noで、最悪の場合はa SEGFAULTです。

(技術的には、vector*_ptrも、希望の仕事が、その場合には、あなたがエミュレートされているlistことはただ意味をですので。)

他のソフトルールは、コンテナーの中央に要素を挿入することで起こり得るパフォーマンスの問題に関係しているため、list推奨されます。


2

シンプルに
する-結局のところ、C ++でのコンテナーの選択が混乱しているときは、このフローチャートの画像を使用してください(私に感謝します):- ここに画像の説明を入力してください

ベクター-
1.ベクトルは伝染性メモリに基づいています
。2.ベクトルが小さなデータセットのために行くための方法です
。3.ベクトルをデータ・セットに横断しながら、最速の実行
4.ベクター挿入の削除は、巨大なデータセットに遅いですが、高速で非常に小さいため


リスト-1 .リストはヒープメモリに基づいています
2.リストは非常に巨大なデータセットを処理する方法です
3.リストは小さなデータセットのトラバースでは比較的低速ですが、巨大なデータセットで
は高速です4.リストの挿入削除は高速です巨大なデータセットですが、小さいデータセットでは遅くなります


1

リストはstlの二重LinkedListのラッパーにすぎないため、d-linklistから期待できる機能、つまりO(1)の挿入と削除を提供します。ベクトルは伝染性のデータシーケンスですが、動的な配列のように機能します。


0

ベクトルリストの場合、私に突き出る主な違いは次のとおりです。

ベクター

  • ベクトルはその要素を連続したメモリに格納します。したがって、ベクター内でランダムアクセスが可能です。これは、ベクターの要素にアクセスするためにベースアドレスとアイテムインデックスを単純に乗算できるため、ベクターの要素へのアクセスが非常に高速であることを意味します。実際、この目的にはO(1)または一定の時間しかかかりません。

  • ベクトルは基本的に配列をラップするため、要素をベクトル(動的配列)に挿入するたびに、メモリの新しい隣接ブロックを見つけてサイズを変更し、時間のかかる新しい要素に対応する必要があります。

  • その中に他の要素へのポインタを格納するために余分なメモリを消費しません。

リスト

  • リストはその要素を非連続メモリに格納します。したがって、リスト内でのランダムアクセスは不可能です。つまり、その要素にアクセスするには、ポインターを使用して、ベクターに比べて遅いリストを走査する必要があります。これにはO(n)またはO(1)より遅い線形時間がかかります。

  • リストは不連続なメモリを使用するため、リスト内に要素を挿入するのにかかる時間は、メモリの再割り当てが回避されるため、対応するベクトルの場合よりもはるかに効率的です。

  • 特定の要素の前後の要素へのポインタを格納するために、余分なメモリを消費します。

したがって、これらの違いを念頭に置いて、通常はメモリ、頻繁なランダムアクセス挿入を考慮して、特定のシナリオでベクトルとリストの勝者を決定します。


0

リストは二重にリンクされたリストなので、要素の挿入と削除は簡単です。ポインターをいくつか変更するだけでよいのに対し、ベクターでは、要素を途中に挿入する場合は、各要素を1つのインデックスでシフトする必要があります。また、ベクターのサイズがいっぱいの場合は、まずそのサイズを増やす必要があります。だから、それは高価な操作です。したがって、そのような場合に挿入および削除操作をより頻繁に実行する必要がある場合は、リストを使用する必要があります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.