Ответ
Двунаправленный (или двусвязный) список — это линейная структура данных, похожая на односвязный список, но с одним ключевым отличием: каждый узел хранит ссылки не только на следующий элемент, но и на предыдущий.
Структура узла:
... ↔ [ prev | data | next ] ↔ ...
Это позволяет обходить список в обоих направлениях: от начала к концу и от конца к началу.
Преимущества перед односвязным списком
- Двусторонний обход: Возможность двигаться в обе стороны.
- Эффективное удаление: Если есть указатель на узел, его можно удалить за
O(1), так как не нужно искать предыдущий элемент для перенаправления ссылок. - Простая вставка: Вставка до или после заданного узла выполняется за
O(1).
Недостатки
- Дополнительная память: Каждый узел хранит дополнительный указатель
prev, что увеличивает расход памяти. - Более сложная реализация: Операции вставки и удаления требуют обновления большего количества ссылок, что повышает вероятность ошибки.
Пример реализации на Python
class Node:
def __init__(self, data):
self.data = data
self.next = None # Ссылка на следующий узел
self.prev = None # Ссылка на предыдущий узел
class DoublyLinkedList:
def __init__(self):
self.head = None
self.tail = None
# Добавление элемента в конец списка
def append(self, data):
new_node = Node(data)
if self.head is None:
# Если список пуст, новый узел становится и головой, и хвостом
self.head = new_node
self.tail = new_node
return
# Связываем новый узел с текущим хвостом
self.tail.next = new_node
new_node.prev = self.tail
# Перемещаем указатель хвоста на новый узел
self.tail = new_node Ответ 18+ 🔞
А, ну ты глянь, какая хитрая жопа придумана! Двунаправленный список, блядь. Это типа как твой односвязный собрат, но с прибамбасом. Каждый узел там не просто тычется пальцем в следующего, а ещё и оглядывается через плечо на предыдущего, сука.
Представь себе цепочку: ... ↔ [ prev | data | next ] ↔ .... Туда-сюда можно ходить, как по коридору, блядь. Не надо разворачиваться нахуй и бежать с начала, если пролетел мимо нужного элемента.
Чем он крут, этот выпендрёжник?
- Ходи куда хочешь: От головы к хвосту, от хвоста к голове — полная свобода передвижения, ёпта. Не то что в односвязном, где назад хода нет, как в метро без обратного билета.
- Удаление — раз плюнуть: Поймал за шкирку нужный узел — и сразу его в утиль, за
O(1). Не надо, блядь, как в односвязном, полсписка пробегать, чтобы найти, кто на него ссылался, и перепривязать ссылочки. Тут у него уже есть ссылка на папашу, всё под рукой. - Вставка — проще пареной репы: Хочешь впихнуть новый узел ПЕРЕД кем-то или ПОСЛЕ — тоже
O(1). Ссылки на соседей есть, обновил их — и делу конец.
А где, блядь, подвох?
- Жрёт память: Каждый узел — это не два поля, а три, сука. Поле
prev— это лишние байты, которые сидят на шее. Если список овердохуища большой, эти мелочи складываются в кучу. - Геморрой с реализацией: Голову сломать можно, пока все эти
nextиprevправильно перепривяжешь. Запутаешься — и получишь, блядь, циклическую ссылку, из которой не выберешься, или вообще всё развалится. Терпения ебать ноль, когда дебажишь.
Вот, смотри, как это на Питоне выглядит, чтоб ты понимал масштаб:
class Node:
def __init__(self, data):
self.data = data
self.next = None # Тычется в следующего
self.prev = None # Оглядывается на предыдущего (вот он, прикол!)
class DoublyLinkedList:
def __init__(self):
self.head = None
self.tail = None # А вот и хвост, чтоб с конца быстро начинать
# Суём элемент в конец
def append(self, data):
new_node = Node(data)
if self.head is None:
# Если список пустой, новичок становится и царём, и богом
self.head = new_node
self.tail = new_node
return
# Цепляем новичка к текущему хвосту
self.tail.next = new_node
new_node.prev = self.tail
# И говорим: "Всё, парень, теперь ты — новый хвост, держись"
self.tail = new_node
Вот такая, блядь, двухсторонняя любовь получается. Удобно, но и возни с ней больше.