Когда я должен использовать список против LinkedList - PullRequest
362 голосов
/ 04 октября 2008

Когда лучше использовать Список против LinkedList ?

Ответы [ 15 ]

258 голосов
/ 04 октября 2008

В большинстве случаев List<T> более полезно. LinkedList<T> будет иметь меньшую стоимость при добавлении / удалении элементов в середине списка, тогда как List<T> может только дешево добавляться / удаляться в конце списка.

LinkedList<T> эффективен только в том случае, если вы обращаетесь к последовательным данным (в прямом или обратном направлении) - произвольный доступ является относительно дорогим, поскольку он должен обходить цепочку каждый раз (следовательно, поэтому у него нет индексатора). Однако, поскольку List<T> по сути является просто массивом (с оберткой), произвольный доступ вполне допустим.

List<T> также предлагает множество методов поддержки - Find, ToArray и т. Д .; однако, они также доступны для LinkedList<T> с .NET 3.5 / C # 3.0 с помощью методов расширения - так что это менее важный фактор.

200 голосов
/ 15 октября 2011

Думая о связанном списке как о списке, можно немного ввести в заблуждение. Это больше похоже на цепь. На самом деле, в .NET LinkedList<T> даже не реализует IList<T>. В связанном списке нет реальной концепции индекса, хотя может показаться, что она есть. Конечно, ни один из методов, предоставленных в классе, не принимает индексы.

Связанные списки могут быть односвязными или двусвязными. Это относится к тому, имеет ли каждый элемент в цепочке ссылку только на следующий (односвязный) или на оба предыдущих / следующих элемента (двусвязный). LinkedList<T> имеет двойную связь.

Внутренне, List<T> поддерживается массивом. Это обеспечивает очень компактное представление в памяти. И наоборот, LinkedList<T> включает дополнительную память для хранения двунаправленных связей между последовательными элементами. Таким образом, объем памяти LinkedList<T> обычно будет больше, чем для List<T> (с учетом того, что List<T> может иметь неиспользуемые элементы внутреннего массива для повышения производительности во время операций добавления).

Они также имеют различные характеристики производительности:

Append

  • LinkedList<T>.AddLast(item) постоянное время
  • List<T>.Add(item) амортизированное постоянное время, линейный наихудший случай

Prepend

  • 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) линейное время

Count

  • 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> предлагает линейное время, так как дополнительные элементы в списке должны быть перемешаны после вставки / удаления.

114 голосов
/ 04 октября 2008

Связанные списки обеспечивают очень быструю вставку или удаление члена списка. Каждый член в связанном списке содержит указатель на следующий член в списке, поэтому для вставки члена в позицию i:

  • обновить указатель в элементе i-1, чтобы он указывал на новый элемент
  • установить указатель в новом элементе, чтобы он указывал на элемент i

Недостатком связанного списка является то, что произвольный доступ невозможен. Чтобы получить доступ к члену, необходимо пройти по списку, пока не будет найден нужный участник.

96 голосов
/ 18 сентября 2012

Редактировать

Пожалуйста, прочитайте комментарии к этому ответу. Люди утверждают, что я не делал правильные тесты. Я согласен, что это не должно быть принятым ответом. Как я был узнав, что я сделал несколько тестов и хотел поделиться ими.

Оригинальный ответ ...

Я нашел интересные результаты:

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

Даже если вы обращаетесь только к данным, это существенно медленнее !! Я говорю, никогда не используйте связанный список.




Вот еще одно сравнение, выполняющее много вставок (мы планируем вставить элемент в середине списка)

