2.3.1.9 — последняя статья раздела: связный список (2.3.1.1) с добавленными "экспресс-полосами"2.3.1.9 — the section's last article: a linked list (2.3.1.1) with added "express lanes"
Обычный отсортированный связный список ищет за O(n) — нет способа пропустить элементы, не проверив их. Skip list добавляет поверх базового уровня несколько дополнительных "уровней" связей, каждый следующий уровень соединяет случайное подмножество узлов предыдущего — получаются "экспресс-полосы", позволяющие сначала быстро проскочить большую часть списка на верхнем уровне, а затем спуститься на более подробные уровни только рядом с искомым значением.An ordinary sorted linked list searches in O(n) — there's no way to skip elements without checking them. A skip list adds several extra "levels" of links on top of the base level, each successive level connecting a random subset of the previous level's nodes — creating "express lanes" that let a search quickly skip over most of the list at the top level, then descend to more detailed levels only near the target value.
Каждый узел получает случайную "высоту" при вставке: подбрасывается монета (или генерируется случайное число), и пока выпадает "орёл" (с вероятностью p, обычно 0.5), высота узла увеличивается на 1 — в среднем высота узла равна 1/(1-p), геометрическое распределение. Узел присутствует на всех уровнях от 0 до своей высоты. Поиск начинается с самого верхнего уровня головного узла и движется вправо, пока следующий узел на этом уровне не превышает искомое значение — тогда поиск спускается на уровень ниже и продолжает оттуда. Инвариант: на уровне i в среднем остаётся n·p^i узлов — уровни становятся экспоненциально более разреженными, что и даёт логарифмическую высоту всей структуры (число уровней ~ log_{1/p}(n)) при линейном ожидаемом числе шагов на уровень.Every node gets a random "height" on insertion: a coin is flipped (or a random number generated), and as long as it comes up "heads" (with probability p, usually 0.5), the node's height grows by 1 — a node's average height is 1/(1-p), a geometric distribution. A node is present at every level from 0 up to its height. Search starts at the head node's topmost level and moves right as long as the next node at that level doesn't exceed the target value — then it drops down a level and continues from there. Invariant: level i has, on average, n·p^i nodes remaining — levels become exponentially sparser, which is exactly what gives the structure its logarithmic height (number of levels ~ log_{1/p}(n)) with a linear expected number of steps per level.
та же логика "отбросить большую часть за один шаг", что у бинарного поиска, но реализованная через структуру связей, а не индексацию массиваthe same "discard most of it in one step" logic as binary search, but implemented via link structure rather than array indexing
struct Node {
int val;
std::vector<Node*> next; // next[i] — указатель на уровне i// next[i] — pointer at level i
};
bool search(Node* head, int level, int val) {
Node* cur = head;
for (int i = level; i >= 0; --i) {
while (cur->next[i] && cur->next[i]->val < val)
cur = cur->next[i]; // двигаться направо на текущем уровне// move right at the current level
} // затем неявно спуститься на уровень ниже// then implicitly drop a level
cur = cur->next[0];
return cur && cur->val == val;
}
int random_level() { // геометрическое распределение// geometric distribution
int lvl = 0;
while ((double)rand() / RAND_MAX < 0.5 && lvl < MAX_LEVEL) ++lvl;
return lvl;
}
Узлы расположены на нескольких уровнях (высота каждого — результат случайного подбрасывания монеты при вставке). Жёлтая подсветка — путь поиска: движение вправо, затем вниз при перескоке цели.Nodes sit at several levels (each one's height is the result of a random coin flip on insertion). Yellow highlights the search path: moving right, then dropping down on overshooting the target.
введите значение, чтобы выполнить операцию сразуenter a value to execute the operation immediately
Высота структуры (число уровней) — случайная величина, определяемая максимальной высотой среди всех вставленных узлов. Ожидаемая максимальная высота среди n независимых геометрических случайных величин — Θ(log n), что и даёт ожидаемую O(log n) сложность поиска. Но теоретически (с исчезающе малой, но ненулевой вероятностью) все n узлов могут получить высоту 0 — тогда структура вырождается в обычный связный список, и поиск деградирует до O(n). Это принципиально отличает skip list от детерминированно сбалансированных деревьев (2.3.2, AVL/Red-Black) — там O(log n) гарантирован структурными инвариантами, а не вероятностью; skip list — сознательный компромисс простоты реализации против абсолютной гарантии.The structure's height (number of levels) is a random variable, determined by the maximum height among all inserted nodes. The expected maximum height among n independent geometric random variables is Θ(log n), giving the expected O(log n) search complexity. But theoretically (with vanishingly small but nonzero probability) all n nodes could get height 0 — the structure would then degenerate into an ordinary linked list, and search would degrade to O(n). This is what fundamentally distinguishes skip lists from deterministically balanced trees (2.3.2, AVL/red-black) — there O(log n) is guaranteed by structural invariants, not probability; a skip list is a deliberate trade-off of implementation simplicity against an absolute guarantee.
Redis реализует упорядоченные множества именно через skip list — простота вставки и удаления без сложных операций перебалансировки (поворотов, перекраски узлов, как в красно-чёрных деревьях) была явно названа авторами Redis причиной этого выбора.Redis implements sorted sets precisely via a skip list — the simplicity of insertion and deletion without complex rebalancing operations (rotations, node recoloring as in red-black trees) was explicitly cited by Redis's authors as the reason for this choice.
Структура в памяти, куда сначала попадают все новые записи перед сбросом на диск в виде SSTable, во многих LSM-движках реализована skip list'ом — конкурентные вставки в skip list проще делать lock-free, чем в сбалансированное дерево, что важно для многопоточной записи.The in-memory structure where all new writes first land before flushing to disk as an SSTable is implemented as a skip list in many LSM engines — concurrent insertions into a skip list are easier to make lock-free than into a balanced tree, which matters for multi-threaded writes.