ListとLinkedListを使用する場合


回答:


107

編集する

この回答へのコメントを読んでください。人々は私が適切なテストをしなかったと主張します。これは受け入れられる答えではないことに同意します。学んでいる間、私はいくつかのテストを行い、それらを共有したいと思いました。

元の答え...

興味深い結果が見つかりました:

// Temporary class to show the example
class Temp
{
    public decimal A, B, C, D;

    public Temp(decimal a, decimal b, decimal c, decimal d)
    {
        A = a;            B = b;            C = c;            D = d;
    }
}

リンクされたリスト(3.9秒)

        LinkedList<Temp> list = new LinkedList<Temp>();

        for (var i = 0; i < 12345678; i++)
        {
            var a = new Temp(i, i, i, i);
            list.AddLast(a);
        }

        decimal sum = 0;
        foreach (var item in list)
            sum += item.A;

リスト(2.4秒)

        List<Temp> list = new List<Temp>(); // 2.4 seconds

        for (var i = 0; i < 12345678; i++)
        {
            var a = new Temp(i, i, i, i);
            list.Add(a);
        }

        decimal sum = 0;
        foreach (var item in list)
            sum += item.A;

本質的にデータにアクセスするだけでも、かなり遅くなります!! linkedListを使用しないでください。




これは、多数の挿入を実行する別の比較です(リストの中央にアイテムを挿入する予定です)。

リンクリスト(51秒)

        LinkedList<Temp> list = new LinkedList<Temp>();

        for (var i = 0; i < 123456; i++)
        {
            var a = new Temp(i, i, i, i);

            list.AddLast(a);
            var curNode = list.First;

            for (var k = 0; k < i/2; k++) // In order to insert a node at the middle of the list we need to find it
                curNode = curNode.Next;

            list.AddAfter(curNode, a); // Insert it after
        }

        decimal sum = 0;
        foreach (var item in list)
            sum += item.A;

リスト(7.26秒)

        List<Temp> list = new List<Temp>();

        for (var i = 0; i < 123456; i++)
        {
            var a = new Temp(i, i, i, i);

            list.Insert(i / 2, a);
        }

        decimal sum = 0;
        foreach (var item in list)
            sum += item.A;

挿入する場所の参照を持つリンクリスト(.04秒)

        list.AddLast(new Temp(1,1,1,1));
        var referenceNode = list.First;

        for (var i = 0; i < 123456; i++)
        {
            var a = new Temp(i, i, i, i);

            list.AddLast(a);
            list.AddBefore(referenceNode, a);
        }

        decimal sum = 0;
        foreach (var item in list)
            sum += item.A;

したがって、複数のアイテムを挿入する予定があり、どこかにアイテムを挿入する予定の参照がある場合にのみ、リンクリストを使用します。たくさんのアイテムを挿入する必要があるからといって、挿入したい場所を探すのに時間がかかるので、速くはなりません。


99
リストよりもLinkedListには1つの利点があります(これは.net固有です)。リストは内部配列によってサポートされているため、1つの連続したブロックに割り当てられます。割り当てられたブロックのサイズが85000バイトを超える場合、そのブロックは、圧縮不可能な世代であるラージオブジェクトヒープに割り当てられます。サイズによっては、ヒープの断片化、つまりメモリリークの穏やかな形につながる可能性があります。
JerKimball 2012

35
(基本的に前の例で行っているように)大量に追加する場合、または最初のエントリを削除する場合、検索や移動/コピーを行う必要がないため、リンクリストはほとんど常に非常に高速になります。リストでは、新しいアイテムに対応するためにすべてを1つ上の位置に移動し、O(N)操作を付加する必要があります。
cHao

6
なぜlist.AddLast(a);最後の2つのLinkedListの例のループ内なのですか?list.AddLast(new Temp(1,1,1,1));最後から2番目のLinkedListの場合と同様に、ループの前に一度実行しますが、ループ自体にTempオブジェクトを2倍追加しているように見えます(私には)。(そして、私がテストアプリ自分自身を再確認するとき、確かに、LinkedListの2倍です。)
ruffin

