Kapan saya harus menggunakan Daftar vs LinkedList


Jawaban:


107

Edit

Silakan baca komentar untuk jawaban ini. Orang mengklaim saya tidak melakukan tes yang tepat. Saya setuju ini seharusnya bukan jawaban yang diterima. Ketika saya belajar saya melakukan beberapa tes dan merasa ingin membagikannya.

Jawaban asli ...

Saya menemukan hasil yang menarik:

// 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;
    }
}

Daftar tertaut (3,9 detik)

        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;

Daftar (2,4 detik)

        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;

Bahkan jika Anda hanya mengakses data pada dasarnya itu jauh lebih lambat !! Saya katakan tidak pernah menggunakan LinkedList.




Berikut adalah perbandingan lain yang melakukan banyak sisipan (kami berencana untuk memasukkan item di tengah daftar)

Daftar Tertaut (51 detik)

        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;

Daftar (7,26 detik)

        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;

Daftar Tertaut memiliki referensi lokasi tempat memasukkan (0,04 detik)

        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;

Jadi hanya jika Anda berencana untuk memasukkan beberapa item dan Anda juga di suatu tempat memiliki referensi di mana Anda berencana untuk memasukkan item kemudian gunakan daftar tertaut. Hanya karena Anda harus memasukkan banyak item, itu tidak membuatnya lebih cepat karena mencari lokasi di mana Anda ingin memasukkannya membutuhkan waktu.


99
Ada satu manfaat bagi LinkedList daripada Daftar (ini adalah .net khusus): karena Daftar didukung oleh array internal, itu dialokasikan dalam satu blok yang berdekatan. Jika blok yang dialokasikan melebihi ukuran 85000 byte, itu akan dialokasikan pada Large Object Heap, generasi yang tidak dapat dipadatkan. Tergantung pada ukurannya, ini dapat menyebabkan tumpukan fragmentasi, bentuk kebocoran memori yang ringan.
JerKimball

35
Perhatikan bahwa jika Anda banyak memprioritaskan (seperti yang Anda lakukan pada contoh terakhir) atau menghapus entri pertama, daftar yang ditautkan akan hampir selalu lebih cepat secara signifikan, karena tidak ada pencarian atau pemindahan / penyalinan yang harus dilakukan. Daftar akan mengharuskan Anda memindahkan semuanya ke atas untuk mengakomodasi item baru, membuat operasi O (N) yang diawali.
cHao

6
Mengapa in-loop list.AddLast(a);dalam dua contoh LinkedList terakhir? Saya bisa melakukannya sekali sebelum loop, seperti list.AddLast(new Temp(1,1,1,1));di sebelah LinkedList terakhir, tapi sepertinya (bagi saya) seperti Anda menambahkan dua kali lebih banyak objek Temp di loop sendiri. (Dan ketika saya mengecek diri saya dengan aplikasi tes , tentu saja, dua kali lebih banyak di LinkedList.)
ruffin

7
Saya menurunkan jawaban ini. 1) saran umum Anda I say never use a linkedList.cacat karena posting Anda nanti mengungkapkan. Anda mungkin ingin mengeditnya. 2) Apa waktu Anda? Instansiasi, penambahan, dan pencacahan sekaligus dalam satu langkah? Kebanyakan, instantiasi dan enumerasi bukan hal yang dikhawatirkan oleh ppl, itu adalah langkah satu kali. Khususnya waktu memasukkan dan penambahan akan memberikan ide yang lebih baik. 3) Yang terpenting, Anda menambahkan lebih dari yang dibutuhkan ke daftar tertaut. Ini perbandingan yang salah. Menyebarkan gagasan yang salah tentang tertaut.
nawfal

47
Maaf, tapi jawaban ini sangat buruk. Tolong, JANGAN dengarkan jawaban ini. Singkatnya: Benar-benar salah untuk berpikir bahwa implementasi daftar yang didukung array cukup bodoh untuk mengubah ukuran array pada setiap penyisipan. Daftar yang ditautkan secara alami lebih lambat daripada daftar yang didukung larik ketika melintasi dan juga saat menyisipkan di kedua ujungnya, karena hanya mereka yang perlu membuat objek baru, sedangkan daftar yang didukung larik menggunakan buffer (di kedua arah, jelas). Benchmark (buruk dilakukan) menunjukkan hal itu. Jawabannya benar-benar gagal untuk memeriksa kasus-kasus di mana daftar tertaut lebih disukai!
mafu

