Большинство раз я вижу, что люди пытаются использовать связанные списки, это кажется мне как бедные (или очень плохой) выбором. Возможно, было бы полезно исследовать обстоятельства, при которых связанный список или не является хорошим выбором структуры данных.
Идеально, ответы рассуждали бы о критериях для использования в выборе структуры данных, и какие структуры данных, вероятно, будут работать лучше всего при указанных обстоятельствах.
Править: Я должен сказать, я вполне впечатлен не только число, но и качество ответов. Я могу только принять один, но существует два или еще три, я должен был бы сказать, будет стоить принять, не было ли что-то немного лучше там. Только пара (особенно та, которую я закончил тем, что принял) указала на ситуации, где связанный список обеспечил реальное преимущество. Я действительно думаю, что Steve Jessop заслуживает своего рода поощрительной премии для предложения не всего один, но и три различных ответа, все из которых я нашел довольно впечатляющим. Конечно, даже при том, что это было отправлено только как комментарий, не ответ, я думаю, что запись в блоге Neil определенно стоит прочитать также - не только информативный, но и довольно интересный также.
Они могут быть полезны для параллельных структур данных. (Ниже приведен пример использования в реальном мире без одновременного использования - его бы не было, если бы @Neil не упомянул FORTRAN. ;-)
Например, ConcurrentDictionary
в .NET 4.0 RC используют связанные списки для связывания элементов, хешируемых с одной и той же корзиной.
Базовая структура данных для ConcurrentStack
также является связанным списком.
ConcurrentStack
- одна из структур данных, которые служат основой для нового пула потоков (с локальными «очередями», реализованными в виде стека, по существу). (Другой основной поддерживающей структурой является ConcurrentQueue
.)
Новый пул потоков, в свою очередь, обеспечивает основу для планирования работы новой библиотеки параллельных задач .
Так что они, безусловно, могут быть полезны - связанный список в настоящее время служит одной из основных поддерживающих структур по крайней мере одной великой новой технологии.
(Односвязный список делает убедительный выбор без блокировки - но не без ожидания - в этих случаях, потому что основные операции могут выполняться с помощью одного CAS (+ повторные попытки). В современной среде GC-d, такой как Java и .NET, проблемы ABA можно легко избежать. Просто оберните элементы, которые вы добавляете только что созданные узлы и не используют их повторно - пусть GC выполняет свою работу. На странице, посвященной проблеме ABA, также представлена реализация стека без блокировок, который фактически работает в .Net (и Java) с (GC-ed) Узел, содержащий элементы.)
Edit :
@ Neil:
на самом деле то, что вы упомянули о FORTRAN, напомнило мне, что такие же типы связанных списков могут быть обнаружен в, вероятно, наиболее часто используемой и часто используемой структуре данных в .NET:
простой общий .NET Dictionary
.
Не один, а множество связанных списков хранятся в массиве.
По сути, многие связанные списки хранятся в массиве. (по одному на каждый используемый сегмент.) Свободный список повторно используемых узлов "переплетается" между ними (если были удаления). Массив выделяется в начале / при повторном хешировании и узлах в нем хранятся цепи. Существует также свободный указатель - индекс в массиве - который следует за удалениями. ;-) Так что - хотите верьте, хотите нет - техника FORTRAN все еще живет. (... и больше нигде, кроме одной из наиболее часто используемых структур данных .NET ;-).
Они полезны, когда вам нужно высокоскоростное нажатие, выталкивание и вращение, и вы не против O (n) индексации.
Массивы - это структуры данных, с которыми обычно сравниваются связанные списки.
Обычно связанные списки полезны, когда вам нужно внести много изменений в сам список, в то время как массивы работают лучше, чем списки при прямом доступе к элементам.
Вот список операций, которые можно выполнять со списками и массивами, в сравнении с относительной стоимостью операции (n = длина списка / массива):
Это очень низкоуровневое сравнение этих двух популярных и основных структур данных, и вы можете видеть, что списки работают лучше в ситуациях, когда вам нужно внести много изменений в сам список (удаление или добавление элементов). С другой стороны, массивы работают лучше, чем списки, когда вам нужно напрямую обращаться к элементам массива.
С точки зрения распределения памяти списки лучше, потому что нет необходимости располагать все элементы рядом друг с другом. С другой стороны, есть (небольшие) накладные расходы на сохранение указателей на следующий (или даже на предыдущий) элемент.
Знание этих различий важно для разработчиков при выборе между списками и массивами в своих реализациях.
Обратите внимание, что это сравнение списков и массивов. Есть хорошие решения проблем, о которых здесь сообщается (например: списки пропуска, динамические массивы и т. Д.). В этом ответе я принял во внимание базовую структуру данных, о которой должен знать каждый программист.
В прошлом я использовал связанные списки (даже двусвязные списки) в приложениях C / C ++. Это было до .NET и даже до stl.
Я, вероятно, не стал бы сейчас использовать связанный список на языке .NET, потому что весь код обхода, который вам нужен, предоставляется вам через методы расширения Linq.
Двусвязный список - хороший выбор для определения порядка хэш-карты, который также определяет порядок элементов (LinkedHashMap в Java), особенно когда они упорядочены при последнем доступе:
Конечно, вы можете спорить о том, является ли кэш LRU хорошей идеей по сравнению с чем-то более сложным и настраиваемым, но если вы собираетесь его иметь, это довольно приличная реализация. Вы не хотите выполнять удаление из середины и добавление в конец для вектора или двухсторонней очереди при каждом доступе для чтения, но перемещение узла в хвост обычно нормально.
Односвязный список - хороший выбор для свободного списка в распределителе ячеек или пуле объектов:
Связанные списки очень гибкие: модифицируя один указатель, вы можете внести серьезные изменения, когда одна и та же операция в списке массивов будет очень неэффективной.
Односвязные списки - очевидная реализация общего типа данных «список» в языках функционального программирования:
(добавить (list x) (L))
и (append (list y) (L))
могут совместно использовать почти все свои данные. Нет необходимости копировать при записи на языке без записи. Функциональные программисты знают, как этим воспользоваться. Для сравнения, вектор или двухсторонняя очередь обычно медленно добавляются на любом конце, требуя (по крайней мере, в моем примере с двумя отдельными добавками), чтобы была сделана копия всего списка (вектора) или индексного блока и блок данных, добавляемый к (deque). На самом деле, там может быть что-то сказать о deque в больших списках, которые по какой-то причине нужно добавить в хвост, я недостаточно осведомлен о функциональном программировании, чтобы судить.
Связанные списки очень полезны, когда вам нужно сделать много вставок и удалений, но не слишком много искать в списке произвольной (неизвестной во время компиляции) длины.
Разделение и объединение (двунаправленных) списков очень эффективно.
Вы также можете комбинировать связанные списки - например, древовидные структуры могут быть реализованы как «вертикальные» связанные списки (отношения родитель / потомок), соединяющие вместе горизонтальные связанные списки (братья и сестры).
Использование списка на основе массива для этих целей имеет серьезные ограничения:
По моему опыту, реализация разреженных матриц и кучи Фибоначчи. Связанные списки дают вам больше контроля над общей структурой таких структур данных. Хотя я не уверен, что лучше всего реализовать разреженные матрицы с использованием связанных списков - возможно, есть лучший способ, но он действительно помог изучить тонкости разреженных матриц с использованием связанных списков в начальной стадии CS :)