Список с пропусками — различия между версиями
Gaporf (обсуждение | вклад) (→Поиск элемента) |
Gaporf (обсуждение | вклад) (→Вставка элемента) |
||
| Строка 72: | Строка 72: | ||
===Вставка элемента=== | ===Вставка элемента=== | ||
| − | + | Алгоритм вставки элементов в список с пропусками состоит из следующих шагов: | |
| − | # Начинаем вставку | + | # Начинаем вставку на самом верхнем уровне |
| − | # | + | # Пока следующих элемент следующего элемента меньше ключа <tex>-</tex> идём по списку вправо. |
| − | # Если мы на первом уровне <tex>-</tex> вставляем элемент и | + | # Если мы на первом уровне <tex>-</tex> вставляем элемент. Иначе спускаемся ниже и возвращаемся к шагу <tex>2</tex>. |
| − | + | # Кидаем монетку и если выпал «Орёл», то возвращаем ссылку на текущий элемент, иначе <tex>-</tex> ''null''. Если мы были не на первом уровне и нам вернули ''null'' <tex>-</tex> возвращаем его без броска монетки. | |
| + | |||
| + | Отдельно стоит обработать случай, когда вставка нового элемента увеличивает число уровней. Тогда необходимо создать ещё один отсортированный список, в котором будет всего один текущий элемент, и не забыть присвоить списку с пропусками новую ссылку на верхний уровень. Будем считать, что вставка каждого нового элемента увеличивает число уровней не более, чем на один. | ||
====Псевдокод==== | ====Псевдокод==== | ||
| + | Функция <tex>\mathtt{insert}</tex> возвращаем ссылку на вставленный элемент в списке, в котором находится <tex>\mathtt{res}</tex>, или ''null'', если на монете выпал не «Орёл». | ||
| + | |||
| + | '''node''' insert ('''node''' res, '''K''' key) | ||
| + | '''while''' res.next <tex>\neq</tex> ''null'' '''and''' res.next.next <tex>\neq</tex> ''null'' | ||
| + | res = res.next | ||
| + | '''if''' res.down = ''null'' | ||
| + | res.next = node(key, ''null'', res.next) | ||
| + | '''if''' random(0, 1) > 0.5 | ||
| + | '''return''' res.next | ||
| + | '''return''' ''null'' | ||
| + | node i = insert(res.down, key) | ||
| + | '''if''' i <tex>\neq</tex> ''null'' | ||
| + | res.next = node(key, i, res.next) | ||
| + | '''if''' random(0, 1) > 0.5 | ||
| + | '''return''' res.next | ||
| + | '''return''' ''null'' | ||
| + | '''return''' ''null'' | ||
| + | |||
| + | Для того, чтобы вставить элемент с ключом <tex>\mathtt{key}</tex> в список с пропусками <tex>\mathtt{skip}</tex> необходимо вызвать следующую функцию | ||
===Удаление элемента=== | ===Удаление элемента=== | ||
Версия 11:08, 24 марта 2019
Список с пропусками (англ. skip list) — вероятностная структура данных, позволяющая в среднем за времени выполнять операции добавления, удаления и поиска элементов.
Список с пропусками состоит из нескольких уровней, на каждом из которых находится отсортированный связный список. На самом нижнем (первом) уровне располагаются все элементы. Дальше около половины элементов в таком же порядке располагаются на втором, почти четверть на третьем и так далее, но при этом известно, что если элемент расположен на уровне , то он также расположен на всех уровнях с номерами меньших .
Содержание
Построение
Допустим, что нам задан односвязный отсортированный список и мы хотим построить на его основе список с пропусками, позволяющий в среднем за времени выполнять операции добавления, удаления и поиска элементов.
На самом нижнем уровне списка с пропусками мы расположим исходный список. На втором уровне всё элементы с чётными номерами, причём каждый элемент будет ссылаться на соответствующий ему элемент на нижнем уровне. Таким же образом построим и третий уровень, куда будем добавлять только те элементы, номера которых кратны четырём. Аналогичным образом построим и последующие уровни.
Псевдокод
Каждый уровень списка с пропусками содержит отсортированный односвязный список, у которое есть начало и конец . Для выполнения операций на списке с пропусками необходимо передавать в качестве аргумента ссылку на начало односвязного списка, расположенного на самом верхнем уровне.
Элементы односвязного списка вершины , у которых есть поля:
- ссылка на следующий элемент списка
- ключ, который хранится в данной вершине
- ссылка на соответственный элемент, лежащий уровнем ниже
Также известно, что и .
Функция возвращает новый уровень списка с пропусками на основе предыдущего построенного уровня.
list build_lvl (list lvl) // Отсеивание нечётных элементов
list next_lvl
node i = lvl.head
while (i != null) and (i != lvl.tail)
next_lvl.push_back(node(i.key, i)) // Конструктор node(key, down) возвращает новую вершину с ключом key и ссылкой down на соответствующую вершину предыдущего уровеня
i = i.next.next // Переход к следующему чётному элементу
return next_lvl
Функция принимает в качестве аргумента односвязный отсортированный список и возвращает новый список с пропусками, построенный на его основе.
list skip_list (list l):
list lvl = build_lvl(l) // Построение первого уровня
while lvl.size > 2 // Добавление следующих уровней; последний содержит не более двух элементов
lvl = build_lvl(lvl)
return lvl // Возвращает ссылку на начало верхнего уровня
Операции над структурой
Поиск элемента
Алгоритм поиска элементе в списке с пропусками состоит из следующих операций:
- Начинаем поиск элемента в самом верхнем уровне
- Переходим к следующему элементу списка, пока значение в следующей ячейке меньше
- Переместиться на один уровень вниз и перейти к шагу . Если мы уже на первом уровне прекратить поиск и вернуть ссылку на текущую вершину
В конце алгоритма функция вернёт элемент, значение которого не меньше ключа или ссылку на конец списка на первом уровне.
Если в качестве случайного источника мы будем использовать честную монету, то в среднем случае будет уровне. На самом верхнем уровне будет не более двух элементов. Тогда на каждом уровне в среднем нужно проверить не более двух элементов (в противном случае могли бы вместо двух нижних элементов проверить ещё один уровнем выше). Уровней всего , откуда вытекает оценка времени поиска элемента в .
Псевдокод
Функция возвращает ссылку на элемент, значение которого не меньше . В случае, если все элементы в списке с пропусками меньше , то возвращается ссылка на конец списка с пропусками.
T find (node res, K key)
while res.key < key // Пока значение вершины меньше ключа
res = res.next // Перейдём к следующей вершине в текущем уровне
if res.down = null // Если мы находимся на первом уровне
return res // Мы нашли искомый элемент
return find(res.down, key) // Иначе спустимся на один уровень ниже
Для того, чтобы найти элемент с ключом в списке с пропусками необходимо запустить следующим образом
find(skip.head, key)
Вставка элемента
Алгоритм вставки элементов в список с пропусками состоит из следующих шагов:
- Начинаем вставку на самом верхнем уровне
- Пока следующих элемент следующего элемента меньше ключа идём по списку вправо.
- Если мы на первом уровне вставляем элемент. Иначе спускаемся ниже и возвращаемся к шагу .
- Кидаем монетку и если выпал «Орёл», то возвращаем ссылку на текущий элемент, иначе null. Если мы были не на первом уровне и нам вернули null возвращаем его без броска монетки.
Отдельно стоит обработать случай, когда вставка нового элемента увеличивает число уровней. Тогда необходимо создать ещё один отсортированный список, в котором будет всего один текущий элемент, и не забыть присвоить списку с пропусками новую ссылку на верхний уровень. Будем считать, что вставка каждого нового элемента увеличивает число уровней не более, чем на один.
Псевдокод
Функция возвращаем ссылку на вставленный элемент в списке, в котором находится , или null, если на монете выпал не «Орёл».
node insert (node res, K key)
while res.next null and res.next.next null
res = res.next
if res.down = null
res.next = node(key, null, res.next)
if random(0, 1) > 0.5
return res.next
return null
node i = insert(res.down, key)
if i null
res.next = node(key, i, res.next)
if random(0, 1) > 0.5
return res.next
return null
return null
Для того, чтобы вставить элемент с ключом в список с пропусками необходимо вызвать следующую функцию
Удаление элемента
Алгоритм удаления элемента выглядит следующим образом:
- Начинаем удалять элемент с верхнего уровня
- Если мы на первом уровне и нашли элемент то просто удаляем элемент
- Иначе спускаемся ниже и также удаляем элемент с текущего уровня, если он есть
Псевдокод
Использование нечестной монеты
Вместо честной монеты с распределением можно взять в качестве случайного источника нечестную монету с распределением (с вероятностью выпадает «Орёл»). Тогда математическим ожиданием количества элементов на уровне будет . Время поиска будет равно (на -ом уровне элементов будет почти в раз больше, чем на -ом, значит на каждом уровне пройдём не более элементов, а уровней всего ).
Для крайних распределений:
- — поиск, добавление и удаления элемента, поскольку мы вместо нескольких списком используем по факту всего один список.
- — зависит от реализации алгоритма. Если при каждой вставке у нас образуется не более одного уровня, то количество уровней будет равным , значит время поиска будет равным .
Применение
Список с пропусками применяется во многих приложениях, поскольку имеет ряд преимуществ.
- Быстрая вставка элемента, поскольку не требуется каким-либо образом изменять другие элементы (только предыдущий элемент)
- Проще реализовать, чем сбалансированные деревья или хеш-таблицы
- Следующий элемент достаётся за
- Легко модифицировать под различные задачи
Нахождение всех интервалов, покрывающих данную точку
| Задача: |
Есть запросов двух видов:
|
Решение данной задачи заключается в том, что когда приходит запрос добавления интервала, то мы добавляем левую и правую границу (если до этого они не были добавлены). Потом идём от левой точки до правой и на пути прибавляем . Когда нам приходит запрос второго типа, то рекурсивно спускаемся сверху вниз и суммируем все числа, лежащие на пути между двумя ближайшими вершинами, между которыми находится заданная точка. На каждый запрос мы отвечаем онлайн за времени.
См. также
- Список
- Рандомизированное бинарное дерево поиска
- Поисковые структуры данных
- Skip quadtree
- Skip graph — структура данных, основанная на списке с пропусками