277

Dalam kebanyakan kasus, List<T>ini lebih bermanfaat. LinkedList<T>akan memiliki biaya lebih sedikit ketika menambahkan / menghapus item di tengah daftar, sedangkan List<T>hanya dapat menambahkan / menghapus dengan murah di akhir daftar.

LinkedList<T>hanya itu yang paling efisien jika Anda mengakses data berurutan (baik maju atau mundur) - akses acak relatif mahal karena harus berjalan di rantai setiap kali (karenanya mengapa tidak memiliki pengindeks). Namun, karena a List<T>pada dasarnya hanyalah sebuah array (dengan pembungkus) akses acak baik-baik saja.

List<T>juga menawarkan banyak metode dukungan - Find, ToArray, dll; Namun, ini juga tersedia untuk LinkedList<T>dengan .NET 3.5 / C # 3.0 melalui metode ekstensi - sehingga kurang dari faktor.


4
Satu keuntungan dari Daftar <> vs. LinkedList <> yang saya tidak pernah pikirkan tentang bagaimana mikroprosesor menerapkan caching memori. Meskipun saya tidak sepenuhnya memahaminya, penulis artikel blog ini berbicara banyak tentang "locality of reference", yang membuat melintasi array jauh lebih cepat daripada melintasi daftar tertaut, setidaknya jika daftar tertaut menjadi agak terfragmentasi dalam memori . kjellkod.wordpress.com/2012/02/25/…
RenniePet

@RenniePet List diimplementasikan dengan array dinamis dan array adalah blok memori yang berdekatan.
Casey

2
Karena Daftar adalah array dinamis, itu sebabnya kadang-kadang ada baiknya untuk menentukan kapasitas Daftar di konstruktor jika Anda mengetahuinya sebelumnya.
Cardin Lee JH

Apakah mungkin bahwa implementasi C # dari semua, array, Daftar <T> dan LinkedList <T> agak suboptimal untuk satu kasus yang sangat penting: Anda perlu daftar yang sangat besar, tambahkan (AddLast) dan urutan berurutan (dalam satu arah) adalah baik-baik saja: Saya ingin tidak ada ukuran array untuk mendapatkan blok kontinu (apakah dijamin untuk setiap array, bahkan array 20 GB?), dan saya tidak tahu terlebih dahulu ukurannya, tapi saya bisa menebak sebelumnya ukuran blok, misalnya 100 MB untuk memesan setiap waktu di muka. Ini akan menjadi implementasi yang baik. Atau apakah array / Daftar mirip dengan itu, dan saya melewatkan satu poin?
Philm

1
@Film itulah jenis skenario di mana Anda menulis sendiri tentang strategi blok yang Anda pilih; List<T>dan T[]akan gagal karena terlalu chunky (semua satu slab), LinkedList<T>akan meratap karena terlalu granular (slab per elemen).
Marc Gravell

212

Memikirkan daftar tertaut sebagai daftar bisa sedikit menyesatkan. Ini lebih seperti sebuah rantai. Bahkan, dalam. NET, LinkedList<T>bahkan tidak diimplementasikan IList<T>. Tidak ada konsep indeks yang nyata dalam daftar tertaut, meskipun tampaknya ada. Tentu saja tidak ada metode yang disediakan di kelas menerima indeks.

Daftar tertaut dapat ditautkan secara tunggal, atau ditautkan secara ganda. Ini mengacu pada apakah setiap elemen dalam rantai hanya memiliki tautan ke yang berikutnya (ditautkan sendiri) atau ke kedua elemen sebelumnya / berikutnya (ditautkan dua kali). LinkedList<T>terkait dua kali lipat.