7
この回答に反対票を投じました。1)I say never use a linkedList.後の投稿で明らかになるように、あなたの一般的なアドバイスに欠陥があります。あなたはそれを編集したいかもしれません。2)タイミングは?インスタンス化、追加、および列挙をすべて1つのステップで実行しますか?ほとんどの場合、インスタンス化と列挙は、PPLが心配することではなく、それらは1つのタイムステップです。具体的には、挿入と追加のタイミングを調整すると、より良いアイデアが得られます。3)最も重要なのは、必要以上にリンクリストに追加することです。これは間違った比較です。リンクリストに関する誤った考えを広めます。
nawfal 14

47
申し訳ありませんが、この答えは本当に悪いです。この答えは聞かないでください。簡単に言うと、配列に基づくリストの実装は、挿入ごとに配列のサイズを変更するのに十分なほど愚かであると考えるのは完全に不備があります。リンク付きリストは、トラバース時と両端で挿入するときに、配列ベースのリストよりも遅くなります。これは、新しいオブジェクトを作成するだけでよいのに対し、配列ベースのリストは(明らかに両方向で)バッファーを使用するためです。(不十分に行われた)ベンチマークはそれを正確に示しています。答えはリンクされたリストが望ましいケースを完全にチェックすることに失敗します!
mafu 2014年

277

ほとんどの場合、List<T>より便利です。LinkedList<T>リストの中央でアイテムを追加/削除する場合のコストは低くなりますが、リストList<T>最後で追加/削除できるのは安価です。

LinkedList<T>順方向または逆方向のシーケンシャルデータにアクセスしている場合にのみ、最も効率的です。ランダムアクセスは、チェーンを毎回ウォークする必要があるため(インデクサーがないため)、比較的コストがかかります。ただし、a List<T>は本質的に単なる配列(ラッパー付き)であるため、ランダムアクセスで問題ありません。

List<T>また、支援方法の多くを提供しています- FindToArrayなど。ただし、これらはLinkedList<T>.NET 3.5 / C#3.0でも拡張メソッドを介して利用できるため、それほど重要ではありません。


4
List <>とLinkedList <>の利点の1つは、マイクロプロセッサがメモリのキャッシュを実装する方法に関係があるとは考えたこともありませんでした。私は完全には理解していませんが、このブログ記事の執筆者は、「参照の局所性」について多く語っています。これにより、少なくともリンクリストがメモリで多少断片化されている場合は、配列のリンクよりもはるかに速く配列をトラバースできます。 。kjellkod.wordpress.com/2012/02/25/...
RenniePet

@RenniePetリストは動的配列で実装され、配列はメモリの連続したブロックです。
ケーシー

2
Listは動的配列であるため、事前にわかっている場合は、コンストラクターでListの容量を指定することをお勧めします。
Cardin Lee JH 2015

all、array、List <T>、およびLinkedList <T>のC#実装が1つの非常に重要なケースでやや準最適である可能性はありますか?非常に大きなリストが必要で、追加(AddLast)とシーケンシャルトラバーサル(一方向)が必要です。まったく問題ありません:連続したブロックを取得するために配列のサイズ変更をしたくありません(20 GBの配列でも各配列で保証されますか?)、サイズは事前にわかりませんが、ブロックサイズを事前に推測できます(例:100)毎回事前に予約するMB。これは良い実装でしょう。または配列/リストはそれに似ていて、ポイントを逃しましたか?
Philm 2017

1
@Philmは、選択したブロック戦略について独自のシムを作成するようなシナリオです。List<T>そして、T[](すべて1スラブ)あまりにも分厚いもののために失敗し、LinkedList<T>(要素ごとスラブ)あまりにも粒状であるために泣き叫ぶます。
Marc Gravell

212

リンクされたリストをリストと考えるのは少し誤解を招く可能性があります。チェーンのようなものです。実際、.NETでは、LinkedList<T>も実装していませんIList<T>。リンクされたリストには実際のインデックスの概念はありませんが、実際にはありません。確かに、クラスで提供されるメソッドはどれもインデックスを受け入れません。

リンクされたリストは、単一にリンクされていても、二重にリンクされていてもかまいません。これは、チェーン内の各要素が次の要素のみにリンクされているか(単一リンク)、前/次の要素の両方にリンクされているか(二重リンク)を示します。 LinkedList<T>二重にリンクされています。

