edubun
Русский

Связные списки

Чтобы добавить значение в начало связного списка, меняются два указателя, будь в списке 4 значения или 4 миллиона.

Значения, которые указывают на следующее

Массив держит значения рядом друг с другом, поэтому начало дорого менять. Связный список отказывается от этого правила. Каждое значение живёт в своей маленькой записи, она называется узлом, и узлы могут лежать где угодно в памяти. В конспектах MIT 6.006 узел описан двумя полями: item (элемент) и next, указатель на следующий узел. Сам список помнит только первый узел, он называется головой (head). У последнего узла next равно null, это отмечает конец.

Чтобы прочитать список, начинают с head и идут по указателям next по одному, как по цепочке записок, где в каждой сказано, где спрятана следующая. У этого есть цена. Формулы для адреса четвёртого узла нет, поэтому получить значение на позиции i значит посетить i + 1 узлов. В списке MIT операция get at(i) для связного списка указана как O(n), а массив справлялся с ней за один шаг.

// один узел на значение: { value, next }
let head = null;
for (const v of [4, 30, 7, 12]) head = { value: v, next: head };
// проход по списку
for (let n = head; n !== null; n = n.next) console.log(n.value); // 12, 7, 30, 4

Изменение списка перестановкой ссылок

Выигрыш появляется, когда список меняют. Чтобы поставить новое значение в начало, создай узел, у которого next указывает на текущую голову, а затем направь head на новый узел. Два изменения указателей, и больше ничего не двигается. Удаление первого узла требует одного изменения: направь head на head.next. Сравни с массивом, где те же операции сдвигали каждое значение.

В середине работа состоит в том, чтобы найти место. Чтобы вставить на позицию i, дойди до узла прямо перед ней (i посещений), затем сделай те же два изменения указателей: new.next = prev.next, затем prev.next = new. Порядок важен. Если сначала задать prev.next, остаток списка потеряется, потому что на него больше ничего не указывает. В лаборатории шагом считается одно посещение узла или одно изменение указателя, так что вставка на позицию 2 в список из 4 узлов стоит 2 посещения плюс 2 изменения, 4 шага.

Загрузка

Перетащи, чтобы повернуть. Колёсико или щипок для масштаба.

Односвязный список из четырёх узлов. Выбери позицию ползунком и вставь или удали; счётчик отдельно показывает посещённые узлы и изменённые указатели. «Найти» идёт от head, пока не встретит значение или null.
ОперацияМассив (n значений)Связный список (n узлов)
Получить значение на позиции i1i + 1 посещений
Вставка в началоn сдвигов + 1 запись2 изменения указателей
Удаление первого1 чтение + (n − 1) сдвигов1 изменение указателя
Вставка на позицию i(n − i) сдвигов + 1 записьi посещений + 2 изменения
Найти значениедо n чтенийдо n посещений

Когда его использовать

Связный список хорошо подходит, когда большинство изменений происходит там, куда у тебя уже есть указатель, например в начале, а позиция i нужна редко. Из связных списков можно строить очереди и стеки, а также цепочки внутри хеш-таблицы из следующего урока. Есть варианты. Двусвязный список даёт каждому узлу ещё и указатель prev, так что можно идти назад и удалять узел за один шаг, когда он уже найден. Если хранить, кроме head, ещё и указатель tail, добавление в конец становится дешёвым.

В повседневном JavaScript связный список пишут редко, потому что массивы хорошо оптимизированы, а кэши современных процессоров любят значения, лежащие рядом. Но идея всё равно важна: деревья, графы и многие библиотечные структуры построены из узлов и указателей именно так.