Secara internal, List<T>didukung oleh sebuah array. Ini memberikan representasi yang sangat kompak dalam memori. Sebaliknya, LinkedList<T>melibatkan memori tambahan untuk menyimpan tautan dua arah antara elemen yang berurutan. Jadi jejak memori a LinkedList<T>umumnya akan lebih besar daripada untuk List<T>(dengan peringatan yang List<T>dapat memiliki elemen array internal yang tidak digunakan untuk meningkatkan kinerja selama operasi penambahan.)

Mereka memiliki karakteristik kinerja yang berbeda juga:

Menambahkan

  • LinkedList<T>.AddLast(item) waktu yang konstan
  • List<T>.Add(item) waktu konstan diamortisasi, kasus terburuk linier

Tergantung

  • LinkedList<T>.AddFirst(item) waktu yang konstan
  • List<T>.Insert(0, item) waktu linier

Insersi

  • LinkedList<T>.AddBefore(node, item) waktu yang konstan
  • LinkedList<T>.AddAfter(node, item) waktu yang konstan
  • List<T>.Insert(index, item) waktu linier

Pemindahan

  • LinkedList<T>.Remove(item) waktu linier
  • LinkedList<T>.Remove(node) waktu yang konstan
  • List<T>.Remove(item) waktu linier
  • List<T>.RemoveAt(index) waktu linier

Menghitung

  • LinkedList<T>.Count waktu yang konstan
  • List<T>.Count waktu yang konstan

Mengandung

  • LinkedList<T>.Contains(item) waktu linier
  • List<T>.Contains(item) waktu linier

Bersih

  • LinkedList<T>.Clear() waktu linier
  • List<T>.Clear() waktu linier

Seperti yang Anda lihat, mereka kebanyakan setara. Dalam praktiknya, API dariLinkedList<T> lebih rumit untuk digunakan, dan rincian kebutuhan internalnya mencurahkan ke dalam kode Anda.

Namun, jika Anda perlu melakukan banyak penyisipan / pemindahan dari dalam daftar, ia menawarkan waktu yang konstan. List<T>menawarkan waktu linier, karena item tambahan dalam daftar harus dikocok setelah penyisipan / penghapusan.


2
Apakah jumlah linkedlist konstan? Saya pikir itu akan linear?
Iain Ballard

10
@ Sekali lagi, hitungan di-cache di kedua kelas daftar.
Drew Noakes

3
Anda menulis "Daftar <T> .Tambahkan (item) waktu logaritmik", namun sebenarnya "Constant" jika kapasitas daftar dapat menyimpan item baru, dan "Linear" jika daftar tidak memiliki cukup ruang dan baru untuk dialokasikan kembali.
aStranger

@ aStranger, tentu saja Anda benar. Tidak yakin apa yang saya pikirkan di atas - mungkin waktu kasus normal yang diamortisasi adalah logaritmik, padahal tidak. Bahkan waktu diamortisasi konstan. Saya tidak masuk ke dalam kasus operasi terbaik / terburuk, bertujuan untuk perbandingan sederhana. Saya pikir operasi add cukup signifikan untuk memberikan detail ini. Akan mengedit jawabannya. Terima kasih.
Drew Noakes

1
@Film, Anda mungkin harus memulai pertanyaan baru, dan Anda tidak mengatakan bagaimana Anda akan menggunakan struktur data ini setelah dibangun, tetapi jika Anda berbicara sejuta baris, Anda mungkin menyukai beberapa jenis hibrida (daftar tautan dari potongan array atau sejenisnya) untuk mengurangi fragmentasi tumpukan, mengurangi overhead memori, dan menghindari objek besar tunggal pada LOH.
Drew Noakes

118

Daftar tertaut menyediakan penyisipan atau penghapusan anggota daftar yang sangat cepat. Setiap anggota dalam daftar tertaut berisi pointer ke anggota berikutnya dalam daftar sehingga untuk memasukkan anggota pada posisi i:

  • perbarui pointer di anggota i-1 untuk menunjuk ke anggota baru
  • atur pointer di anggota baru untuk menunjuk ke anggota i

Kerugian dari daftar tertaut adalah akses acak tidak dimungkinkan. Mengakses anggota memerlukan melintasi daftar sampai anggota yang diinginkan ditemukan.


