Линейный список. Функциональная спецификация: операции в произвольном месте

Модуль "Линейные списки" • урок 1 из 9 • ~6 минут

Суть урока. Линейный список - это упорядоченная последовательность, которая обобщает очередь и стек. Главное отличие: она разрешает доступ, вставку и удаление в произвольном месте при сохранении порядка остальных элементов. Поиск стоит O(N), но вставка по известной позиции - очень дешевая.
Линейный список. Функциональная спецификация: до вставки узлы A, B, C и D связаны в цепочку, после вставки новый узел X подцеплен между B и C, а сами узлы остались на прежних местах
Вставка меняет две связи, а не положение элементов в памяти

Что это такое

Линейный список - это представление в памяти компьютера конечного упорядоченного динамического множества элементов. В отличие от строгих структур (стека или очереди), линейный список позволяет получать доступ, вставлять и удалять элементы в любом произвольном месте, сохраняя порядок следования остальных компонент.

Зачем это нужно

Линейные списки необходимы там, где принцип очереди "первым пришел - первым ушел" недостаточно гибок. Например, в операционных системах очередь процессов может зависеть от приоритета: если приходит высокоприоритетная задача, она должна быть вставлена не в конец очереди, а в ее середину или даже в начало. Списки решают задачу произвольной вставки без разрушения структуры.

Линейный список. Функциональная спецификация: восемь операций

Математически список - это упорядоченная последовательность (в которой элементы могут повторяться). Порядок определяется не жесткими индексами, как в векторе, а отношениями "предыдущий-следующий".

Спецификация включает в себя восемь абстрактных операций:

Важное логическое свойство списка: ПРЕДЫДУЩИЙ(СЛЕДУЮЩИЙ(x)) = x (каждый элемент является предыдущим для следующего за ним). Для кольцевых списков также выполняется условие СЛЕДУЮЩИЙ(ПОСЛЕДНИЙ) = ПЕРВЫЙ.

А как в Python? В Python встроенный list поддерживает вставку в середину через insert(i, val) и удаление через del list[i]. Но важно помнить, что физически это скрытый массив указателей, поэтому вставка в начало занимает O(N) (все элементы сдвигаются).

Цена свободы (Сложность доступа)

В отличие от вектора, где доступ к i-му элементу занимает время O(1) по индексу, элементы линейного списка не пронумерованы. Чтобы найти нужный элемент, необходимо стартовать с головы и перебирать их один за другим. Поэтому поиск в списке занимает время O(N). Зато сама физическая вставка между уже найденными соседями обходится очень дешево.

Абстрактный интерфейс на C

Поскольку физическое устройство списка может быть разным, его спецификация в C выражается через интерфейс с использованием абстрактного итератора для указания позиции:

#include <stdbool.h>

typedef int T;
typedef struct List List;
typedef struct Iterator Iterator;

void Create(List* l);
bool Empty(List* l);
int Length(List* l);
Iterator First(List* l);
Iterator Last(List* l);
Iterator Next(Iterator i);
Iterator Prev(Iterator i);

/* Вставка и удаление требуют указания КОНКРЕТНОЙ позиции */
void Insert(List* l, Iterator pos, T value); 
void Delete(List* l, Iterator pos);
void Destroy(List* l);

Коротко: главное

  • Линейный список обобщает очередь и стек, разрешая доступ в любую точку.
  • Порядок задается понятиями "следующий" и "предыдущий", а не индексами.
  • Спецификация требует операций ВСТАВКА и УДАЛЕНИЕ относительно произвольной позиции.
  • Время поиска нужного элемента - O(N), так как список приходится перебирать.
  • Сама операция вставки/удаления (если позиция уже известна) выполняется за константное время.

Проверьте себя

Почему поиск i-го элемента в линейном списке занимает время O(N)?

Как вам урок? Спасибо за отзыв!