Получить доступ

Чем массив отличается от связного списка?

JuniorСтруктуры данных
Ещё спрашивают так:
  • Когда использовать связный список вместо массива?
  • Какая сложность операций у массива и связного списка?
Коротко

Массив — элементы подряд в памяти: доступ по индексу O(1), вставка в середину O(n); связный список — узлы со ссылками: вставка по известному месту O(1), доступ по индексу O(n).

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

А ещё «Вставка в список O(1)» — только если узел уже найден; поиск места — O(n)

Так подсказку видит пользователь sotto на живом собеседовании — за пару секунд, пока вы думаете вслух.

Подробно

Массив хранит элементы в непрерывном куске памяти. Адрес i-го элемента вычисляется арифметикой: начало + i × размер элемента. Поэтому:

  • доступ по индексу — O(1);
  • добавление в конец динамического массива — амортизированно O(1);
  • вставка и удаление в середине — O(n): нужно сдвинуть все элементы правее;
  • поиск значения — O(n), в отсортированном — O(log n) бинарным поиском.

Связный список хранит каждый элемент в отдельном узле со ссылкой на следующий (в двусвязном — и на предыдущий). Узлы разбросаны по памяти.

  • доступ к i-му элементу — O(n): нужно пройти по ссылкам;
  • вставка и удаление, если узел уже найден, — O(1): поменять ссылки;
  • вставка в начало — O(1);
  • дополнительная память на ссылки в каждом узле.

Практика. Процессор читает память блоками и кэширует их. Массив читается подряд и попадает в кэш почти идеально, а переходы по узлам списка вызывают промахи кэша. Поэтому даже там, где по О-большому список выигрывает, массив на реальном железе часто быстрее. Связные списки используют, когда нужны стабильные ссылки на элементы и частые вставки или удаления по уже известной позиции: LRU-кэш (хеш-таблица + двусвязный список), очереди задач в ядре ОС.

Пример

class Node:
    def __init__(self, value, next=None):
        self.value, self.next = value, next

# вставка после известного узла — O(1)
def insert_after(node, value):
    node.next = Node(value, node.next)

# доступ по индексу — O(n)
def get(head, i):
    while i > 0:
        head, i = head.next, i - 1
    return head.value

Что хотят услышать

  • Устройство в памяти обеих структур.
  • Сложность доступа, вставки, удаления, поиска.
  • Оговорка про поиск места перед вставкой в список.
  • Влияние кэша процессора — плюс.

Частые ошибки

  • «Вставка в связный список всегда O(1)».
  • Выбирать список по теории, игнорируя локальность памяти.

Что спросят дальше

Связанные вопросы

Закрытая бета

Подсказки на собеседовании — в реальном времени

sotto слышит вопрос интервьюера и показывает короткий ответ и план, как в блоке «Коротко». Оставьте контакт — пришлём доступ и промокод.

  • Промокод на 2 часа — сверх пробных 30 минут
  • Программа для Windows — скачать сразу, войти — через Яндекс ID или VK ID
  • Прямая связь с разработчиком: что улучшить — сделаем