6
Saya ingin menambahkan bahwa daftar tertaut memiliki overhead per item yang tersimpan tersirat di atas melalui LinkedListNode yang mereferensikan node sebelumnya dan berikutnya. Imbalannya adalah blok memori yang berdekatan tidak diperlukan untuk menyimpan daftar, tidak seperti daftar berbasis array.
paulecoyote

3
Bukankah blok memori yang berdekatan biasanya ditangguhkan?
Jonathan Allen

7
Ya, blok yang berdekatan lebih disukai untuk kinerja akses acak dan konsumsi memori tetapi untuk koleksi yang perlu mengubah ukuran secara teratur struktur seperti Array umumnya perlu disalin ke lokasi baru sedangkan daftar tertaut hanya perlu mengelola memori untuk node yang baru saja dimasukkan / dihapus.
jpierson

6
Jika Anda pernah harus bekerja dengan array atau daftar yang sangat besar (daftar hanya membungkus array), Anda akan mulai mengalami masalah memori meskipun tampaknya ada banyak memori yang tersedia di mesin Anda. Daftar ini menggunakan strategi penggandaan ketika mengalokasikan ruang baru di array yang mendasarinya. Jadi array elemnt 1000000 yang penuh akan disalin ke array baru dengan 2000000 elemen. Array baru ini perlu dibuat dalam ruang memori yang berdekatan yang cukup besar untuk menampungnya.
Andrew

1
Saya memiliki kasus khusus di mana semua yang saya lakukan adalah menambah dan menghapus, dan pengulangan satu per satu ... di sini daftar tertaut jauh lebih unggul dari daftar normal ..
Peter

26

Jawaban saya sebelumnya tidak cukup akurat. Benar-benar mengerikan: D Tetapi sekarang saya dapat memposting jawaban yang jauh lebih berguna dan benar.


Saya melakukan beberapa tes tambahan. Anda dapat menemukan sumbernya dengan tautan berikut dan periksa kembali di lingkungan Anda sendiri: https://github.com/ukushu/DataStructuresTestsAndOther.git

Hasil singkat:

  • Array perlu digunakan:

    • Sesering mungkin. Cepat dan membutuhkan rentang RAM terkecil untuk informasi jumlah yang sama.
    • Jika Anda tahu jumlah pasti sel yang dibutuhkan
    • Jika data disimpan dalam array <85000 b (85000/32 = 2656 elemen untuk data integer)
    • Jika diperlukan kecepatan Akses Acak tinggi
  • Daftar harus digunakan:

    • Jika perlu menambahkan sel ke akhir daftar (sering)
    • Jika perlu menambahkan sel di awal / tengah daftar (BUKAN SERING)
    • Jika data disimpan dalam array <85000 b (85000/32 = 2656 elemen untuk data integer)
    • Jika diperlukan kecepatan Akses Acak tinggi
  • LinkedList perlu menggunakan:

    • Jika perlu menambahkan sel di awal / tengah / akhir daftar (sering)
    • Jika diperlukan hanya akses sekuensial (maju / mundur)
    • Jika Anda perlu menyimpan item BESAR, tetapi jumlah item rendah.
    • Lebih baik tidak menggunakan item dalam jumlah besar, karena menggunakan memori tambahan untuk tautan.

Keterangan lebih lanjut:

введите сюда описание изображения Menarik untuk diketahui:

  1. LinkedList<T>secara internal bukan Daftar di .NET. Itu bahkan tidak diimplementasikan IList<T>. Dan itu sebabnya ada indeks dan metode yang tidak ada terkait dengan indeks.

  2. LinkedList<T>adalah koleksi berbasis simpul-pointer. Dalam. NET itu dalam implementasi terkait dua kali lipat. Ini berarti bahwa elemen sebelum / berikutnya memiliki tautan ke elemen saat ini. Dan data terfragmentasi - objek daftar yang berbeda dapat ditemukan di berbagai tempat RAM. Juga akan ada lebih banyak memori yang digunakan LinkedList<T>daripada untuk List<T>atau Array.

  3. List<T>di .Net adalah alternatif Java dari ArrayList<T>. Ini berarti bahwa ini adalah pembungkus array. Jadi itu dialokasikan dalam memori sebagai satu blok data yang berdekatan. Jika ukuran data yang dialokasikan melebihi 85000 byte, itu akan dipindahkan ke Tumpukan Objek Besar. Tergantung pada ukuran, ini dapat menyebabkan tumpukan tumpukan (bentuk kebocoran memori ringan). Tetapi dalam waktu yang sama jika ukuran <85000 byte - ini memberikan representasi yang sangat ringkas dan akses cepat dalam memori.

  4. Blok bersebelahan tunggal lebih disukai untuk kinerja akses acak dan konsumsi memori tetapi untuk koleksi yang perlu mengubah ukuran secara teratur struktur seperti Array umumnya perlu disalin ke lokasi baru sedangkan daftar tertaut hanya perlu mengelola memori untuk yang baru dimasukkan / menghapus node.


