Связные списки
Чтобы добавить значение в начало связного списка, меняются два указателя, будь в списке 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 шага.
Перетащи, чтобы повернуть. Колёсико или щипок для масштаба.
| Операция | Массив (n значений) | Связный список (n узлов) |
|---|---|---|
| Получить значение на позиции i | 1 | i + 1 посещений |
| Вставка в начало | n сдвигов + 1 запись | 2 изменения указателей |
| Удаление первого | 1 чтение + (n − 1) сдвигов | 1 изменение указателя |
| Вставка на позицию i | (n − i) сдвигов + 1 запись | i посещений + 2 изменения |
| Найти значение | до n чтений | до n посещений |
Когда его использовать
Связный список хорошо подходит, когда большинство изменений происходит там, куда у тебя уже есть указатель, например в начале, а позиция i нужна редко. Из связных списков можно строить очереди и стеки, а также цепочки внутри хеш-таблицы из следующего урока. Есть варианты. Двусвязный список даёт каждому узлу ещё и указатель prev, так что можно идти назад и удалять узел за один шаг, когда он уже найден. Если хранить, кроме head, ещё и указатель tail, добавление в конец становится дешёвым.
В повседневном JavaScript связный список пишут редко, потому что массивы хорошо оптимизированы, а кэши современных процессоров любят значения, лежащие рядом. Но идея всё равно важна: деревья, графы и многие библиотечные структуры построены из узлов и указателей именно так.
- Какие два поля хранит узел односвязного списка?
- item и next
- index и value
- left и right
- head и tail
- Список 12 → 7 → 30 → 4. Сколько шагов (изменений указателей) занимает вставка нового значения в начало?
- Тот же список: сколько шагов (посещения плюс изменения указателей) стоит вставка на позицию 2?
- Тот же список: сколько узлов посещает поиск 30?
- Тот же список: сколько узлов посещает поиск 99, прежде чем дойдёт до null?
- Тот же список: сколько шагов стоит удаление узла на позиции 3 (это 4)?
- В массиве 1000 значений, и место ещё есть. Сколько шагов занимает вставка в начало?
- Какая работа подходит связному списку лучше, чем массиву?
- Много раз читать значение на позиции 500
- Много раз добавлять и удалять в начале
- Двоичный поиск
- Сортировка по индексу
- При вставке после узла p какой порядок шагов сохраняет остаток списка?
- p.next = new, затем new.next = p.next
- new.next = p.next, затем p.next = new
- Подходит любой порядок
- Сначала удалить p
- Какой язык, написанный около 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