Menu
Coddy logo textTech

Двусвязный список

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

Двусвязный список - это связный список, в котором каждый узел хранит два указателя: next (на следующий узел) и prev (на предыдущий). Эта дополнительная обратная ссылка позволяет обходить список в обоих направлениях и удалять узел за O(1), когда у вас уже есть ссылка на него, потому что вы можете сразу добраться до его предшественника, а не идти от головы. Нажмите воспроизведение выше, чтобы увидеть, как узлы связываются и перепривязываются в обе стороны.

Цена - один дополнительный указатель на узел и больше учёта: каждая вставка и удаление должны обновлять и next, и prev у соседей. Это структура, лежащая в основе многих деков и LRU-кэшей, где важно быстрое удаление с обоих концов.

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

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

Односвязный и двусвязный список

АспектОдносвязныйДвусвязный
Указателей на узел1 (next)2 (next + prev)
Обход назадНетДа
Удаление известного узлаO(n) (найти prev)O(1)
Накладные расходы памятиНижеВыше

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

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

ШагСтруктураДействие
НачалоnullПустой список: head и tail оба равны null
Добавить 1010Первый узел; head и tail указывают на него, prev = next = null
Добавить 2010 <-> 20Устанавливаем 10.next = 20 и 20.prev = 10; tail = 20
Добавить 3010 <-> 20 <-> 30Устанавливаем 20.next = 30 и 30.prev = 20; tail = 30
Удалить 2010 <-> 30Используя 20.prev и 20.next: устанавливаем 10.next = 30 и 30.prev = 10 за O(1)

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

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

Код Doubly Linked List

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

Код Doubly Linked List на Python

Python
1class Node:2    def __init__(self, value):3        self.value = value4        self.prev = None5        self.next = None6
7
8class DoublyLinkedList:9    def __init__(self):10        self.head = None11        self.tail = None12
13    def append(self, value):14        node = Node(value)15        if self.tail is None:16            self.head = self.tail = node17            return18        # Wire both directions: old tail <-> new node19        node.prev = self.tail20        self.tail.next = node21        self.tail = node22
23    def prepend(self, value):24        node = Node(value)25        if self.head is None:26            self.head = self.tail = node27            return28        node.next = self.head29        self.head.prev = node30        self.head = node31
32    def forward(self):33        values, current = [], self.head34        while current:35            values.append(current.value)36            current = current.next37        return values38
39    def backward(self):40        values, current = [], self.tail41        while current:42            values.append(current.value)43            current = current.prev44        return values45
46
47lst = DoublyLinkedList()48for value in [2, 3, 4]:49    lst.append(value)50lst.prepend(1)51
52print("Forward: ", lst.forward())53print("Backward:", lst.backward())
Запустите этот код в плейграунде Python

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

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

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

НАЧАТЬ