1
Pertanyaan: Dengan "data yang disimpan dalam array <atau> 85.000 byte" maksud Anda data per array / daftar ELEMEN kan? Dapat dipahami bahwa yang Anda maksud adalah data seluruh array ..
Philm

Elemen array terletak berurutan dalam memori. Jadi per array. Saya tahu tentang kesalahan dalam tabel, nanti saya akan memperbaikinya :) (Saya harap ....)
Andrew

Dengan daftar yang lambat di sisipan, jika daftar memiliki banyak turnaround (banyak sisipan / penghapusan) adalah memori ditempati oleh ruang yang dihapus disimpan dan jika demikian, apakah itu membuat "kembali" -masukkan lebih cepat?
Rob

18

Perbedaan antara Daftar dan LinkedList terletak pada implementasi yang mendasarinya. Daftar adalah koleksi berbasis array (ArrayList). LinkedList adalah koleksi berbasis simpul-pointer (LinkedListNode). Pada penggunaan level API, keduanya hampir sama karena keduanya mengimplementasikan set antarmuka yang sama seperti ICollection, IEnumerable, dll.

Perbedaan utama muncul ketika masalah kinerja. Misalnya, jika Anda menerapkan daftar yang memiliki operasi "INSERT" berat, LinkedList mengungguli Daftar. Karena LinkedList dapat melakukannya dalam waktu O (1), tetapi Daftar mungkin perlu memperluas ukuran array yang mendasarinya. Untuk informasi lebih lanjut / detail Anda mungkin ingin membaca tentang perbedaan algoritmik antara LinkedList dan struktur data array. http://en.wikipedia.org/wiki/Linked_list dan Array

Semoga bantuan ini,


4
Daftar <T> berbasis array (T []), bukan berbasis ArrayList. Masukkan kembali: ukuran array bukan masalah (algoritma penggandaan berarti bahwa sebagian besar waktu tidak harus melakukan ini): masalahnya adalah ia harus memblokir-menyalin semua data yang ada terlebih dahulu, yang membutuhkan sedikit waktu.
Marc Gravell

2
@Marc, 'algoritma penggandaan "hanya membuatnya O (logN), tetapi masih lebih buruk daripada O (1)
Ilya Ryzhenkov

2
Maksud saya adalah bahwa itu bukan ukuran yang menyebabkan rasa sakit - itu adalah blit. Jadi kasus terburuk, jika kita menambahkan elemen pertama (nol) setiap kali, maka blit harus memindahkan semuanya setiap waktu.
Marc Gravell

@IlyaRyzhenkov - Anda berpikir tentang kasus di mana Addselalu di akhir array yang ada. List"cukup baik" dalam hal itu, bahkan jika bukan O (1). Masalah serius terjadi jika Anda membutuhkan banyak Addyang tidak pada akhirnya. Marc menunjukkan bahwa kebutuhan untuk memindahkan data yang ada setiap kali Anda memasukkan (bukan hanya ketika ukuran diperlukan) adalah biaya kinerja yang lebih besar List.
ToolmakerSteve

Masalahnya adalah Teoritis Big O notasi tidak menceritakan keseluruhan cerita. Dalam ilmu komputer itu semua orang yang pernah peduli, tetapi ada jauh lebih banyak yang harus diperhatikan daripada ini di dunia nyata.
MattE

11