内部的にList<T>は、配列によってサポートされています。これにより、メモリ内で非常にコンパクトな表現が提供されます。逆に、LinkedList<T>連続する要素間の双方向リンクを格納するために追加のメモリが必要です。そのため、aのメモリフットプリントは、LinkedList<T>一般的にの場合よりも大きくなります(追加操作中のパフォーマンスを向上させるために、未使用の内部配列要素をList<T>含めることList<T>ができるという警告があります)。

また、パフォーマンス特性も異なります。

追加

  • LinkedList<T>.AddLast(item) 一定の時間
  • List<T>.Add(item) 償却一定時間、線形ワーストケース

先頭に追加

  • LinkedList<T>.AddFirst(item) 一定の時間
  • List<T>.Insert(0, item) 線形時間

挿入

  • LinkedList<T>.AddBefore(node, item) 一定の時間
  • LinkedList<T>.AddAfter(node, item) 一定の時間
  • List<T>.Insert(index, item) 線形時間

除去

  • LinkedList<T>.Remove(item) 線形時間
  • LinkedList<T>.Remove(node) 一定の時間
  • List<T>.Remove(item) 線形時間
  • List<T>.RemoveAt(index) 線形時間

カウント

  • LinkedList<T>.Count 一定の時間
  • List<T>.Count 一定の時間

含む

  • LinkedList<T>.Contains(item) 線形時間
  • List<T>.Contains(item) 線形時間

晴れ

  • LinkedList<T>.Clear() 線形時間
  • List<T>.Clear() 線形時間

ご覧のとおり、これらはほとんど同じです。実際には、のAPI LinkedList<T>を使用するのはより面倒であり、その内部ニーズの詳細がコードに流出します。

ただし、リスト内から多くの挿入/削除を行う必要がある場合は、一定の時間が提供されます。 List<T>リスト内の余分なアイテムは挿入/削除後にシャッフルする必要があるため、線形時間を提供します。


2
リンクリストのカウントは一定ですか?それは線形だと思いましたか?
Iain Ballard

10
@Iain、カウントは両方のリストクラスにキャッシュされます。
Drew Noakes、2011年

3
「List <T> .Add(item)logarithmic time」と書いたが、リストの容量が新しい項目を格納できる場合は実際には「一定」であり、リストに十分なスペースがなく新しい場合は「線形」である再割り当てされます。
aStranger 2012

@aStranger、もちろんあなたは正しい。上記で私が何を考えていたのかわからない-おそらく、通常のケースの償却時間は対数であり、そうではない。実際、償却期間は一定です。単純な比較を目的として、私は操作のベスト/ワーストケースには入りませんでした。追加操作は、この詳細を提供するのに十分重要であると思います。回答を編集します。ありがとう。
Drew Noakes 2012

1
@Philm、新しい質問を開始する必要があるかもしれませんが、一度構築されたこのデータ構造をどのように使用するかは言いませんが、100万行を話している場合は、ある種のハイブリッド(リンクされたリスト配列チャンクなど)を使用して、ヒープの断片化を減らし、メモリのオーバーヘッドを減らし、LOH上の単一の巨大なオブジェクトを回避します。
Drew Noakes

118

リンクされたリストは、リストメンバーの非常に高速な挿入または削除を提供します。リンクリストの各メンバーには、リスト内の次のメンバーへのポインターが含まれているため、メンバーを位置iに挿入します。

  • メンバーi-1のポインターを更新して、新しいメンバーを指す
  • 新しいメンバーのポインターをメンバーiを指すように設定する

リンクリストの欠点は、ランダムアクセスができないことです。メンバーにアクセスするには、目的のメンバーが見つかるまでリストを走査する必要があります。


6
リンクされたリストには、前のノードと次のノードを参照するLinkedListNodeを介して、上記で暗黙的に格納されたアイテムごとのオーバーヘッドがあることを追加します。配列ベースのリストとは異なり、リストの格納にメモリの連続したブロックは必要ありません。
ポールコヨーテ2009

3
通常、連続したメモリブロックは使用されませんか?
ジョナサンアレン

7
はい、連続したブロックがランダムアクセスのパフォーマンスとメモリの消費に適していますが、サイズを定期的に変更する必要があるコレクションでは、配列などの構造を新しい場所にコピーする必要がありますが、リンクリストはメモリの管理のみが必要です新しく挿入/削除されたノード。
jpierson

