Какие плюсы и минусы у связного списка (LinkedList)?

Ответ

Плюсы:

  • Быстрая вставка/удаление в начале/конце или при известном узле: Операция выполняется за O(1), так как требуется только обновить несколько ссылок.
  • Динамический размер: Память выделяется по мере добавления элементов, нет необходимости в предварительном резервировании, как в массиве.
  • Эффективность при частых структурных изменениях: Если основная операция — добавление/удаление в середине последовательности (при условии, что узел уже найден), связный список предпочтительнее массива, где пришлось бы сдвигать элементы.

Минусы:

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

В контексте Dart/Flutter, структура данных LinkedList используется редко. Для большинства задач предпочтительнее встроенный List<E>, который является динамическим массивом (growable list) и обеспечивает быстрый доступ по индексу O(1).

// Пример реализации двусвязного списка в Dart
class ListNode<T> {
  T value;
  ListNode<T>? next;
  ListNode<T>? prev;

  ListNode(this.value);
}

class DoublyLinkedList<T> {
  ListNode<T>? head;
  ListNode<T>? tail;

  void addToTail(T value) {
    final newNode = ListNode(value);
    if (tail == null) {
      head = tail = newNode;
    } else {
      tail!.next = newNode;
      newNode.prev = tail;
      tail = newNode;
    }
  }
}

Ответ 18+ 🔞

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

Плюсы, которые прям радуют глаз:

  • Впихнуть или выпихнуть элемент в начале, в конце или если ты уже на него наткнулся — это вообще раз плюнуть, O(1). Просто перекинул пару ссылок, типа «эй, Вань, теперь ты стоишь за Петькой», и всё, дело в шляпе. Быстро, как ядрёна вошь.
  • Размер динамический. Не надо, как с массивом, заранее орать «мама, мне на 100 человек колбасы!», а потом половину выбрасывать. Выделяешь память под каждого нового чувака по мере того, как он в очередь встаёт. Удобно, волнение ебать.
  • Если у тебя там постоянная движуха в середине — вставил, выкинул, переставил — и ты УЖЕ знаешь, к кому лезешь, то список рулит. В массиве пришлось бы всё, что после, сдвигать, как шкаф с посудой, а тут — чих-пых и готово.

Но минусы, бля, тоже овердохуища:

  • Доступ по индексу — это пиздец медленно. Хочешь найти 25-го человека в очереди? Придётся с начала идти и вслух считать: раз, два, три... двадцать пять. O(n), Карл! В массиве ты сразу прыгнул бы на нужную ячейку. Тут — хуй с горы, иди пешком.
  • Жрёт память. Каждый узел — это не только сама колбаса (данные), но и две бумажки: «кто перед тобой» и «кто сзади» (ссылки prev и next). Для каждой буханки — свой секьюрити.
  • Кэш-локальность накрылась медным тазом. Элементы в памяти разбросаны, как говно в проруби. Процессору не за что зацепиться, чтобы предугадать, что грузить дальше. В массиве всё аккуратненько рядком лежит, как на складе, и работает в разы шустрее.

Вот смотри, в Dart/Flutter на эту манду с ушами (LinkedList) смотрят как на диковинку. В 99% случаев все юзают обычный List<E> — это ж динамический массив, быстрый, удобный, родной. К нему по индексу обращаться — раз плюнуть.

// Вот, смотри, как эту хрень на Dart'е можно наколхозить
class ListNode<T> {
  T value;
  ListNode<T>? next;
  ListNode<T>? prev;

  ListNode(this.value);
}

class DoublyLinkedList<T> {
  ListNode<T>? head;
  ListNode<T>? tail;

  void addToTail(T value) {
    final newNode = ListNode(value);
    if (tail == null) {
      head = tail = newNode;
    } else {
      tail!.next = newNode;
      newNode.prev = tail;
      tail = newNode;
    }
  }
}

Короче, инструмент на любителя. Для специфичных задач, где основная работа — это перелинковывать узлы, а не скакать по ним туда-сюда, — самое то. А так — обычный лист рулит и не парится.