Keuntungan utama daftar yang ditautkan daripada array adalah bahwa tautan tersebut memberi kami kemampuan untuk mengatur ulang item secara efisien. Sedgewick, hlm. 91


1
IMO ini harus menjadi jawabannya. LinkedList digunakan ketika pesanan yang dijamin penting.
RBaarda

1
@RBaarda: Saya tidak setuju. Itu tergantung pada level yang kita bicarakan. Level algoritmik berbeda dengan level implementasi mesin. Untuk pertimbangan kecepatan, Anda membutuhkan yang terakhir juga. Seperti yang ditunjukkan, array diimplementasikan menjadi "satu potongan" memori yang merupakan batasan, karena ini dapat menyebabkan pengubahan ukuran dan reorganisasi memori, terutama dengan array yang sangat besar. Setelah berpikir sebentar, struktur data khusus sendiri, daftar array yang terhubung akan menjadi satu ide untuk memberikan kontrol yang lebih baik terhadap kecepatan pengisian linier dan mengakses struktur data yang sangat besar.
Philm

1
@Film - Saya meningkatkan komentar Anda, tetapi saya ingin menunjukkan bahwa Anda menggambarkan persyaratan yang berbeda. Apa jawabannya adalah daftar tertaut memiliki keunggulan kinerja untuk algoritme yang melibatkan banyak pengaturan ulang item. Karena itu, saya menafsirkan komentar RBaarda sebagai merujuk pada kebutuhan untuk menambah / menghapus item sambil terus mempertahankan pemesanan yang diberikan (mengurutkan kriteria). Jadi bukan hanya "mengisi linier". Mengingat hal ini, Daftar kalah, karena indeks tidak berguna (ubah setiap kali Anda menambahkan elemen di mana pun kecuali di ujung ekor).
ToolmakerSteve

4

Keadaan umum untuk menggunakan LinkedList adalah seperti ini:

Misalkan Anda ingin menghapus banyak string tertentu dari daftar string dengan ukuran besar, katakanlah 100.000. String yang akan dihapus dapat dicari di HashSet dic, dan daftar string diyakini mengandung antara 30.000 hingga 60.000 string yang harus dihapus.

Lalu apa jenis Daftar terbaik untuk menyimpan 100.000 Strings? Jawabannya adalah LinkedList. Jika mereka disimpan dalam ArrayList, maka iterasi di atasnya dan menghapus Strings yang cocok akan membutuhkan milyaran operasi, sementara itu hanya membutuhkan sekitar 100.000 operasi dengan menggunakan iterator dan metode remove ().

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
Anda cukup menggunakan RemoveAlluntuk menghapus item dari Listtanpa memindahkan banyak item di sekitar, atau gunakan Wheredari LINQ untuk membuat daftar kedua. Menggunakan LinkedListsini namun akhirnya memakan dramatis memori lebih dari jenis lain dari koleksi dan hilangnya berarti memori lokalitas bahwa hal itu akan terasa lebih lambat untuk iterate, sehingga cukup sedikit lebih buruk daripada List.
Servy

@Servy, perhatikan bahwa jawaban @ Tom menggunakan Java. Saya tidak yakin apakah ada yang RemoveAllsetara di Jawa.
Arturo Torres Sánchez

3
@ ArturoTorresSánchez Nah pertanyaannya secara khusus menyatakan bahwa ini tentang .NET, sehingga hanya membuat jawaban yang kurang sesuai.
Servy

@Ervy, maka Anda harus menyebutkan itu dari awal.
Arturo Torres Sánchez

Jika RemoveAlltidak tersedia untuk List, Anda bisa melakukan algoritma "pemadatan", yang akan terlihat seperti loop Tom, tetapi dengan dua indeks dan kebutuhan untuk memindahkan item agar disimpan satu per satu di array internal daftar. Efisiensi adalah O (n), sama dengan algoritma Tom untuk LinkedList. Di kedua versi, waktu untuk menghitung kunci HashSet untuk string mendominasi. Ini bukan contoh yang baik kapan harus digunakan LinkedList.
ToolmakerSteve

2