6
非常に大きな配列またはリスト(リストが配列をラップするだけ)で作業する必要があった場合、マシンに十分なメモリがあるように見えても、メモリの問題が発生し始めます。リストは、基になる配列に新しいスペースを割り当てるときに、倍増戦略を使用します。したがって、満杯の1000000要素配列は、2000000要素を持つ新しい配列にコピーされます。この新しい配列は、それを保持するのに十分な大きさの連続したメモリ空間に作成する必要があります。
Andrew

1
私は..私は追加と削除、および1つずつループしてなかった...ここにすべてがリンクリストは、通常のリストよりはるかに優れていた特定のケースを持っていた
ピーター

26

私の以前の答えは十分に正確ではありませんでした。本当にそれは恐ろしいことでした:Dしかし、今では私ははるかに有用で正しい答えを投稿することができます。


追加のテストをいくつか行いました。次のリンクでソースを見つけ、ご自分の環境で再確認できます。https//github.com/ukushu/DataStructuresTestsAndOther.git

短い結果:

  • 配列を使用する必要があります:

    • できるだけ頻繁に。これは高速で、同じ量の情報に対して最小のRAM範囲を使用します。
    • 必要な細胞の正確な数がわかっている場合
    • 配列に保存されたデータが85000 b未満の場合(85000/32 =整数データの場合は2656要素)
    • 必要に応じて高いランダムアクセス速度
  • リストは使用する必要があります:

    • リストの最後にセルを追加する必要がある場合(多くの場合)
    • リストの最初/中央にセルを追加する必要がある場合(頻繁ではない)
    • 配列に保存されたデータが85000 b未満の場合(85000/32 =整数データの場合は2656要素)
    • 必要に応じて高いランダムアクセス速度
  • LinkedListを使用する必要があります:

    • リストの先頭/中央/末尾にセルを追加する必要がある場合(多くの場合)
    • 必要な場合、シーケンシャルアクセスのみ(前方/後方)
    • 大きなアイテムを保存する必要があるが、アイテム数が少ない場合。
    • リンクに追加のメモリを使用するため、大量のアイテムには使用しないでください。

詳細:

введитесюдаописаниеизображения 知っておくと面白い:

  1. LinkedList<T>内部的には.NETのリストではありません。それも実装していませんIList<T>。そしてそれが、インデックスに関連するインデックスとメソッドが存在しない理由です。

  2. LinkedList<T>ノードポインタベースのコレクションです。.NETでは、二重にリンクされた実装にあります。これは、前/次の要素が現在の要素にリンクしていることを意味します。そして、データは断片化されています-RAMの異なる場所に異なるリストオブジェクトを配置できます。またLinkedList<T>List<T>または配列よりも多くのメモリが使用されます。

  3. List<T>.NetはJavaのの代替ですArrayList<T>。これは、これが配列ラッパーであることを意味します。つまり、1つの連続したデータブロックとしてメモリに割り当てられます。割り当てられたデータサイズが85000バイトを超える場合、ラージオブジェクトヒープに移動されます。サイズによっては、ヒープの断片化(メモリリークの穏やかな形式)が発生する可能性があります。しかし同時に、サイズが85000バイト未満の場合、これはメモリ内で非常にコンパクトで高速なアクセス表現を提供します。

  4. ランダムアクセスパフォーマンスとメモリ消費には単一の連続ブロックが推奨されますが、定期的にサイズを変更する必要があるコレクションでは、配列などの構造を新しい場所にコピーする必要がありますが、リンクリストは新しく挿入されたメモリのみを管理する必要があります/ deletedノード。


1
質問:「配列に保存されたデータ<または> 85.000バイト」とは、配列/リスト要素ごとのデータを意味しますか?..あなたは、配列全体のデータサイズを意味することを理解できる
Philm

メモリ内に順番に配置された配列要素。つまり、アレイごとです。テーブルの間違いについては知っていますが、後で修正します:)(願っています...)
Andrew

リストの挿入が遅い場合、リストのターンアラウンド(挿入/削除の数が多い)が多い場合、メモリは削除されたスペースによって占有され、保持されている場合、「再」挿入が速くなりますか?
ロブ

18

ListとLinkedListの違いは、基礎となる実装にあります。リストは配列ベースのコレクション(ArrayList)です。LinkedListはノードポインターベースのコレクション(LinkedListNode)です。APIレベルの使用では、どちらもICollection、IEnumerableなどの同じインターフェイスのセットを実装しているため、どちらもほとんど同じです。

