Что такое связный список, его структура и отличия от массива/слайса?

Ответ

Связный список (Linked List) — это базовая динамическая структура данных, состоящая из последовательности узлов (nodes).

Каждый узел содержит две части:

  1. Данные (Data): Полезная информация, которую хранит узел.
  2. Указатель (Next): Ссылка на следующий узел в последовательности. У последнего узла указатель равен nil.

В двусвязном списке узел также содержит указатель на предыдущий узел (Prev).

Структура узла в Go:

// Узел для односвязного списка
type Node struct {
    data int
    next *Node
}

Ключевые характеристики и отличия от массива/слайса:

Характеристика Связный список Массив / Слайс
Хранение в памяти Элементы хранятся вразброс, связаны указателями. Элементы хранятся в непрерывном блоке памяти.
Доступ к элементу Последовательный, O(n). Нужно пройти все узлы от начала. Прямой по индексу, O(1).
Вставка/Удаление Эффективно в начале/конце (O(1)), если есть указатели на голову/хвост. Неэффективно в начале/середине (O(n)), требует сдвига элементов.
Размер Динамический, легко изменяется во время выполнения. В Go слайсы динамические, но расширение может вызвать переаллокацию всего массива.
Доп. память Требует дополнительную память для хранения указателей. Не требует доп. памяти на указатели между элементами.

Вывод: Связные списки идеально подходят для задач, где часто выполняются операции вставки/удаления элементов, а доступ по индексу не требуется (например, реализации стека или очереди).

Ответ 18+ 🔞

А, ну это же классика, блядь! Связный список, ёпта! Представь себе цепочку пьяных гопников, идущих из подъезда в магазин за сигаретами.

Каждый такой гопник — это узел. У него в одной руке бутылка — это его данные, полезная информация. А другой рукой он держит за шкирку следующего гопника — это указатель на следующего. Последний в цепочке никого не держит, рука свободна — это nil, блядь.

А если это двусвязный список, так это вообще цирк: каждый гопник держит и того, кто впереди, и того, кто сзади. Полный бардак, но зато можно и вперед, и назад двигаться.

Вот как это выглядит в коде, если бы гопники писали на Go:

// Один такой гопник-узел
type Gopnik struct {
    butilka string // Данные: что в бутылке? "Балтика 9"
    next    *Gopnik // Указатель: кого он держит за шкирку?
}

А теперь смотри, в чём главный пиздец и отличие от нормального, человеческого массива или слайса:

  • Как стоят? Гопники из списка стоят вразнобой, где попало, лишь бы держались друг за друга. А элементы в массиве — как солдаты на параде, ровненько, плечом к плечу, в одном сплошном строю.
  • Найти пятого? Чтобы найти пятого гопника в цепочке, тебе придётся, блядь, начать с первого, отсчитать: раз, два, три, четыре, пять — вот он, O(n). В строю солдат ты сразу крикнул: "Пятый, шаг вперёд!" — и он вышел, O(1), потому что знает своё место.
  • Вставить нового в середину? В цепочке гопников — легко, ёпта! Подошёл, разорвал хватку между двумя, нового впихнул, чтобы он взял одного за шкирку, а второй взял его. Делов на минуту, O(1), если знаешь место. А в строю солдат, чтобы вставить нового в середину, придётся всем, кто сзади, дать команду "Шаг назад!", чтобы место освободить. Суета, O(n).
  • Расшириться? Цепочке гопников похуй, хоть сто новых прибей, лишь бы руки не оторвались. Список динамический. А строю солдат иногда не хватает плаца, приходится всей толпой перебегать на новый, большой плац — это переаллокация, тоже может быть затратно.

Так зачем же этот гопницкий цирк нужен? А затем, сука, что когда тебе похуй на поиск по номеру, но нужно постоянно кого-то в начало цепочки ставить или из конца выкидывать (очередь, стек) — это идеально! Быстро и без лишних телодвижений.

Вот и весь высер. Вывод: списки — они для специфичных задач, где главное — ловко перецеплять указатели, а не бегать по индексам.