Ketika Anda membutuhkan akses indeks bawaan, pengurutan (dan setelah pencarian biner ini), dan metode "ToArray ()", Anda harus menggunakan Daftar.


2

Pada dasarnya, List<>dalam. NET adalah pembungkus array . A LinkedList<> adalah daftar tertaut . Jadi pertanyaannya adalah, apa perbedaan antara array dan daftar yang ditautkan, dan kapan seharusnya sebuah array digunakan daripada daftar yang ditautkan. Mungkin dua faktor terpenting dalam keputusan Anda yang akan digunakan adalah:

  • Daftar yang ditautkan memiliki kinerja penyisipan / penghilangan yang jauh lebih baik, asalkan penyisipan / pemindahan tidak pada elemen terakhir dalam koleksi. Ini karena array harus menggeser semua elemen yang tersisa setelah titik penyisipan / penghapusan. Namun, jika penyisipan / pemindahan berada di ujung ekor daftar, pergeseran ini tidak diperlukan (meskipun array mungkin perlu diubah ukurannya, jika kapasitasnya terlampaui).
  • Array memiliki kemampuan mengakses yang jauh lebih baik. Array dapat diindeks menjadi langsung (dalam waktu konstan). Daftar tertaut harus dilalui (waktu linier).

1

Ini diadaptasi dari jawaban yang diterima Tono Nam yang mengoreksi beberapa pengukuran yang salah di dalamnya.

Ujian:

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);
}

Dan kodenya:

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();
        }
    }
}

Anda dapat melihat hasilnya sesuai dengan kinerja teoritis yang telah didokumentasikan orang lain di sini. Cukup jelas - LinkedList<T>mendapatkan waktu besar jika dimasukkan. Saya belum menguji penghapusan dari bagian tengah daftar, tetapi hasilnya harus sama. Tentu saja List<T>memiliki area lain di mana ia berkinerja lebih baik seperti O (1) akses acak.


0

Gunakan LinkedList<>kapan

  1. Anda tidak tahu berapa banyak benda yang melewati gerbang banjir. Sebagai contoh Token Stream,.
  2. Ketika Anda HANYA ingin menghapus \ masukkan di ujungnya.

Untuk yang lainnya, lebih baik digunakan List<>.


6
Saya tidak mengerti mengapa poin 2 masuk akal. Daftar tertaut sangat bagus ketika Anda melakukan banyak penyisipan / penghapusan di seluruh daftar.
Drew Noakes

Karena fakta bahwa LinkedLists bukan berbasis Indeks, Anda benar-benar harus memindai seluruh daftar untuk penyisipan atau penghapusan yang menimbulkan penalti O (n). Daftar <> di sisi lain menderita pengubahan ukuran Array, tetapi tetap saja, IMO, adalah pilihan yang lebih baik bila dibandingkan dengan LinkedLists.
Antony Thomas

1
Anda tidak perlu memindai daftar untuk penyisipan / penghapusan jika Anda melacak LinkedListNode<T>objek dalam kode Anda. Jika Anda bisa melakukannya, maka itu jauh lebih baik daripada menggunakan List<T>, terutama untuk daftar yang sangat panjang di mana memasukkan / menghilangkan sering.
Drew Noakes

Maksudmu melalui hashtable? Jika itu masalahnya, itu akan menjadi tradeoff ruang \ waktu yang khas bahwa setiap programmer komputer harus membuat pilihan berdasarkan domain masalah :) Tapi ya, itu akan membuatnya lebih cepat.
Antony Thomas

1
@AntonyThomas - Tidak, maksudnya dengan membagikan referensi ke node alih-alih membagikan referensi ke elemen . Jika semua yang Anda miliki adalah sebuah elemen , maka keduanya Daftar dan LinkedList memiliki kinerja buruk, karena Anda harus mencari. Jika Anda berpikir "tetapi dengan Daftar, saya hanya bisa meneruskan dalam indeks": itu hanya berlaku ketika Anda tidak pernah memasukkan elemen baru ke tengah Daftar. LinkedList tidak memiliki batasan ini, jika Anda berpegang pada sebuah simpul (dan menggunakan node.Valuekapan pun Anda menginginkan elemen aslinya). Jadi Anda menulis ulang algoritma untuk bekerja dengan node, bukan nilai mentah.
ToolmakerSteve

