Чем массив отличается от связного списка?
- Когда использовать связный список вместо массива?
- Какая сложность операций у массива и связного списка?
Массив — элементы подряд в памяти: доступ по индексу O(1), вставка в середину O(n); связный список — узлы со ссылками: вставка по известному месту O(1), доступ по индексу O(n).
- Массив хорошо работает с кэшем процессора — на практике обычно быстрее
- Список удобен, когда часто вставляют и удаляют в середине по уже найденной позиции
А ещё «Вставка в список O(1)» — только если узел уже найден; поиск места — O(n)
Подробно
Массив хранит элементы в непрерывном куске памяти. Адрес 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)».
- Выбирать список по теории, игнорируя локальность памяти.
Проверьте себя
Один верный вариант. Подумайте, потом откройте ответ.
Показать варианты и ответ
- ✗Связный список даёт доступ по индексу за O(1)
- ✗Массив вставляет в середину за O(1)
- ✗Связный список быстрее массива почти во всём: вставка O(1) в любом месте без поиска, доступ по индексу O(log n) за счёт указателей, лучше работает с кэшем
- ✓Массив — доступ по индексу O(1), вставка в середину O(n); список — вставка по известному месту O(1), доступ по индексу O(n)
Что спросят дальше
Связанные вопросы
- Как устроена хеш-таблица? Junior
Разбор собеседования с sotto
После собеседования sotto разбирает разговор: оценка, каждый вопрос — хорошо, частично или слабо, чего не хватило в ответе и над чем поработать к следующему разу. Первые 30 минут бесплатно.