Menu
Coddy logo textTech

Связный список

Последнее обновление

Связный список хранит последовательность как цепочку узлов, где каждый узел содержит значение и указатель на следующий узел. В отличие от массива, узлы не расположены в памяти непрерывно - вы следуете по указателям next, чтобы обойти список. Нажмите воспроизведение выше, чтобы увидеть, как узлы связываются в начале и в конце, как значение ищется обходом цепочки и как узел удаляется путём перенаправления указателя.

Вставка или удаление в начале - это O(1), потому что вы просто переустанавливаете голову. Достижение позиции в середине или конце - это O(n), потому что сначала нужно дойти туда. Этот компромисс -дешёвые концы, отсутствие произвольного доступа- и отличает связный список от массива.

Временная сложность

ОперацияСложностьПримечания
Вставка в началоO(1)Переустановить голову
Вставка в конецO(n)Сначала дойти до конца (O(1) с указателем на хвост)
ПоискO(n)Следовать по указателям next
Удаление головыO(1)Переустановить голову за неё
Доступ по индексуO(n)Нет произвольного доступа

Связный список против массива

АспектСвязный списокМассив
ПамятьРазбросанные узлы + указателиНепрерывный блок
Произвольный доступO(n)O(1)
Вставка/удаление в началеO(1)O(n) (сдвиг)
Дружественность к кэшуПлохаяХорошая

Разобранный пример

Построение списка [10, 20], добавление 5 в начало, затем удаление 20:

ШагСтруктураДействие
Началоhead -> nullПустой список
Вставка в начало 10head -> 10 -> nullПереустановить голову на новый узел, чей next - прежняя голова (null)
Вставка в конец 20head -> 10 -> 20 -> nullДойти до узла 10, установить его next на новый узел 20
Вставка в начало 5head -> 5 -> 10 -> 20 -> nullНовый узел 5 указывает на прежнюю голову 10; голова теперь указывает на 5
Удаление 20head -> 5 -> 10 -> nullДойти до 10, изменить его next с 20 на null; узел 20 отсоединён

Когда использовать связный список

Используйте, когдаИзбегайте, когда
Вы вставляете или удаляете на концах (или в узле, который держите) гораздо чаще, чем индексируетеВам нужен быстрый произвольный доступ по позиции (индексация O(1))
Размер сильно меняется и вы не хотите затрат на изменение размера/копированиеВы проходите плотные циклы, где локальность кэша определяет производительность
Вы строите очередь, стек или список смежностиПамять ограничена: каждый узел платит дополнительный указатель на элемент
Вам нужны стабильные ссылки на узлы, которые переживают вставки в других местахВы в основном читаете и редко изменяете, поэтому массив проще и быстрее

Код Linked List

Чистая, готовая к запуску реализация Linked List на Python, JavaScript, Java, C++, C. Выберите язык, скопируйте код или откройте его уже загруженным в плейграунде Coddy.

Код Linked List на Python

Python
1class Node:2    def __init__(self, value):3        self.value = value4        self.next = None5
6
7class LinkedList:8    def __init__(self):9        self.head = None10
11    def append(self, value):12        node = Node(value)13        if self.head is None:14            self.head = node15            return16        current = self.head17        while current.next:18            current = current.next19        current.next = node20
21    def find(self, value):22        current = self.head23        while current:24            if current.value == value:25                return True26            current = current.next27        return False28
29    def delete(self, value):30        # Re-link the previous node around the match31        current, prev = self.head, None32        while current:33            if current.value == value:34                if prev is None:35                    self.head = current.next36                else:37                    prev.next = current.next38                return True39            prev, current = current, current.next40        return False41
42    def __str__(self):43        values, current = [], self.head44        while current:45            values.append(str(current.value))46            current = current.next47        return " -> ".join(values) + " -> None"48
49
50lst = LinkedList()51for value in [3, 7, 1, 9]:52    lst.append(value)53
54print("List:    ", lst)55print("find(7): ", lst.find(7))56lst.delete(1)57print("After delete(1):", lst)
Запустите этот код в плейграунде Python

Часто задаваемые вопросы о связных списках

В чём разница между связным списком и массивом?
Массив хранит элементы в одном непрерывном блоке, что даёт произвольный доступ O(1), но вставки/удаления O(n), которые сдвигают элементы. Связный список хранит узлы где угодно в памяти, соединённые указателями, что даёт вставки/удаления O(1) в известной позиции, но доступ O(n), так как нужно обходить цепочку.
Когда следует использовать связный список?
Связные списки хороши, когда вы часто вставляете или удаляете на концах (или в узле, ссылку на который уже держите) и редко нуждаетесь в произвольном доступе - например, очереди, стеки, списки смежности и LRU-кэши. Если вам нужен быстрый индексированный доступ, массив или динамический массив обычно лучше.
Какова временная сложность связного списка?
Вставка или удаление в голове - O(1). Поиск или достижение хвоста без указателя на хвост - O(n), потому что вы следуете по указателям next один за другим. Произвольного доступа O(1) нет: индексация в связном списке - это O(n).
В чём разница между односвязным и двусвязным списком?
Односвязный список хранит только указатель next на узел, поэтому вы можете идти в одном направлении, а удаление узла требует ссылки на его предшественника. Двусвязный список добавляет указатель prev, что позволяет обход назад и удаление O(1) удерживаемого узла ценой дополнительного указателя на узел и большей возни при каждой вставке и удалении.
Почему вставка в конец O(n), если вставка в начало O(1)?
Вставка в начало лишь перенаправляет указатель head, что является постоянной работой. Достижение конца означает следование по указателям next от головы до самого конца, что составляет O(n). Отдельный указатель tail делает вставку в конец тоже O(1), поэтому реальные списки часто отслеживают оба конца.
Имеют ли связные списки вставку O(1) везде?
Нет - это распространённое заблуждение. Сама вставка - O(1) только тогда, когда вы уже держите указатель на узел, после которого вставляете. Поиск этой позиции по значению или индексу всё равно стоит O(n), потому что нужно обойти цепочку, чтобы добраться туда.
Coddy programming languages illustration

Освойте алгоритмы с Coddy

НАЧАТЬ