重要な違いは、パフォーマンスが重要な場合です。たとえば、 "INSERT"操作の多いリストを実装している場合、LinkedListはListよりも優れています。LinkedListはO(1)時間でそれを行うことができるので、Listは基礎となる配列のサイズを拡張する必要があるかもしれません。詳細/詳細については、LinkedListと配列データ構造のアルゴリズムの違いを確認することをお勧めします。http://en.wikipedia.org/wiki/Linked_list and Array

この助けを願っています、


4
List <T>は配列(T [])ベースであり、ArrayListベースではありません。再挿入:配列のサイズ変更は問題ではありません(倍増アルゴリズムは、ほとんどの場合これを行う必要がないことを意味します):問題は、最初に既存のすべてのデータをブロックコピーする必要があることです。時間。
Marc Gravell

2
@Marc、「ダブリングアルゴリズム」はO(logN)にするだけですが、O(1)よりもさらに悪いです
Ilya Ryzhenkov

2
私のポイントは、痛みを引き起こすのはサイズ変更ではないということでした-それはブリットです。最悪の場合、最初の(0番目の)要素を毎回追加する場合、ブリットは毎回すべてを移動する必要があります。
Marc Gravell

@IlyaRyzhenkov-あなたAddは常に既存の配列の最後にある場合について考えています。ListO(1)でなくても、「十分」です。終わりでない多くAddのが必要な場合、深刻な問題が発生します。Marcは、(サイズ変更が必要なときだけでなく)挿入するたびに既存のデータを移動する必要があると、のパフォーマンスコストが大幅に増加することを指摘しています。List
ToolmakerSteve 2018年

問題は、理論的なBig Oの表記法がすべてのストーリーを伝えていないことです。コンピュータサイエンスでは、誰もが気にかけているものですが、現実の世界では、これよりもずっと心配する必要があります。
MattE 2019

11

配列よりもリンクリストの主な利点は、リンクによってアイテムを効率的に再配置できることです。セジウィック、p。91


1
IMOこれが答えになるはずです。LinkedListは、保証された順序が重要な場合に使用されます。
RBaarda

1
@RBaarda:同意しません。それは私たちが話しているレベルに依存します。アルゴリズムレベルは、マシン実装レベルとは異なります。速度を考慮すると、後者も必要です。指摘されているように、特に非常に大規模な配列の場合、サイズ変更やメモリの再編成につながる可能性があるため、配列はメモリの「1つのチャンク」として制限が実装されています。しばらく考えてから、特別な独自のデータ構造である配列のリンクリストを使用すると、線形入力の速度と非常に大きなデータ構造へのアクセスをより適切に制御できるようになります。
Philm

1
@Philm-私はあなたのコメントに賛成票を投じましたが、別の要件について説明していることを指摘しておきます。答えが言っているのは、リンクされたリストは、多くのアイテムの再配置を伴うアルゴリズムに対してパフォーマンス上の利点があるということです。そのため、RBardaのコメントは、指定された順序(ソート基準)を継続的に維持しながらアイテムを追加/削除する必要性を参照していると解釈します。したがって、「線形充填」だけではありません。これを考えると、インデックスは役に立たないため、Listは失われます(要素を末尾以外の場所に追加するたびに変更されます)。
ToolmakerSteve

4

LinkedListを使用する一般的な状況は次のとおりです。

たとえば、100,000という大きなサイズの文字列のリストから、特定の多くの文字列を削除するとします。削除する文字列はHashSet dicで検索でき、文字列のリストには、削除する30,000〜60,000の文字列が含まれていると考えられています。

それでは、100,000個の文字列を格納するのに最適なタイプのリストは何でしょうか。答えはLinkedListです。それらがArrayListに格納されている場合、それを繰り返し、一致する文字列を削除すると、何十億もの操作が必要になりますが、イテレータとremove()メソッドを使用すると、約100,000操作しかかかりません。

LinkedList<String> strings = readStrings();
HashSet<String> dic = readDic();
Iterator<String> iterator = strings.iterator();
while (iterator.hasNext()){
    String string = iterator.next();
    if (dic.contains(string))
    iterator.remove();
}

6
を使用RemoveAllしてList、多くの項目を移動せずにから項目を削除するかWhere、LINQからを使用して2番目のリストを作成できます。LinkedListただし、here を使用すると、他のタイプのコレクションよりも劇的に多くのメモリを消費することになり、メモリの局所性が失われると、反復処理が著しく遅くなり、aよりもかなり悪くなりますList
サービー2014年

