私は多くのリストと配列を使用していますが、配列リストがリンクリストよりも簡単ではないにしても簡単に使用できないシナリオにまだ出会っていません。リンクされたリストが特に優れている場合の例を誰かが教えてくれることを望んでいました。
リンクリストは、配列よりも望ましい:
リストからの一定時間の挿入/削除が必要です(時間の予測可能性が絶対的に重要なリアルタイムコンピューティングなど)
リストに含まれるアイテムの数がわかりません。配列では、配列が大きくなりすぎた場合にメモリを再宣言してコピーする必要がある場合があります
要素にランダムにアクセスする必要はありません
リストの中央にアイテムを挿入できるようにしたい(優先キューなど)
次の場合に配列が望ましい
要素へのインデックス付き/ランダムアクセスが必要
配列に適切な量のメモリを割り当てることができるように、事前に配列内の要素の数を知っている
すべての要素を順番に繰り返すには速度が必要です。配列でポインター演算を使用して各要素にアクセスできますが、リンクリスト内の各要素のポインターに基づいてノードを検索する必要があるため、ページフォールトが発生し、パフォーマンスが低下する可能性があります。
メモリが心配です。いっぱいになった配列は、リンクリストよりもメモリを消費しません。配列内の各要素は単なるデータです。各リンクリストノードには、データと、リンクリスト内の他の要素への1つ(または複数)のポインターが必要です。
配列リスト(.Netのリストなど)は配列の利点を提供しますが、動的にリソースを割り当てるので、リストサイズを気にする必要がなく、どんなインデックスでも項目を削除することができます。要素をシャッフルします。パフォーマンス面では、arraylistsはraw配列よりも低速です。
配列にはO(1)ランダムアクセスがありますが、ものを追加したり削除したりするのは本当に高価です。
リンクリストは、どこにでもアイテムを追加または削除したり、反復したりするのに非常に安価ですが、ランダムアクセスはO(n)です。
Algorithm ArrayList LinkedList
seek front O(1) O(1)
seek back O(1) O(1)
seek to index O(1) O(N)
insert at front O(N) O(1)
insert at back O(1) O(1)
insert after an item O(N) O(1)
ArrayListsは、1回だけの読み取りまたは多数のアペンダーには適していますが、前面または中央からの追加/削除には適していません。
他の回答に追加するために、ほとんどの配列リストの実装は、リストの最後に余分な容量を予約して、O(1)時間で新しい要素をリストの最後に追加できるようにします。配列リストの容量を超えると、新しいより大きな配列が内部的に割り当てられ、すべての古い要素がコピーされます。通常、新しい配列は古い配列の2倍のサイズです。つまり、平均、配列リストの最後に新しい要素を追加することは、これらの実装ではO(1)操作です。したがって、要素の数が事前にわからなくても、最後に要素を追加する限り、配列リストは要素を追加するためのリンクリストよりも高速です。明らかに、配列リストの任意の場所に新しい要素を挿入することは、依然としてO(n)操作です。
配列リストの要素へのアクセスは、アクセスがシーケンシャルであっても、リンクリストよりも高速です。これは、配列要素が連続したメモリに格納され、簡単にキャッシュできるためです。リンクリストノードは、多くの異なるページに散在する可能性があります。
任意の場所でアイテムを挿入または削除することがわかっている場合にのみ、リンクリストを使用することをお勧めします。配列リストは、他のほとんどすべての場合に高速になります。
リストの利点は、アイテムを中央に挿入する必要があり、配列のサイズを変更したり、物事を移動したりしたくない場合に表示されます。
これは通常そうではないという点で正しいです。そのような非常に具体的なケースがいくつかありましたが、多すぎません。
これらは、最も一般的に使用されるCollectionの実装です。
ArrayList:
最後に挿入/削除しますO(1)最悪の場合O(n)
中央のO(n)で挿入/削除
任意の位置O(1)を取得します
LinkedList:
任意の位置で挿入/削除O(1)(要素への参照がある場合は注意してください)
中央で取得O(n)
最初または最後の要素O(1)を取得
ベクトル:使用しないでください。 ArrayListに似ていますが、すべてのメソッドが同期されている古い実装です。マルチスレッド環境での共有リストの正しいアプローチではありません。
HashMap
o(1)のキーによる挿入/削除/取得
TreeSet O(log N)に挿入/削除/含む
HashSet O(1)に挿入/削除/含む/サイズ
それはすべて、反復中に実行する操作の種類に依存し、すべてのデータ構造は時間とメモリの間でトレードオフがあり、必要に応じて適切なDSを選択する必要があります。そのため、LinkedListが配列よりも高速である場合や、その逆の場合があります。データ構造に関する3つの基本操作を検討してください。
Arrayはインデックスベースのデータ構造であるため、array.get(index)はO(1)時間かかりますが、linkedlistはインデックスDSではないので、indexまで移動する必要があります。 index <= n、nはリンクリストのサイズです。したがって、要素のランダムアクセスがある場合、配列はリンクリストより高速です。
Q。だから、この背後にある美しさは何ですか?
配列は連続したメモリブロックであるため、最初のアクセス時にそれらの大きなチャンクがキャッシュにロードされます。これにより、配列の残りの要素に比較的迅速にアクセスできます。ミス、キャッシュの局所性とは、キャッシュ内にある操作を指すため、メモリ内と比較してはるかに高速に実行されます。リンクリストは必ずしもメモリの連続ブロックにあるわけではありませんが、リスト内で順番に表示されるアイテムがメモリ内で互いに近くに実際に配置されるという保証はありません。リンクリスト要素のアクセスごとにメモリから読み取る必要があるため、キャッシュミスが多くなり、アクセスに時間がかかり、パフォーマンスが低下するため、検索と呼ばれるより多くのランダムアクセス操作を行う場合、配列は以下で説明するように高速になります。
挿入はLinkedList(Java)でのO(1)操作であるため、配列と比較してLinkedListでは簡単で高速です。配列がいっぱいの場合を考慮し、配列の場合は新しい配列に内容をコピーする必要があります最悪の場合O(n)のArrayListに要素を挿入するフルになりますが、リンクされたリストの場合、配列の最後以外の場所に何かを挿入するとArrayListもそのインデックスを更新する必要がありますサイズを変更する必要はありません。ポインタを更新するだけです。
これは挿入のように機能し、LinkedListでは配列よりも優れています。
実際には、メモリの局所性は、実際の処理においてパフォーマンスに大きな影響を与えます。
「ビッグデータ」処理でのディスクストリーミングの使用がランダムアクセスと比較して増加していることは、これを中心にアプリケーションを構築することで、大規模でパフォーマンスを劇的に改善できることを示しています。
配列に順番にアクセスする方法があれば、それが圧倒的に最高のパフォーマンスです。パフォーマンスが重要な場合は、これを目標として設計することを少なくとも検討する必要があります。
ベンチマークを行ったところ、リストクラスは実際にランダム挿入のLinkedListよりも高速であることがわかりました。
using System;
using System.Collections.Generic;
using System.Diagnostics;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
int count = 20000;
Random Rand = new Random(12345);
Stopwatch watch = Stopwatch.StartNew();
LinkedList<int> ll = new LinkedList<int>();
ll.AddLast(0);
for (int i = 1; i < count; i++)
{
ll.AddBefore(ll.Find(Rand.Next(i)),i);
}
Console.WriteLine("LinkedList/Random Add: {0}ms", watch.ElapsedMilliseconds);
watch = Stopwatch.StartNew();
List<int> list = new List<int>();
list.Add(0);
for (int i = 1; i < count; i++)
{
list.Insert(list.IndexOf(Rand.Next(i)), i);
}
Console.WriteLine("List/Random Add: {0}ms", watch.ElapsedMilliseconds);
Console.ReadLine();
}
}
}
リンクリストには900ミリ秒、リストクラスには100ミリ秒かかります。
後続の整数のリストを作成します。新しい整数はそれぞれ、リストに既にある乱数の後に挿入されます。たぶん、Listクラスは単なる配列よりも優れたものを使用します。
主な違いは、リストの一番上にあるものを頻繁に挿入または削除する必要があるかどうかだと思います。
配列の場合、リストの最上部から何かを削除すると、配列要素のすべてのインデックスをシフトする必要があるため、複雑さはo(n)になります。
リンクリストの場合、ノードを作成し、ヘッドを再割り当てし、前のヘッドとしてnextに参照を割り当てるだけでよいため、o(1)です。
リストの最後で頻繁に挿入または削除する場合、複雑さはo(1)であり、インデックスの再作成は必要ないため、配列が望ましいですが、リンクリストの場合はo(n)になります。ヘッドから最後のノードに移動する必要があります。
リンクリストと配列の両方での検索は、おそらくバイナリ検索を使用しているため、o(log n)になると思います。
うーん、Arraylistは次のような場合に使用できます。
たとえば、連絡先リストのすべての要素をインポートしてアクセスする必要があります(サイズは不明です)
1)上記で説明したように、挿入および削除操作は、ArrayList(O(n))と比較してLinkedListで良好なパフォーマンス(O(1))を提供します。したがって、アプリケーションで頻繁に追加および削除する必要がある場合は、LinkedListが最適です。
2)検索(メソッドの取得)操作はArraylist(O(1))では高速ですが、LinkedList(O(n))では高速ではないため、追加と削除の操作が少なく、検索操作の要件が多い場合は、ArrayListが最適です。
配列の基数ソートおよび多項式演算にリンクリストを使用します。