← Назад к вопросам

Для чего используется связанный список?

1.7 Middle🔥 181 комментариев
#Алгоритмы и структуры данных

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI28 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Связные списки (Linked List) и их применение

Связный список — это фундаментальная структура данных, состоящая из узлов, где каждый узел содержит данные и ссылку на следующий узел. Хотя в Node.js редко пишут связные списки вручную, понимание их особенностей критично для интервью и оптимизации алгоритмов.

Структура связного списка

// Узел (Node)
class Node {
  data: any;
  next: Node | null = null;
  
  constructor(data: any) {
    this.data = data;
  }
}

// Простой связный список
class LinkedList {
  head: Node | null = null;
  
  // Добавить в конец
  append(data: any) {
    const newNode = new Node(data);
    
    if (!this.head) {
      this.head = newNode;
      return;
    }
    
    let current = this.head;
    while (current.next) {
      current = current.next;
    }
    current.next = newNode;
  }
  
  // Вставить в начало
  prepend(data: any) {
    const newNode = new Node(data);
    newNode.next = this.head;
    this.head = newNode;
  }
  
  // Удалить элемент
  remove(data: any) {
    if (!this.head) return;
    
    if (this.head.data === data) {
      this.head = this.head.next;
      return;
    }
    
    let current = this.head;
    while (current.next) {
      if (current.next.data === data) {
        current.next = current.next.next;
        return;
      }
      current = current.next;
    }
  }
}

Сравнение со звичайными массивами

//           ARRAY          LINKED LIST
Access      O(1)           O(n)
Insert      O(n)           O(1) *
Delete      O(n)           O(1) *
Space       O(1)           O(n)

// * если уже есть ссылка на узел

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

1. Быстрое добавление/удаление в начале

// Массив — нужно сдвигать все элементы O(n)
const arr = [1, 2, 3, 4, 5];
arr.unshift(0);  // [0, 1, 2, 3, 4, 5] — сдвинули всё

// Связный список — просто меняем указатель O(1)
list.prepend(0);  // быстро!

2. Экономия памяти при частых вставках/удалениях

// Массив перевыделяет память при расширении
const arr = new Array(10);  // выделил 10 мест
arr.push(11);  // нужно больше, перевыделяет на 20

// Связный список растет на N элементов
list.append(11);  // добавляет одно звено

3. Динамический размер без переутечек памяти

// Массив может иметь пустые места
const arr = new Array(1000000);  // 1 млн мест, но используем 10

// Список использует ровно столько, сколько нужно
const list = new LinkedList();
for (let i = 0; i < 10; i++) {
  list.append(i);  // 10 узлов, ровно столько сколько надо
}

Недостатки связных списков

1. Нет прямого доступа по индексу O(n)

// Массив — мгновенный доступ
const arr = [1, 2, 3, 4, 5];
const third = arr[2];  // O(1)

// Список — нужно пройти с начала
let current = list.head;
for (let i = 0; i < 2; i++) {
  current = current.next;  // O(n)
}

2. Дополнительная память на указатели

// Каждый узел содержит указатель на следующий
// Если данные маленькие, память на указатели >> памяти на данные
const list = new Node(1);  // 1 число + 8 байт на указатель

Применение в реальных системах

1. Очередь и стек

// Очередь (Queue) — идеально с linked list
class Queue {
  head: Node | null = null;
  tail: Node | null = null;
  
  enqueue(data: any) {
    const newNode = new Node(data);
    if (!this.tail) {
      this.head = this.tail = newNode;
    } else {
      this.tail.next = newNode;
      this.tail = newNode;
    }
  }
  
  dequeue() {
    if (!this.head) return null;
    const data = this.head.data;
    this.head = this.head.next;
    return data;  // O(1)!
  }
}

// С массивом shift() — O(n)
const queue = [1, 2, 3];
queue.shift();  // медленно, сдвигает всё

2. LRU Cache (используется в браузерах, базах данных)

// LRU Cache использует двусвязный список + HashMap
class LRUCache {
  private capacity: number;
  private cache: Map<number, Node> = new Map();
  private head: Node = new Node(0);
  private tail: Node = new Node(0);
  
  constructor(capacity: number) {
    this.capacity = capacity;
    this.head.next = this.tail;
    this.tail.prev = this.head;
  }
  
  // Получить значение и переместить в начало
  get(key: number): number {
    if (!this.cache.has(key)) return -1;
    
    const node = this.cache.get(key)!;
    this.removeNode(node);
    this.addToHead(node);
    return node.value;
  }
}

3. Реализация графов (adjacency list)

// Граф часто представляют как связные списки
class Graph {
  private adj: Map<number, Node> = new Map();
  
  addEdge(u: number, v: number) {
    if (!this.adj.has(u)) {
      this.adj.set(u, new Node(v));
    } else {
      let current = this.adj.get(u)!;
      while (current.next) {
        current = current.next;
      }
      current.next = new Node(v);
    }
  }
}

4. Кэширование с LRU политикой в Node.js

// Redis часто использует связные списки внутри
const cache = new LinkedList();

function cacheData(key: string, value: any) {
  // Удалить старый если есть
  cache.remove(key);
  
  // Добавить новый в начало (самый свежий)
  cache.prepend({ key, value });
  
  // Если превышен размер, удалить с конца
  if (cache.size > MAX_SIZE) {
    cache.removeFromEnd();
  }
}

Двусвязный список (Doubly Linked List)

class DoublyNode {
  data: any;
  next: DoublyNode | null = null;
  prev: DoublyNode | null = null;  // указатель на предыдущий
  
  constructor(data: any) {
    this.data = data;
  }
}

// Позволяет просто двигаться в обе стороны
// Используется в LRU Cache для быстрого удаления из середины

Когда использовать в Node.js

// ✅ Используй связные списки
- Реализация Queue/Stack (нет операции shift)
- LRU Cache
- Граф (adjacency list)
- Когда нужны частые вставки/удаления в начало

// ❌ Не используй
- Когда нужна индексация (используй Array)
- Когда нужна сортировка (используй Array)
- Когда данные статичные (используй Array)

// В Node.js обычно используешь структуры данных библиотек
import Deque from 'collections/deque';  // очередь
import LRU from 'lru-cache';  // LRU cache

Понимание связных списков — это основа computer science, даже если в production code ты используешь готовые структуры данных. На интервью это часто спрашивают как проверку фундаментальных знаний.

Для чего используется связанный список? | PrepBro