Каковы конкретные правила использования связанного списка вместо массива?

18

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

Это очень абстрактно, и я хотел бы получить конкретное объяснение того, почему следует использовать связанный список, а не массив. Я не очень опытный в программировании, поэтому у меня нет большого (если вообще) реального опыта.

rightfold
источник
3
Честно говоря, я не думаю, что примеры будут так много значить для вас, пока у вас не появится некоторый опыт написания чего-либо, а затем не увидите, как изменение структуры данных влияет на ваш код. Либо попробуйте написать что-то, что вас интересует, и выясните, какие структуры данных и контейнеры лучше всего подходят, либо посмотрите на некоторый существующий код и подумайте, почему был выбран каждый контейнер, и какой эффект от него произойдет.
бесполезно
Я просто не думаю, что можно с пользой сообщать о компромиссах и побочных эффектах выбора структуры данных для (неопытного) ОП без гораздо более сложного примера, чем здесь разумно. Примеры, приведенные в качестве ответов, хороши, и отвечают на вопрос в том виде, в котором они были заданы, но не являются (то, что я прочитал) подразумеваемым запросом на фактическое понимание.
бесполезно

Ответы:

37

Здесь есть нечто среднее между примером и аналогией. У вас есть кое-какие поручения, поэтому вы берете лист бумаги и пишете:

  • банка
  • бакалейные товары
  • отказаться от химчистки

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

  • банка
  • штампы
  • бакалейные товары
  • отказаться от химчистки

или вы могли бы набросать на тот, который у вас был:

  • банк ....... STAMPS
  • бакалейные товары
  • отказаться от химчистки

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

Тогда ваш мобильный телефон звонит, пока вы в банке "Эй, у меня есть марки, больше не бери". Вы просто вычеркиваете STAMPS из списка, вы не переписываете новый без STAMPS.

Теперь вы можете реализовать список поручений в коде (может быть, приложение, которое упорядочивает ваши поручения в зависимости от вашей географии), и есть реальный шанс, что вы на самом деле будете использовать связанный список для этого в коде. Вы хотите добавить и удалить много элементов, порядок вещей, но вы не хотите переписывать весь список после каждой вставки или удаления.

Кейт Грегори
источник
@ Денис, да, я знаю, благодаря семантике перемещения. Однако это не верно для всех языков, поэтому пример стоит.
Кейт Грегори
1
@KateGregory достаточно справедливо, но все же хорошо отметить, что «базовые» структуры данных часто лучше, чем классные структуры данных, которые мы изучаем в школе (или где-либо еще). Я не хочу, чтобы новые выпускники везде использовали LL, потому что это теоретически уместно, хотя в действительности это плохой выбор. Использование правильной структуры данных важно, и иногда люди слишком усложняют ее. Слегка связан этот разговор Джонатана Блоу (создателя "Braid") о структурах данных .
Денис
1
@Ray: связанный список может превзойти древовидную структуру, если вы ожидаете, что он будет порядка 4 элементов, а сравнения сравнительно дешевы. Я не знаю точно, где отсечка, где это не так. Кроме того, древовидная структура требует, чтобы ваши данные могли быть отсортированы, тогда как структура списка позволяет вам поддерживать порядок вставки (или любой произвольный порядок).
Дэвид Стоун
2
Я не думаю, что эта аналогия очень точна. Как люди, мы можем (по крайней мере, для такого короткого списка) найти элемент в O (1). Но если вы хотите сделать случайную вставку в связанный список, вам придется в среднем пройти половину элементов, что стоит O (n) только для того, чтобы найти позицию, куда вы хотите вставить. Тогда фактическая вставка стоит только O (1), но с массивом ваши затраты также «только» O (n). Так что причина не только в семантике перемещения, но и в алгоритме. Однако постоянный фактор, скрываемый big-O, намного больше для списка избранных из-за несмежного доступа к памяти.
5gon12eder
18
  • Хеш- таблицы, которые используют цепочку для разрешения коллизий хешей, обычно имеют один связанный список для каждой группы для элементов в этой области.
  • Простые распределители памяти используют свободный список неиспользуемых областей памяти, в основном связанный список с указателем списка внутри самой свободной памяти
  • В файловой системе FAT метаданные большого файла организованы как связанный список записей FAT.
Майкл Боргвардт
источник
12

«Стек вызовов» языка C реализован в виде связанного списка в двоичных API x86 (и большинстве других).

То есть, вызов процедуры на языке C следует дисциплине «первым пришел - последним вышел». Результат выполнения (возможно, рекурсивных) вызовов функций упоминается как «стек вызовов», а иногда даже просто «стек».

CALLИнструкция x86 заканчивается implmenting связанного списка , используя «стек вызовов». CALLИнструкция толкает содержимое регистра EIP%, адрес команды после в CALLна память стека. Пролог call-fuction помещает содержимое регистра% EBP, самого низкого адреса локальных переменных в вызывающей функции, в стековую память. Затем пролог вызываемой функции устанавливает% EBP в базу стека текущей функции.

Это означает, что% EBP является указателем на область памяти, в которой хранится адрес значения% EBP вызывающей функции. Это не что иное, как связанный список, реализованный частично через аппаратное обеспечение CALL.

Насколько это хорошо, это то, как процессоры x86 реализуют вызовы функций, особенно вызовы функций, где функция имеет свою собственную копию аргументов и локальные для функции переменные. Каждый вызов функции помещает некоторую информацию в «стек вызовов», который позволяет процессору определить, где он остановился в вызывающей функции, без вмешательства вызываемой функции или вызывающей функции.

Брюс Эдигер
источник
3

Связанный список можно использовать для реализации очереди сообщений.

Очередь сообщений - это структура, в которой мы храним информацию о событиях для последующей обработки. Например, когда пользователь нажимает клавишу или перемещает мышь, это событие. Приложение может быть занято в момент возникновения события, поэтому нельзя ожидать, что оно обработает событие в тот момент, когда оно произойдет. Таким образом, событие помещается в очередь сообщений (информация о том, какая клавиша была нажата или где перемещена мышь), и, когда у приложения остается свободное время, оно проверяет свою очередь сообщений, выбирает события из нее и обрабатывает их. (Это происходит за миллисекунды, поэтому это не заметно).

Из сценария использования, который я только что описал, должно быть очевидно, что нам никогда не нужен произвольный доступ к событиям, хранящимся в очереди сообщений; мы заботимся только о том, чтобы иметь возможность хранить в нем сообщения и извлекать их. Таким образом, имеет смысл использовать связанный список, который обеспечивает оптимальное время вставки / удаления.

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

Майк Накис
источник