@ Servy、@ Tomの回答はJavaを使用していることに注意してください。RemoveAllJavaに同等のものがあるかどうかはわかりません。
Arturo TorresSánchez2014年

3
@ArturoTorresSánchezさて、質問は具体的には.NETについてであると述べているので、答えはそれほど適切ではありません。
2014年

@Servy、それからあなたは最初からそれを述べるべきだった。
Arturo TorresSánchez2014年

でをRemoveAll使用できない場合はList、トムのループのように見える「圧縮」アルゴリズムを実行できますが、2つのインデックスがあり、リストの内部配列で一度に1つずつ保持されるように項目を移動する必要があります。効率はO(n)であり、のトムのアルゴリズムと同じですLinkedList。どちらのバージョンでも、文字列のHashSetキーを計算する時間が支配的です。これは、を使用する良い例ではありませんLinkedList
ToolmakerSteve

2

組み込みのインデックス付きアクセス、並べ替え(およびこのバイナリ検索の後)、および "ToArray()"メソッドが必要な場合は、Listを使用する必要があります。


2

基本的に、List<>.NETのは配列のラッパーです。A LinkedList<> はリンクされたリストです。したがって、問題は、配列とリンクリストの違いは何か、そしてリンクリストの代わりに配列を使用する必要がある場合です。おそらく、どちらを使用するかを決定する上で最も重要な2つの要素は次のようになります。

  • リンクされたリストは、挿入/削除がコレクションの最後の要素にない限り、挿入/削除のパフォーマンスが大幅に向上します。これは、挿入/削除ポイントの後に来る残りのすべての要素を配列がシフトする必要があるためです。ただし、挿入/削除がリストの最後にある場合、このシフトは必要ありません(ただし、容量を超えた場合は、配列のサイズを変更する必要があります)。
  • 配列には、より優れたアクセス機能があります。配列は直接(一定の時間で)にインデックスを付けることができます。リンクされたリストをトラバースする必要があります(線形時間)。

1

これは、遠野ナムが受け入れたいくつかの誤った測定値を修正した答えを改作したものです。

テスト:

static void Main()
{
    LinkedListPerformance.AddFirst_List(); // 12028 ms
    LinkedListPerformance.AddFirst_LinkedList(); // 33 ms

    LinkedListPerformance.AddLast_List(); // 33 ms
    LinkedListPerformance.AddLast_LinkedList(); // 32 ms

    LinkedListPerformance.Enumerate_List(); // 1.08 ms
    LinkedListPerformance.Enumerate_LinkedList(); // 3.4 ms

    //I tried below as fun exercise - not very meaningful, see code
    //sort of equivalent to insertion when having the reference to middle node

    LinkedListPerformance.AddMiddle_List(); // 5724 ms
    LinkedListPerformance.AddMiddle_LinkedList1(); // 36 ms
    LinkedListPerformance.AddMiddle_LinkedList2(); // 32 ms
    LinkedListPerformance.AddMiddle_LinkedList3(); // 454 ms

    Environment.Exit(-1);
}

そしてコード:

using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

namespace stackoverflow
{
    static class LinkedListPerformance
    {
        class Temp
        {
            public decimal A, B, C, D;

            public Temp(decimal a, decimal b, decimal c, decimal d)
            {
                A = a; B = b; C = c; D = d;
            }
        }



        static readonly int start = 0;
        static readonly int end = 123456;
        static readonly IEnumerable<Temp> query = Enumerable.Range(start, end - start).Select(temp);

        static Temp temp(int i)
        {
            return new Temp(i, i, i, i);
        }

        static void StopAndPrint(this Stopwatch watch)
        {
            watch.Stop();
            Console.WriteLine(watch.Elapsed.TotalMilliseconds);
        }

        public static void AddFirst_List()
        {
            var list = new List<Temp>();
            var watch = Stopwatch.StartNew();

            for (var i = start; i < end; i++)
                list.Insert(0, temp(i));

            watch.StopAndPrint();
        }

        public static void AddFirst_LinkedList()
        {
            var list = new LinkedList<Temp>();
            var watch = Stopwatch.StartNew();

            for (int i = start; i < end; i++)
                list.AddFirst(temp(i));

            watch.StopAndPrint();
        }