КонтекстСвязные списки появились в середине 1950-х в языке обработки информации IPL, который написали Аллен Ньюэлл, Клифф Шоу и Герберт Саймон в корпорации RAND и Технологическом институте Карнеги около 1956 г. Их программы искали доказательства логических теорем, и заранее нельзя было знать, сколько и каких по размеру вещей придётся помнить. Списки из связанных ячеек позволяли выдавать и забирать память по одной ячейке. Обработка списков в IPL повлияла на Lisp, который Джон Маккарти разработал несколькими годами позже.
  1. Какие два поля хранит узел односвязного списка?
    • item и next
    • index и value
    • left и right
    • head и tail
  2. Список 12 → 7 → 30 → 4. Сколько шагов (изменений указателей) занимает вставка нового значения в начало?
  3. Тот же список: сколько шагов (посещения плюс изменения указателей) стоит вставка на позицию 2?
  4. Тот же список: сколько узлов посещает поиск 30?
  5. Тот же список: сколько узлов посещает поиск 99, прежде чем дойдёт до null?
  6. Тот же список: сколько шагов стоит удаление узла на позиции 3 (это 4)?
  7. В массиве 1000 значений, и место ещё есть. Сколько шагов занимает вставка в начало?
  8. Какая работа подходит связному списку лучше, чем массиву?
    • Много раз читать значение на позиции 500
    • Много раз добавлять и удалять в начале
    • Двоичный поиск
    • Сортировка по индексу
  9. При вставке после узла p какой порядок шагов сохраняет остаток списка?
    • p.next = new, затем new.next = p.next
    • new.next = p.next, затем p.next = new
    • Подходит любой порядок
    • Сначала удалить p
  10. Какой язык, написанный около 1956 г. в RAND, ввёл обработку списков из связанных ячеек?
    • Fortran
    • IPL
    • C
    • JavaScript
Главное
  • Связный список хранит каждое значение в узле с указателем next; список хранит head.
  • Вставка или удаление в начале меняет 1 или 2 указателя; ничего не двигается.
  • Дойти до позиции i стоит i + 1 посещений, поэтому get at(i) работает за O(n).
  • Задай new.next до того, как переставлять ссылку предыдущего узла.
Источники курса (11)
  • MIT OpenCourseWare 6.006 Introduction to Algorithms, Spring 2020 (Demaine, Ku, Solomon)
  • lecture notes 2 Data Structures (interface vs data structure, Sequence and Set interfaces, stack = insert_last/delete_last, queue = insert_last/delete_first, array, linked list, dynamic array, amortized Θ(1) append)
  • 4 Hashing (chaining, pigeonhole collisions, chain length n/m)
  • 6 Binary Trees Part 1 (height, traversal order, O(h) operations) and 7 Binary Trees Part 2: AVL (Adelson-Velsky and Landis, 1962) at ocw.mit.edu/courses/6-006-introduction-to-algorithms-spring-2020/pages/lecture-notes
  • MDN Web Docs, Array.prototype.shift (shifts all values left by 1)
  • Array.prototype.unshift, Map (insertion order, Map vs Object, spec requires sublinear average access) and Set (developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects)
  • ECMAScript 2025 Language Specification (tc39.es/ecma262)
  • 24.1 Map Objects and 24.2 Set Objects ("must be implemented using either hash tables or other mechanisms that, on average, provide access times that are sublinear on the number of elements")
  • Array.prototype.shift steps (Repeat, while k < len: move element k to k - 1)
  • D. E. Knuth, The Art of Computer Programming vol. 3 (2nd ed. 1998) section 6.4 History (H. P. Luhn, IBM internal memorandum January 1953, hashing with chaining; digit pairs added mod 10, 31415926 -> 4548) via gwern.net copy, and IEEE Spectrum, "Hans Peter Luhn and the Birth of the Hashing Algorithm" (2018)
  • "A brief history of the stack", SIGCIS (sigcis.org/files/A brief history.pdf: Bauer and Samelson applied for the patent in 1957, US 3,047,228 granted July 31, 1962; IPL list processing by Newell, Simon and Shaw at RAND 1956) and Wikipedia, Information Processing Language (Newell, Shaw, Simon, RAND and Carnegie Institute of Technology, about 1956).

Проверено: 2026-10-18