0

Saya setuju dengan sebagian besar poin di atas. Dan saya juga setuju bahwa Daftar terlihat seperti pilihan yang lebih jelas dalam sebagian besar kasus.

Tapi, saya hanya ingin menambahkan bahwa ada banyak contoh di mana LinkedList adalah pilihan yang jauh lebih baik daripada Daftar untuk efisiensi yang lebih baik.

  1. Misalkan Anda melintasi elemen-elemen dan Anda ingin melakukan banyak penyisipan / penghapusan; LinkedList melakukannya dalam waktu O (n) linear, sedangkan Daftar melakukannya dalam kuadrat O (n ^ 2) waktu.
  2. Misalkan Anda ingin mengakses objek yang lebih besar lagi dan lagi, LinkedList menjadi sangat berguna.
  3. Deque () dan antrian () lebih baik diimplementasikan menggunakan LinkedList.
  4. Meningkatkan ukuran LinkedList jauh lebih mudah dan lebih baik setelah Anda berurusan dengan banyak objek yang lebih besar.

Semoga seseorang akan menemukan komentar ini bermanfaat.


Perhatikan bahwa saran ini untuk .NET, bukan Java. Dalam implementasi daftar tertaut Java Anda tidak memiliki konsep "simpul saat ini", jadi Anda harus melintasi daftar untuk setiap sisipan.
Jonathan Allen

Jawaban ini hanya sebagian benar: 2) jika elemen besar, maka buat elemen jenis Kelas bukan Struct, sehingga Daftar hanya memegang referensi. Kemudian ukuran elemen menjadi tidak relevan. 3) Deque dan antrian dapat dilakukan secara efisien dalam Daftar jika Anda menggunakan Daftar sebagai "buffer bundar", alih-alih melakukan penyisipan atau menghapus di awal. StephenCleary's Deque . 4) sebagian benar: ketika banyak objek, pro LL tidak perlu memori berdekatan yang besar; Kelemahannya adalah memori ekstra untuk pointer node.
ToolmakerSteve

-2

Begitu banyak jawaban rata-rata di sini ...

Beberapa implementasi daftar tertaut menggunakan blok yang mendasari node yang dialokasikan sebelumnya. Jika mereka tidak melakukan ini daripada waktu konstan / waktu linier kurang relevan karena kinerja memori akan buruk dan kinerja cache lebih buruk.

Gunakan daftar tertaut saat

1) Anda menginginkan keamanan utas. Anda dapat membangun algo yang lebih aman. Biaya penguncian akan mendominasi daftar gaya bersamaan.

2) Jika Anda memiliki struktur seperti antrian besar dan ingin menghapus atau menambahkan di mana saja tetapi akhirnya sepanjang waktu. > Daftar 100K ada tetapi tidak umum.


3
Pertanyaan ini adalah tentang dua implementasi C #, bukan daftar yang terhubung secara umum.
Jonathan Allen

Sama dalam setiap bahasa
user1496062

-2

Saya mengajukan pertanyaan serupa terkait dengan kinerja koleksi LinkedList , dan menemukan penerapan Cque karya Steven Cleary dari Deque adalah solusinya. Berbeda dengan koleksi Antrian, Deque memungkinkan barang bergerak on / off depan dan belakang. Ini mirip dengan daftar tertaut, tetapi dengan peningkatan kinerja.


1
Re pernyataan Anda bahwa Dequeadalah "mirip dengan linked list, tetapi dengan peningkatan kinerja" . Harap masukkan pernyataan itu: Dequelebih baik daripada LinkedList, untuk kode spesifik Anda . Mengikuti tautan Anda, saya melihat bahwa dua hari kemudian Anda mengetahui dari Ivan Stoev bahwa ini bukan inefisiensi dari LinkedList, tetapi inefisiensi dalam kode Anda. (Dan bahkan jika itu merupakan inefisiensi dari LinkedList, itu tidak akan membenarkan pernyataan umum bahwa Deque lebih efisien; hanya dalam kasus-kasus tertentu.)
ToolmakerSteve
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.