        public static void AddLast_List()
        {
            var list = new List<Temp>();
            var watch = Stopwatch.StartNew();

            for (var i = start; i < end; i++)
                list.Add(temp(i));

            watch.StopAndPrint();
        }

        public static void AddLast_LinkedList()
        {
            var list = new LinkedList<Temp>();
            var watch = Stopwatch.StartNew();

            for (int i = start; i < end; i++)
                list.AddLast(temp(i));

            watch.StopAndPrint();
        }

        public static void Enumerate_List()
        {
            var list = new List<Temp>(query);
            var watch = Stopwatch.StartNew();

            foreach (var item in list)
            {

            }

            watch.StopAndPrint();
        }

        public static void Enumerate_LinkedList()
        {
            var list = new LinkedList<Temp>(query);
            var watch = Stopwatch.StartNew();

            foreach (var item in list)
            {

            }

            watch.StopAndPrint();
        }

        //for the fun of it, I tried to time inserting to the middle of 
        //linked list - this is by no means a realistic scenario! or may be 
        //these make sense if you assume you have the reference to middle node

        //insertion to the middle of list
        public static void AddMiddle_List()
        {
            var list = new List<Temp>();
            var watch = Stopwatch.StartNew();

            for (var i = start; i < end; i++)
                list.Insert(list.Count / 2, temp(i));

            watch.StopAndPrint();
        }

        //insertion in linked list in such a fashion that 
        //it has the same effect as inserting into the middle of list
        public static void AddMiddle_LinkedList1()
        {
            var list = new LinkedList<Temp>();
            var watch = Stopwatch.StartNew();

            LinkedListNode<Temp> evenNode = null, oddNode = null;
            for (int i = start; i < end; i++)
            {
                if (list.Count == 0)
                    oddNode = evenNode = list.AddLast(temp(i));
                else
                    if (list.Count % 2 == 1)
                        oddNode = list.AddBefore(evenNode, temp(i));
                    else
                        evenNode = list.AddAfter(oddNode, temp(i));
            }

            watch.StopAndPrint();
        }

        //another hacky way
        public static void AddMiddle_LinkedList2()
        {
            var list = new LinkedList<Temp>();
            var watch = Stopwatch.StartNew();

            for (var i = start + 1; i < end; i += 2)
                list.AddLast(temp(i));
            for (int i = end - 2; i >= 0; i -= 2)
                list.AddLast(temp(i));

            watch.StopAndPrint();
        }

        //OP's original more sensible approach, but I tried to filter out
        //the intermediate iteration cost in finding the middle node.
        public static void AddMiddle_LinkedList3()
        {
            var list = new LinkedList<Temp>();
            var watch = Stopwatch.StartNew();

            for (var i = start; i < end; i++)
            {
                if (list.Count == 0)
                    list.AddLast(temp(i));
                else
                {
                    watch.Stop();
                    var curNode = list.First;
                    for (var j = 0; j < list.Count / 2; j++)
                        curNode = curNode.Next;
                    watch.Start();

                    list.AddBefore(curNode, temp(i));
                }
            }

            watch.StopAndPrint();
        }
    }
}

結果は、他の人がここで文書化した理論的なパフォーマンスに従っていることがわかります。かなり明確- LinkedList<T>挿入の場合に大きな時間を得る。リストの中央からの削除についてはテストしていませんが、結果は同じになるはずです。もちろんList<T>、O(1)ランダムアクセスのようにパフォーマンスが向上する他の領域があります。


0

LinkedList<>いつ使用するか

  1. 水門から入ってくる物体の数がわかりません。例えば、Token Stream
  2. 最後にのみ削除\挿入したい場合。

それ以外の場合は、を使用することをお勧めしますList<>


6
ポイント2が理にかなっている理由がわかりません。リンクされたリストは、リスト全体で多くの挿入/削除を行う場合に最適です。
Drew Noakes、

LinkedListsはインデックスベースではないため、実際にはリスト全体をスキャンして、O(n)ペナルティが発生する挿入または削除を探す必要があります。一方、List <>は配列のサイズ変更の影響を受けますが、LinkedListsと比較すると、IMOの方が優れたオプションです。
アントニートーマス

