Я использую много списков и массивов, но я еще не встречал сценария, в котором список массивов не мог бы использоваться так же легко, если не проще, чем связанный список. Я надеялся, что кто-нибудь может дать мне несколько примеров того, когда связанный список заметно лучше.
Также проверьте это, stackoverflow.com/questions/322715/…
Возможный дубликат Массив против связанного списка
С.Лотт: Это неправда. Java ArrayList - это оболочка для массива с добавленными служебными функциями. Связанный список, очевидно, является связным списком. developer.classpath.org/doc/java/util/ArrayList-source.html



Преимущество списков проявляется в том случае, если вам нужно вставить элементы посередине и вы не хотите начинать изменять размер массива и перемещать элементы.
Вы правы в том, что обычно это не так. У меня было несколько очень конкретных случаев, но не слишком много.
Сдвиг и изменение размера массива - это то, что на самом деле происходит, когда вы выполняете инверсию в середине. Вам потребуется только сдвиг без изменения размера, если вы не достигнете предела амортизации.
Массивы имеют произвольный доступ O (1), но добавлять или удалять данные очень дорого.
Связанные списки действительно дешевы для добавления или удаления элементов в любом месте и для итерации, но произвольный доступ - это O (n).
Удаление элементов из конца массива происходит постоянно, как и вставка / удаление элементов из конца либо связанного списка. Посередине ... ни того, ни другого.
@Joey - это не вставка / удаление в конце связанного списка O (n)? Если вы уже не находитесь на предпоследней ссылке, вам все равно потребуется O (n) шагов, чтобы найти последний элемент, не так ли?
@ AlexMoore-Niemi: Для односвязного списка - да. Но у многих есть ссылки вперед и назад, и поэтому они сохраняют указатели на любой конец.
Наличие двусвязных списков заставит вас выполнять поиск вперед и назад, если ваш LL не имеет упорядоченных значений ... и все же худший сценарий - O (n)
«Связанные списки действительно дешевы, чтобы добавлять или удалять элементы где угодно и выполнять итерацию» - не совсем так. Если я хочу удалить элемент, который находится в середине связанного списка, мне придется выполнять итерацию с самого начала, пока я не дойду до этого элемента в списке. Его время O (n / 2), где n = количество элементов в списке. Из вашего ответа похоже, что вы предлагаете его постоянное время O (1), как в массиве. Это постоянное время для добавления / удаления главного / корневого узла связанного списка.
Перебирать связанные списки довольно дорого. Следование всем этим указателям по всей памяти полностью уничтожает локальность кеша.
Связанные списки предпочтительнее массивов, когда:
вам нужны постоянные вставки / удаления из списка (например, в вычислениях в реальном времени, где предсказуемость времени абсолютно критична)
вы не знаете, сколько элементов будет в списке. С массивами вам может потребоваться повторно объявить и скопировать память, если массив становится слишком большим.
вам не нужен произвольный доступ к каким-либо элементам
вы хотите иметь возможность вставлять элементы в середину списка (например, очередь с приоритетом)
Массивы предпочтительнее, когда:
вам нужен индексированный / произвольный доступ к элементам
вы заранее знаете количество элементов в массиве, чтобы можно было выделить правильный объем памяти для массива
вам нужна скорость при последовательном прохождении всех элементов. Вы можете использовать математику указателя в массиве для доступа к каждому элементу, тогда как вам нужно искать узел на основе указателя для каждого элемента в связанном списке, что может привести к ошибкам страниц, что может привести к снижению производительности.
память вызывает беспокойство. Заполненные массивы занимают меньше памяти, чем связанные списки. Каждый элемент в массиве - это просто данные. Каждому узлу связанного списка требуются данные, а также один (или несколько) указателей на другие элементы связанного списка.
Списки массивов (например, в .Net) дают вам преимущества массивов, но динамически выделяют для вас ресурсы, так что вам не нужно слишком беспокоиться о размере списка, и вы можете удалять элементы по любому индексу без каких-либо усилий или повторного использования. перемешивание элементов. С точки зрения производительности массивы работают медленнее, чем необработанные массивы.
Хорошее начало, но при этом не учитываются важные вещи: списки поддерживают совместное использование структуры, массивы более плотные и имеют лучшую локализацию.
На практике разница в производительности массивов и массивов незначительна. Предполагается, что вы сравниваете сопоставимые объекты и, например, когда заранее знаете размер, вы сообщаете об этом массиву.
С каких это пор LinkedList имеет O (1) вставок / удалений (что, я полагаю, вы имеете в виду, когда говорите постоянное время вставляет / удаляет)? Вставка материала в середину LinkedList всегда O (n)
Связанные списки содержат O (1) вставок, если вы уже оказались в месте вставки (через итератор). «Не всегда, - подумал.
Использование связанных списков для очередей с приоритетом - очень глупая идея. Динамические кучи с поддержкой массива допускают вставку с амортизацией O (lg n) и логарифмическое удаление-min в наихудшем случае и являются одними из самых быстрых практических структур очереди с приоритетами.
@Lamar Можете ли вы дать мне реальный пример вставки / удаления в постоянное время? Я не очень хорошо понимаю.
@ İsmailKocacan Представьте себе алгоритм обхода, который заботится только о «текущем» элементе и соседних элементах, и вам нужно иметь возможность вставить новый элемент рядом с текущим элементом, или вам нужно удалить элемент из этого узкого окна, а затем вам не нужен массив с произвольным доступом. Подумайте о том, как массив похож на словарь с ключами типа int; тогда как LinkedList похож на аудиокассету, проходящую через головку магнитной ленты. Раньше аудиоредактор мог соединять / удалять часть ленты в заданном месте. Было бы разумно расположить эту точку рядом с головкой ленты.
Лучший пример, который я могу придумать для реального использования, - это Rolodex, отсортированный по фамилии. Если вам нужно было добавить новые записи, отредактировать старые записи, удалить старые записи, возможно, имеет смысл выполнить все эти задачи в порядке Rolodex, поэтому вы должны пройти каждую карточку и вставлять / редактировать / удалять по мере необходимости. .
Прочитав код своего профессора, я думаю, что увижу причину в вашем объяснении. В связанном списке вы не обязательно храните объект или указатель на него, у вас есть набор узловых объектов, которые хранят указатели друг на друга и на соответствующий объект. Например, если у меня есть три объекта Person - Том, Дик и Гарри - и мне нужен связанный список, я бы создал узел, в котором хранится Том - или указатель на него, - и указатель на другой узел, в котором хранятся оба Дик - или указатель на него - и указатель на другой узел, в котором хранится Гарри - или указатель на него.
Это похоже на дек?
@Pacerier В случае двусвязного списка у вас может быть индивидуальная реализация со ссылкой на элемент в середине, и ссылка будет регулировать его положение при вставке / удалении. Это позволит получить O (1) для вставок точно посередине.
Хм, я думаю, Arraylist можно использовать в следующих случаях:
Например, вам нужно импортировать и получить доступ ко всем элементам в списке контактов (размер которого вам неизвестен)
Чтобы добавить к другим ответам, большинство реализаций списков массивов резервируют дополнительную емкость в конце списка, чтобы новые элементы могли быть добавлены в конец списка за время O (1). Когда емкость списка массивов превышена, внутри выделяется новый, более крупный массив, а все старые элементы копируются. Обычно новый массив вдвое больше старого. Это означает, что в среднем, добавление новых элементов в конец списка массивов является операцией O (1) в этих реализациях. Таким образом, даже если вы не знаете количество элементов заранее, список массивов может быть быстрее, чем связанный список для добавления элементов, если вы добавляете их в конце. Очевидно, что вставка новых элементов в произвольные места в списке массивов по-прежнему является операцией O (n).
Доступ к элементам в списке массивов также быстрее, чем в связанном списке, даже если доступ является последовательным. Это связано с тем, что элементы массива хранятся в непрерывной памяти и могут быть легко кэшированы. Узлы связанного списка потенциально могут быть разбросаны по множеству разных страниц.
Я бы рекомендовал использовать только связанный список, если вы знаете, что собираетесь вставлять или удалять элементы в произвольных местах. Списки массивов будут быстрее почти для всего остального.
Кроме того, вы также можете реализовать связанные списки (в смысле абстрактного типа данных), используя динамические массивы. Таким образом, вы можете использовать кэш компьютера, амортизируя вставки и удаления с постоянным временем в начале списка, а также амортизируя вставки и удаления с постоянным временем в середине списка, когда у вас есть индекс элемента, после которого вставка должна или индекс удаляемого элемента (сдвиги / отмены не требуются). Хорошая ссылка для этого - CLRS 10.3.
Используйте связанный список для Radix Sort над массивами и для полиномиальных операций.
Это наиболее часто используемые реализации Collection.
ArrayList:
вставить / удалить в конце обычно O (1) худший случай O (n)
вставить / удалить в середине O (n)
получить любую позицию O (1)
LinkedList:
вставить / удалить в любой позиции O (1) (обратите внимание, есть ли у вас ссылка на элемент)
получить в середине O (n)
получить первый или последний элемент O (1)
Вектор: не используйте его. Это старая реализация, похожая на ArrayList, но со всеми синхронизированными методами. Это неправильный подход для общего списка в многопоточной среде.
HashMap
вставить / удалить / получить по ключу в O (1)
TreeSet вставить / удалить / содержит в O (журнал N)
HashSet вставить / удалить / содержит / размер в O (1)
1) Как объяснялось выше, операции вставки и удаления дают хорошую производительность (O (1)) в LinkedList по сравнению с ArrayList (O (n)). Следовательно, если в приложении требуется частое добавление и удаление, то LinkedList - лучший выбор.
2) Операции поиска (получения метода) выполняются быстро в Arraylist (O (1)), но не в LinkedList (O (n)), поэтому, если требуется меньше операций добавления и удаления и требуется больше операций поиска, ArrayList будет вашим лучшим выбором.
Я думаю, что основное различие заключается в том, нужно ли вам часто вставлять или удалять что-то из верхней части списка.
С массивом, если вы удалите что-то из верхней части списка, сложность будет o (n), потому что все индексы элементов массива должны будут сдвинуться.
В связанном списке это o (1), потому что вам нужно только создать узел, переназначить заголовок и назначить ссылку на следующий как на предыдущий заголовок.
При частой вставке или удалении в конце списка предпочтительнее использовать массивы, потому что сложность будет o (1), переиндексация не требуется, но для связанного списка это будет o (n), потому что вам нужно идти от головы до последнего узла.
Я думаю, что поиск как в связанном списке, так и в массивах будет o (log n), потому что вы, вероятно, будете использовать двоичный поиск.
Все зависит от того, какой тип операции вы выполняете во время итерации, все структуры данных имеют компромисс между временем и памятью, и в зависимости от наших потребностей мы должны выбрать правильный DS. Таким образом, в некоторых случаях LinkedList быстрее, чем массив, и наоборот. Рассмотрим три основные операции со структурами данных.
Поскольку массив представляет собой структуру данных на основе индекса, поиск array.get (index) займет время O (1), в то время как связанный список не является индексным DS, поэтому вам нужно будет перейти до индекса, где index <= n, n - размер связанного списка, поэтому массив быстрее связанного списка, когда есть произвольный доступ к элементам.
В: Так в чем же прелесть этого?
Поскольку массивы представляют собой непрерывные блоки памяти, большие их фрагменты будут загружены в кеш при первом доступе, что позволяет сравнительно быстро получить доступ к оставшимся элементам массива, поскольку мы получаем доступ к элементам в массиве, локальность ссылки также увеличивается, что снижает улов промахов, локальность кэша относится к операциям, находящимся в кеше и, таким образом, выполняется намного быстрее по сравнению с памятью, в основном в массиве мы максимизируем шансы последовательного доступа к элементам, находящимся в кеше. Хотя связанные списки не обязательно находятся в непрерывных блоках памяти, нет гарантии, что элементы, которые появляются последовательно в списке, действительно расположены рядом друг с другом в памяти, это означает меньшее количество попаданий в кеш, например больше промахов в кэше, потому что нам нужно читать из памяти для каждого доступа к элементу связанного списка, что увеличивает время, необходимое для доступа к ним, и снижает производительность, поэтому, если мы выполняем больше операций с произвольным доступом, или поиск, массив будет быстрым, как описано ниже.
Это легко и быстро в LinkedList, поскольку вставка - это операция O (1) в LinkedList (в Java) по сравнению с массивом, рассмотрите случай, когда массив заполнен, нам нужно скопировать содержимое в новый массив, если массив заполняется, что делает вставку элемент в ArrayList of O (n) в худшем случае, в то время как 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 использует что-то получше, чем просто массив.
Список - это интерфейс, а не класс
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 хороши для однократной записи и многократного чтения или добавлений, но плохи при добавлении / удалении спереди или посередине.
Обратите внимание, что O(1) для вставки после элемента в связанном списке истинен, только если у вас уже есть указатель на элемент, после которого вы должны вставить новый узел. В противном случае вам придется перебирать связанный список, пока вы не найдете правильную позицию, и это будет O(N).
На самом деле локальность памяти имеет огромное влияние на производительность при реальной обработке.
Более широкое использование потоковой передачи на диск при обработке «больших данных» по сравнению с произвольным доступом показывает, как структурирование вашего приложения вокруг этого может значительно повысить производительность в более крупном масштабе.
Если есть какой-либо способ доступа к массиву последовательно, это, безусловно, лучший результат. Проектирование с этой целью должно быть рассмотрено, по крайней мере, если производительность важна.
Массивы, безусловно, являются наиболее широко используемыми структурами данных. Однако связанные списки оказываются полезными по-своему, когда массивы неуклюжи - или, мягко говоря, дороги.
Связанные списки полезны для реализации стеков и очередей в ситуациях, когда их размер может меняться. Каждый узел в связанном списке можно нажимать или выталкивать, не нарушая работу большинства узлов. То же самое касается вставки / удаления узлов где-то посередине. Однако в массивах все элементы должны быть сдвинуты, что является дорогостоящей работой с точки зрения времени выполнения.
Бинарные деревья и бинарные деревья поиска, хеш-таблицы и попытки - вот некоторые из структур данных, в которых - по крайней мере в C - вам нужны связанные списки в качестве основного ингредиента для их построения.
Однако следует избегать использования связанных списков в ситуациях, когда ожидается, что он сможет вызывать любой произвольный элемент по его индексу.
Простой ответ на вопрос можно дать, используя следующие пункты:
Массивы должны использоваться, когда требуется набор элементов данных аналогичного типа. Принимая во внимание, что связанный список представляет собой набор связанных элементов данных смешанного типа, известных как узлы.
В массиве можно посетить любой элемент за O (1) раз. Принимая во внимание, что в связанном списке нам нужно будет пройти весь связанный список от головы до требуемого узла за время O (n).
Для массивов необходимо изначально указать конкретный размер. Но связанные списки имеют динамический размер.
В Java ArrayList и LinkedList используют точно такой же код, кроме конструктора. Ваш «список массивов ... используемый так же легко, как связанный список» не имеет смысла. Приведите пример того, что ArrayList «проще», чем LinkedList.