リストを反復する方が、インデックスを付けるよりも速いのはなぜですか?


125

それが言うADTリストのJavaドキュメントを読む:

Listインターフェースは、リスト要素への位置(インデックス)アクセスのための4つのメソッドを提供します。リスト(Java配列のような)はゼロベースです。これらの操作は、一部の実装(LinkedListクラスなど)のインデックス値に比例して時間内に実行される場合があることに注意してください。したがって、呼び出し元が実装を知らない場合は、リストの要素を反復処理する方が、インデックスを作成するよりも通常は望ましい方法です。

これはどういう意味ですか?描かれた結論がわかりません。


12
この一般的なケースを理解するのに役立つ別の例は、Joel Spolskyの記事「Back to Basics」です -「Shlemiel the painter's algorithm」を検索してください。
CVn

回答:


211

リンクされたリストでは、各要素には次の要素へのポインタがあります。

head -> item1 -> item2 -> item3 -> etc.

にアクセスするにはitem3、直接ジャンプできないため、item3に到達するまですべてのノードを先頭から歩く必要があることが明確にわかります。

したがって、各要素の値を出力したい場合は、次のように記述します。

for(int i = 0; i < 4; i++) {
    System.out.println(list.get(i));
}

これはどうなりますか:

head -> print head
head -> item1 -> print item1
head -> item1 -> item2 -> print item2
head -> item1 -> item2 -> item3 print item3

これは、インデックスを作成するたびにリストの先頭から再開され、すべてのアイテムを通過するため、非常に非効率的です。これは、あなたの複雑さが事実上O(N^2)リストをたどるだけであることを意味します!

代わりに私がこれをした場合:

for(String s: list) {
    System.out.println(s);
}

次に何が起こるのですか:

head -> print head -> item1 -> print item1 -> item2 -> print item2 etc.

すべて単一のトラバーサル、つまりですO(N)

次に、の他の実装でListあるに移動します。これはArrayList、単純な配列によってサポートされています。その場合、配列は隣接しているため、任意の位置にランダムにジャンプできるため、上記のトラバーサルは両方とも同等です。


29
軽微な注意:インデックスがリストの後半にある場合、LinkedListはリストの最後から検索しますが、それでも根本的な非効率は変わりません。それはそれを少しだけ問題にします。
Joachim Sauer、2012年

8
これはひどく非効率的です。LinkedListが大きい場合は、はい、小さい場合は速く動作します REVERSE_THRESHOLD。18に設定さjava.util.Collectionsれているので、コメントなしで非常に賛成された回答が表示されるのはおかしいです。
bestss

1
@DanDiplo、構造がリンクされている場合、はい、それは成り立ちます。ただし、LinkedS構造の使用は小さな謎です。それらはほとんどの場合、アレイでサポートされているものよりもはるかにパフォーマンスが悪い(追加のメモリフットプリント、GCに適さない、ひどい局所性)。C#の標準リストは配列でサポートされています。
bestsss

3
マイナー通告:あなたは(イテレータ/ foreachの対インデックス付け)を使用すべき反復タイプをチェックしたい場合、あなたは常にテストすることができますリスト実装している場合ランダム・(マーカーインタフェース):List l = unknownList(); if (l instanceof RandomAccess) /* do indexed loop */ else /* use iterator/foreach */
afk5min

1
@ KK_07k11A0585:実際、最初の例の拡張forループは、2番目の例のようにイテレーターにコンパイルされているため、同等です。
チューダー

35

答えはここに暗示されています:

これらの操作は、一部の実装(LinkedListクラスなど)のインデックス値に比例して時間内に実行される場合があることに注意してください。

リンクリストには固有のインデックスはありません。呼び出しで.get(x)は、最初のエントリを見つけて.next()x-1回(O(n)または線形時間アクセスの場合)を呼び出すためにリスト実装が必要になります。ここで、配列付きリストはbackingarray[x]、O(1)または一定時間でインデックスを作成できます。

JavaDocをLinkedList見ると、コメントが表示されます。

