Оба хранят последовательность элементов, но используют разные методы.
Массив сохраняет элементы в последовательном порядке в памяти, то есть это выглядит следующим образом:
--------------------------------------------------------------------------------------
| item 1 | item 2 | item 3 | ... ... | item x | //here comes other stuff
--------------------------------------------------------------------------------------
Это означает, что элементы один за другим последовательно в памяти.
A ((вдвойне) связанный) list , с другой стороны, сохраняет элементы следующим образом: создает собственный «элемент списка» для каждого элемента; этот «элемент списка» содержит фактический элемент и указатель / ссылку / подсказку / и т. д. на следующий «элемент списка». Если он дважды связан, он также содержит указатель / ссылку / подсказку / и т. Д. На предыдущий «элемент списка»:
------------
------------ ---------- | item 4 |
| item 1 | | item 2 | | next ---+----...
| next ---+------->| next | ------------
------------ ---+------ ^
| |
| |
v |
---------- |
| item 3 | |
| next --+---+
----------
Это означает, что элементы могут быть распределены по всей памяти и не хранятся в определенных местах памяти.
Теперь, когда мы знаем это, мы можем сравнить некоторые обычные операции над последовательностями элементов:
Доступ к элементу по определенному индексу: Используя массив , мы просто вычисляем смещение для этого индекса и имеем элемент в индекс.
Это очень дешево. С списком , с другой стороны, мы не знаем a priori , где хранится элемент в индексе (поскольку все элементы могут быть где угодно в памяти), поэтому нам нужно пройтись по список элемент за элементом, пока мы не найдем элемент по индексу. Это дорогая операция.
Добавление нового элемента в конце последовательности: Использование массива это может привести к следующей проблеме: Массивы обычно имеют фиксированный размер, поэтому, если мы в ситуации, когда наш массив уже полностью заполнен (см. //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 -+
Мы просто создали новый элемент списка и сгенерировали своего рода «обход» для его вставки - очень дешево (если у нас есть указатель / ссылка на элемент списка, после которого будет добавлен новый элемент).
* * 1087
Итак, давайте подведем итоги: связанные списки действительно хороши, когда дело доходит до вставки в случайных позициях (если у вас есть указатель на соответствующий элемент списка). Если ваша операция предполагает динамическое добавление большого количества элементов и обход всех элементов, то список может быть хорошим выбором.
Массив очень хорош, когда дело доходит до доступа к индексу. Если вашему приложению очень часто требуется доступ к элементам в определенных позициях, вам лучше использовать массив.
Известные вещи:
Решение проблемы фиксированного размера для массивов. Как упоминалось ранее, (необработанные) массивы обычно имеют фиксированный размер.Однако, в настоящее время эта проблема больше не имеет смысла, так как почти все языки программирования предоставляют механизмы для генерации массивов, которые растут (и, возможно, сокращаются) динамически - по мере необходимости.Это увеличение и сжатие может быть реализовано так, что у нас есть амортизируемая среда выполнения O (1) для вставки и удаления элементов (в конце массива) и такая, что программист не должен вызывать grow
и shrink
явно.
Поскольку списки предоставляют такие приятные свойства для вставок, они могут использоваться в качестве базовых структур данных для деревьев поиска и т. д. Т.е. вы создаете дерево поиска, чьенижний уровень состоит из связанного списка.