Что такое двунаправленный (двусвязный) список

Ответ

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

Структура узла: ... ↔ [ 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

Вот такая, блядь, двухсторонняя любовь получается. Удобно, но и возни с ней больше.