Ответ 1
Оба сохраняют последовательность элементов, но используют разные методы.
Массив хранит элементы в последовательном порядке в памяти, т.е. выглядит следующим образом:
--------------------------------------------------------------------------------------
| item 1 | item 2 | item 3 | ... ... | item x | //here comes other stuff
--------------------------------------------------------------------------------------
Это означает, что элементы поочередно сохраняются в памяти.
A ((дважды) связанный) список, с другой стороны, сохраняет элементы следующим образом: он создает собственный "элемент списка" для каждого элемента; этот "элемент списка" содержит фактический элемент и указатель/ссылку/подсказку/etc для следующего "элемента списка". Если он дважды связан, он также содержит указатель/ссылку/подсказку/etc для предыдущего "элемента списка":
------------
------------ ---------- | item 4 |
| item 1 | | item 2 | | next ---+----...
| next ---+------->| next | ------------
------------ ---+------ ^
| |
| |
v |
---------- |
| item 3 | |
| next --+---+
----------
Это означает, что элементы могут распространяться по всей памяти и не сохраняться в определенных ячейках памяти.
Теперь, когда мы это знаем, мы можем сравнить некоторые обычные операции над последовательностями элементов:
-
Доступ к элементу по определенному индексу:. Используя массив , мы просто вычисляем смещение для этого индекса и имеем элемент в индексе.
Это очень дешево. С другой стороны, с списком мы не знаем априорно, где хранится элемент в индексе (поскольку все элементы могут быть где угодно в памяти), поэтому нам нужно перемещать элемент списка по элементу, пока мы находим элемент в индексе. Это дорогостоящая операция.
-
Добавление нового элемента в конце последовательности: Использование массива может привести к следующей проблеме: массивы обычно имеют фиксированный размер, поэтому если у нас есть ситуация, что наш массив уже полностью заполнен (см.
//here comes other stuff
), мы, вероятно, не можем добавить новый элемент, потому что может быть что-то еще. Итак, возможно, нам нужно скопировать весь массив. Однако, если массив не заполнен, мы можем просто поместить туда элемент.Используя список, мы должны сгенерировать новый "элемент списка" , поместить в него элемент и установить указатель
next
текущего последнего элемента в этот новый элемент списка. Обычно мы храним ссылку на текущий последний элемент, поэтому нам не нужно искать весь список, чтобы найти его. Таким образом, вставка нового элемента не является реальной проблемой в списках. -
Добавление нового элемента где-то посередине: Рассмотрим сначала массивы: здесь мы можем перейти к следующей ситуации: у нас есть массив с элементами 1 до 1000:
1 | 2 | 3 | 4 | 5 | 6 | ... | 999 | 1000 | free | free
Теперь мы хотим вставить
4.5
между4
и5
: это означает, что мы должны перемещать все элементы от5
до1000
по одной позиции вправо, чтобы освободить место для4.5
:1 | 2 | 3 | 4 | free | 5 | 6 | ... | 999 | 1000 | free || vv 1 | 2 | 3 | 4 | 4.5 | 5 | 6 | ... | 999 | 1000 | free
Перемещение всех этих элементов - очень дорогостоящая операция. Так что лучше не делайте этого слишком часто.
Теперь рассмотрим, как список может выполнить эту задачу: скажем, у нас есть следующий список:
1 -> 2 -> 3 -> 4 -> 5 -> ... -> 999 -> 1000
Опять же, мы хотим вставить
4.5
между4
и5
. Это можно сделать очень просто: мы создаем новый элемент списка и обновляем указатели/ссылки:1 -> 2 -> 3 -> 4 5 -> ... -> 999 -> 1000 | ^ +-> 4.5 -+
Мы просто создали новый элемент списка и создали "байпас" для его вставки - очень дешево (если у нас есть указатель/ссылка на элемент списка, новый элемент будет вставлен после).
Итак, подведите итог: Linked lists действительно приятны, когда дело доходит до вставки в случайные позиции (пока у вас есть указатель на соответствующий элемент списка). Если ваша операция включает динамическое добавление множества элементов и перемещение всех элементов, список может быть хорошим выбором.
Массив очень хорош, когда дело касается доступа к индексу. Если вашему приложению требуется очень часто обращаться к элементам в определенных положениях, вам следует использовать массив.
Известные вещи:
-
Решение проблемы с фиксированным размером для массивов: Как упоминалось ранее, (raw) массивы обычно имеют фиксированный размер. Однако эта проблема в настоящее время не имеет реальной точки, поскольку почти все языки программирования предоставляют механизмы для генерации массивов, которые растут (и, возможно, сокращаются) динамически - по мере необходимости. Это увеличение и сокращение могут быть реализованы таким образом, что мы амортизировали время выполнения O (1) для вставки и удаления элементов (в конце массива) и так, чтобы программисту не приходилось явно вызывать
grow
иshrink
. -
Поскольку списки предоставляют такие приятные свойства для вставок, их можно использовать в качестве базовых структур данных для деревьев поиска и т.д. I.e. вы строите дерево поиска, нижний уровень которого состоит из связанного списка.