Связанный список (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;

Связанный список, имеющий ссылку на место, куда вставить (0,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;

Таким образом, только если вы планируете вставить несколько элементов, и у вас также где-то есть ссылка на то, куда вы планируете вставить элемент, тогда используйте связанный список. Просто потому, что вам нужно вставить много элементов, это не делает это быстрее, потому что поиск места, куда вы хотели бы вставить, требует времени.

17 голосов
/ 04 октября 2008

Разница между List и LinkedList заключается в их базовой реализации. Список - это массив на основе массива (ArrayList). LinkedList - это основанная на указателе узла коллекция (LinkedListNode). Что касается использования уровня API, то оба они в значительной степени одинаковы, поскольку оба реализуют один и тот же набор интерфейсов, таких как ICollection, IEnumerable и т. Д.

Ключевое различие возникает, когда производительность имеет значение. Например, если вы реализуете список со сложной операцией «INSERT», LinkedList превосходит List. Поскольку LinkedList может сделать это за O (1) раз, но List может потребоваться расширить размер базового массива. Для получения дополнительной информации вы можете прочитать об алгоритмическом различии между LinkedList и структурами данных массива. http://en.wikipedia.org/wiki/Linked_list и Массив

Надеюсь, это поможет,

15 голосов
/ 25 марта 2015

Мой предыдущий ответ был недостаточно точным. Как верно было ужасно: D Но теперь я могу опубликовать гораздо более полезный и правильный ответ.


Я провел несколько дополнительных тестов. Вы можете найти его источник по следующей ссылке и заново проверить его в своей среде: https://github.com/ukushu/DataStructuresTestsAndOther.git

Краткие результаты:

  • Массив необходимо использовать:

    • Так часто, как это возможно. Это быстро и занимает наименьший диапазон ОЗУ для информации того же объема.
    • Если вы знаете точное количество необходимых клеток
    • Если данные сохранены в массиве <85000 b </li>
    • При необходимости высокая скорость произвольного доступа
  • Список необходимо использовать:

    • При необходимости добавить ячейки в конец списка (часто)
    • При необходимости добавить ячейки в начале / середине списка (НЕ ЧАСТО)
    • Если данные сохранены в массиве <85000 b </li>
    • При необходимости высокая скорость произвольного доступа
  • LinkedList необходимо использовать:

    • При необходимости добавить ячейки в начале / середине / конце списка (часто)
    • При необходимости только последовательный доступ (вперед / назад)
    • Если вам нужно сохранить БОЛЬШИЕ предметы, но количество предметов невелико.
    • Лучше не использовать для большого количества элементов, так как он использует дополнительную память для ссылок.

Подробнее:

введите сюда описание изображения Интересно знать:

  1. LinkedList<T> внутренне не является списком в .NET. Это даже не реализует IList<T>. И поэтому отсутствуют индексы и методы, связанные с индексами.

  2. LinkedList<T> - коллекция на основе указателя узла. В .NET это в двусвязной реализации. Это означает, что предыдущие / следующие элементы имеют ссылку на текущий элемент. И данные фрагментированы - разные объекты списка могут быть расположены в разных местах оперативной памяти. Также для LinkedList<T> будет использоваться больше памяти, чем для List<T> или массива.

  3. List<T> в .Net - альтернатива Java ArrayList<T>. Это означает, что это оболочка массива. Таким образом, он выделяется в памяти как один непрерывный блок данных. Если выделенный размер данных превышает 85000 байт, он будет перемещен в кучу больших объектов. В зависимости от размера это может привести к фрагментации кучи (легкая форма утечки памяти). Но в то же время, если размер <85000 байт - это обеспечивает очень компактное и быстрое представление в памяти. </p>

  4. Один непрерывный блок предпочтителен для производительности произвольного доступа и потребления памяти, но для коллекций, которым необходимо регулярно менять размер, структуру, такую ​​как массив, обычно необходимо копировать в новое местоположение, тогда как связанный список должен управлять память для вновь вставленных / удаленных узлов.

11 голосов
/ 25 ноября 2012

Основным преимуществом связанных списков над массивами является то, что ссылки дают нам возможность эффективно переупорядочивать элементы. Седжвик, р. 91

2 голосов
/ 19 августа 2014

Обычное обстоятельство для использования LinkedList выглядит следующим образом:

Предположим, вы хотите удалить много определенных строк из списка строк большого размера, скажем, 100 000. Строки для удаления можно найти в HashSet dic, и список строк, как полагают, содержит от 30000 до 60000 таких строк для удаления.

Тогда какой тип списка лучше всего подходит для хранения 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();
}
2 голосов
/ 04 октября 2008

Когда вам нужен встроенный индексированный доступ, сортировка (и после этого двоичного поиска) и метод ToArray (), вы должны использовать List.

1 голос
/ 08 декабря 2017

По сути, List<> в .NET - это оболочка над массивом . LinkedList<> - это связанный список . Таким образом, вопрос сводится к тому, какова разница между массивом и связанным списком, и когда следует использовать массив вместо связанного списка. Вероятно, два наиболее важных фактора, которые вы решите использовать, сводятся к следующему:

  • Связанные списки имеют гораздо лучшую производительность вставки / удаления, если вставки / удаления не находятся на последнем элементе в коллекции. Это связано с тем, что массив должен сдвигать все оставшиеся элементы, которые идут после точки вставки / удаления. Однако, если вставка / удаление находится в конце списка, этот сдвиг не требуется (хотя может потребоваться изменить размер массива, если его емкость превышена).
  • Массивы имеют гораздо лучшие возможности доступа. Массивы могут быть проиндексированы напрямую (в постоянное время). Связанные списки должны быть пройдены (линейное время).
...