1
LinkedListNode<T>コード内のオブジェクトを追跡している場合は、挿入/削除のリストをスキャンする必要はありません。それができる場合List<T>、特に挿入/削除が頻繁に行われる非常に長いリストの場合は、を使用するよりもはるかに優れています。
Drew Noakes

ハッシュテーブルを介してですか?その場合、それはすべてのコンピュータープログラマーが問題のドメインに基づいて選択する必要がある典型的な時空のトレードオフになります:)しかし、はい、それはそれをより速くします。
アントニートーマス

1
@AntonyThomas-いいえ、彼は要素への参照を渡すのではなく、ノードへの参照を渡すことを意味します。あなたが持っているすべての場合は要素は、両方のあなたが検索する必要があるためリストとLinkedListのは、悪いパフォーマンスを持っています。「リストではインデックスを渡すことができる」と思う場合、それは、リストの中央に新しい要素を挿入しない場合にのみ有効です。LinkedListには、ノードを保持している場合(および元の要素が必要な場合はいつでも使用できる場合)、この制限はありません。したがって、生の値ではなくノードで動作するようにアルゴリズムを書き換えます。node.Value
ToolmakerSteve

0

上記の点のほとんどに同意します。また、ほとんどの場合、リストがより明白な選択肢のように見えることにも同意します。

ただし、LinkedListの方がListよりも効率が良いため、はるかに優れている場合が多いことを付け加えておきます。

  1. 要素をトラバースしていて、多くの挿入/削除を実行したいとします。LinkedListは線形O(n)時間で実行しますが、Listは2次O(n ^ 2)時間で実行します。
  2. 大きなオブジェクトに何度もアクセスしたいとすると、LinkedListは非常に便利になります。
  3. Deque()およびqueue()は、LinkedListを使用してより適切に実装されます。
  4. 多くのより大きなオブジェクトを扱うと、LinkedListのサイズを大きくすることははるかに簡単になり、より良くなります。

誰かがこれらのコメントが役に立てば幸いです。


このアドバイスはJavaではなく.NETに対するものであることに注意してください。Javaのリンクリストの実装では、「現在のノード」の概念がないため、すべての挿入についてリストを走査する必要があります。
ジョナサンアレン

この回答は部分的にしか正しくありません。2)要素が大きい場合は、要素タイプを構造体ではなくクラスにして、リストが単に参照を保持するようにします。その後、要素のサイズは無関係になります。3)開始時に挿入または削除を行う代わりに、リストを「循環バッファー」として使用すると、デキューおよびキューリストで効率的に実行できます StephenClearyのDeque。4)部分的に当てはまる:多数のオブジェクトがある場合、LLのプロは大きな連続したメモリを必要としません。欠点は、ノードポインタ用の追加メモリです。
ToolmakerSteve

-2

ここに非常に多くの平均的な答え...

一部のリンクリストの実装では、事前に割り当てられたノードの基本ブロックを使用します。彼らがこれを一定時間/線形時間よりも実行しない場合、メモリのパフォーマンスが低下し、キャッシュのパフォーマンスがさらに悪化するため、関連性は低くなります。

リンクリストを使用する場合

1)スレッドセーフが必要です。より優れたスレッドセーフアルゴを構築できます。ロックコストは、並行スタイルリストを支配します。

2)構造のような大きなキューがあり、常に最後以外の場所で削除または追加したい場合。10万を超えるリストは存在しますが、それほど一般的ではありません。


3
この質問は、一般にリンクされたリストではなく、2つのC#実装に関するものでした。
Jonathan Allen

すべての言語で同じ
user1496062 2018

-2

LinkedListコレクションのパフォーマンスに関連する同様の質問をしたところ、DequeのSteven ClearyのC#実装が解決策であることを発見しました。Queueコレクションとは異なり、Dequeではアイテムを前後に移動できます。リンクリストに似ていますが、パフォーマンスが向上しています。


1
「リンクリストに似ているが、パフォーマンスが向上しDequeいるという記述を再確認してください。そのステートメントを修飾してください:特定のコードでDequeLinkedList、はより優れたパフォーマンスです。リンクをたどると、2日後にIvan Stoevから、これはLinkedListの非効率ではなく、コードの非効率であることがわかりました。(そして、それがLinkedListの非効率であったとしても、それはDequeがより効率的であるという一般的なステートメントを正当化しません;特定の場合にのみ。)
ToolmakerSteve
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.