Какая временная сложность доступа к элементу связного списка (linked list)?

Ответ

Доступ к элементу по индексу в односвязном списке имеет линейную временную сложность O(n). Это связано с тем, что список не поддерживает произвольный доступ по индексу, как массив. Для поиска элемента необходимо последовательно пройти от головного узла (head) до нужной позиции.

// Реализация доступа к узлу по индексу в Node.js
class ListNode {
  constructor(value, next = null) {
    this.value = value;
    this.next = next;
  }
}

function getNodeAtIndex(head, index) {
  let currentNode = head;
  let currentIndex = 0;

  while (currentNode !== null) {
    if (currentIndex === index) {
      return currentNode; // Найден нужный узел
    }
    currentNode = currentNode.next;
    currentIndex++;
  }
  return null; // Индекс выходит за пределы списка
}

// Пример использования
const list = new ListNode(1, new ListNode(2, new ListNode(3)));
const node = getNodeAtIndex(list, 2); // Чтобы добраться до узла со значением 3, нужно пройти через два предыдущих узла.
console.log(node?.value); // 3

Почему O(n)? В худшем случае (доступ к последнему элементу) необходимо пройти все n узлов. Это основное отличие от массивов (Array в JavaScript), где доступ по индексу выполняется за O(1) благодаря вычислению смещения в памяти. Связные списки полезны для частых операций вставки/удаления в начале или середине списка, где их сложность O(1) или O(n) для доступа + O(1) для изменения ссылок может быть выгоднее, чем O(n) для сдвига элементов в массиве.

Ответ 18+ 🔞

А, ну это же классика, блядь! Сейчас объясню на пальцах, чтобы даже мартышлюшка поняла.

Представь, что у тебя цепочка из людей, каждый знает только следующего. Ты стоишь первым и хочешь поговорить с пятым по счёту. Ты можешь спросить только у того, кто рядом: «Эй, где следующий?» И так, блядь, пока не дойдёшь. Это и есть односвязный список, ёпта. Чтобы добраться до элемента, тебе надо пройти все предыдущие — вот и получается O(n), то есть время растёт прямо пропорционально количеству элементов. Овердохуища неудобств, если список длинный!

А теперь смотри на этот код. Функция getNodeAtIndex — это как раз тот самый наивный проход.

// Реализация доступа к узлу по индексу в Node.js
class ListNode {
  constructor(value, next = null) {
    this.value = value;
    this.next = next;
  }
}

function getNodeAtIndex(head, index) {
  let currentNode = head;
  let currentIndex = 0;

  while (currentNode !== null) {
    if (currentIndex === index) {
      return currentNode; // Найден нужный узел
    }
    currentNode = currentNode.next;
    currentIndex++;
  }
  return null; // Индекс выходит за пределы списка
}

// Пример использования
const list = new ListNode(1, new ListNode(2, new ListNode(3)));
const node = getNodeAtIndex(list, 2); // Чтобы добраться до узла со значением 3, нужно пройти через два предыдущих узла.
console.log(node?.value); // 3

Видишь этот while? Это он, сука, и есть наше путешествие от головы до нужной точки. Хуй с горы, если индекс маленький, а если тебе нужен тысячный элемент — терпения ноль, ебать, потому что придётся сделать тысячу шагов.

А теперь главный прикол, чувак. В обычном массиве (Array) — всё иначе. Там у каждого элемента есть свой точный адрес, как квартира в доме. Компьютер сразу знает, где она, и приходит без стука. Это O(1), мгновенно. А в нашем списке — хуй в пальто, приходится стучаться в каждую дверь по цепочке.

Так зачем тогда эти списки нужны, спросишь? А затем, что вставка или удаление элемента в середине — это просто перекинуть пару ссылок (в идеале O(1) после нахождения места), а в массиве пришлось бы сдвигать овердохуища элементов, как маршрутку в час пик. Так что выбирай, что тебе важнее: быстрый доступ по индекту или быстрая вставка. Волнение, ебать, но приходится.