すべての操作は、二重にリンクされたリストで予想されるとおりに実行されます。リストにインデックスを付ける操作は、指定されたインデックスに近い方から、リストの最初または最後からトラバースします。

一方のためのJavadocはArrayList対応してい

Listインターフェースのサイズ変更可能な配列の実装。オプションのリスト操作をすべて実装し、nullを含むすべての要素を許可します。Listインターフェースの実装に加えて、このクラスは、リストを格納するために内部的に使用される配列のサイズを操作するメソッドを提供します。(このクラスは、非同期である点を除いて、Vectorとほぼ同じです。)

sizeisEmptygetsetiterator、およびlistIterator操作は、一定の時間で実行されます。追加操作は一定の償却時間で実行されます。つまり、n個の要素を追加するにはO(n)時間必要です。他のすべての操作は線形時間で(大まかに言えば)実行されます。定数はLinkedList実装の場合と比較して低くなっています。

「JavaコレクションフレームワークのBig-Oの概要」というタイトル関連質問には、このリソース「JavaコレクションJDK6」を指す回答があり、参考になるかもしれません。


7

受け入れられた答えは確かに正しいですが、マイナーな欠陥を指摘するかもしれません。チューダーの引用:

次に、Listの他の実装であるArrayListに進みます。これは、単純な配列によってサポートされています。その場合、配列は連続しているため、任意の位置へのランダムジャンプが可能になるため、上記のトラバーサルは両方とも同等です。

これは完全に真実ではありません。真実は、

ArrayListを使用すると、手書きのカウントループが約3倍速くなります

出典:Designing for Performance、GoogleのAndroidドキュメント

手書きループはインデックス付きの反復を指すことに注意してください。拡張されたforループで使用される反復子が原因であると思います。これは、隣接する配列に裏打ちされた構造で、ペナルティの小さなパフォーマンスを生成します。私はまた、これがVectorクラスに当てはまるのではないかと疑っています。

私のルールは、可能な限り拡張されたforループを使用し、パフォーマンスに本当に関心がある場合は、ArrayListsまたはVectorsのいずれかにのみインデックス付き反復を使用することです。ほとんどの場合、これを無視することもできます。コンパイラがバックグラウンドで最適化している可能性があります。

Androidでの開発のコンテキストでは、ArrayListの両方のトラバーサルが必ずしも同等でないことを指摘したいだけです。ただ考えるための食べ物。


ソースはAnndroidのみです。これは他のJVMにも当てはまりますか?
Matsemann

tbhは完全にはわかりませんが、ほとんどの場合、拡張forループの使用がデフォルトです。
Dhruv Gairola

配列を使用するデータ構造にアクセスするときにすべてのイテレータロジックを削除する方が速く機能します。3倍高速かどうかはわかりませんが、確かに高速です。
Setzer22 2013

7

など、ルックアップのオフセットを使用してリストを反復処理することi、ペインターのアルゴリズムであるShlemielに似ています

シュレミエルは通りの画家としての仕事を得て、道の真ん中に点線を描きます。最初の日、彼はペンキの缶を道路に出し、道路の300ヤードをフィニッシュします。「それはかなり良いことです!」彼は上司に言いました、「あなたは速い労働者です!」そして彼にコペックを支払う。

翌日、Shlemielは150ヤードしか完了しません。「まあ、それは昨日ほど良くないが、あなたはまだ速い労働者だ。150ヤードは立派だ」と彼にコペックを払った。

翌日、Shlemielは道路の30ヤードをペイントします。「たった30!」彼の上司を叫ぶ。「それは受け入れられません。初日に、あなたはその10倍の仕事をしました!何が起こっているのですか?」

「どうしようもない」とシュレミエルは言う。「毎日私は絵の具の缶からどんどん遠ざかっています!」

ソース

この小さなストーリーは、内部で何が起こっているのか、なぜそれがそれほど非効率的であるのかを理解しやすくするかもしれません。


4

aのi番目の要素を見つけるにはLinkedList、実装はi番目までのすべての要素を通過します。

そう

for(int i = 0; i < list.length ; i++ ) {
    Object something = list.get(i); //Slow for LinkedList
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.