Список с пропусками — различия между версиями

Материал из Викиконспекты
Перейти к: навигация, поиск
(Поиск элемента)
(не показано 6 промежуточных версий этого же участника)
Строка 3: Строка 3:
 
'''Список с пропусками''' (англ. ''skip list'') — вероятностная структура данных, позволяющая в среднем за <tex>O(\log(n))</tex> времени выполнять операции добавления, удаления и поиска элементов.
 
'''Список с пропусками''' (англ. ''skip list'') — вероятностная структура данных, позволяющая в среднем за <tex>O(\log(n))</tex> времени выполнять операции добавления, удаления и поиска элементов.
  
Список с пропусками состоит из нескольких уровней, на каждом из которых находится отсортированный связный список. На самом нижнем (первом) уровне располагаются все элементы. Дальше около половины элементов в таком же порядке располагаются на втором, почти четверть <tex>-</tex> на третьем и так далее, но при этом известно, что если элемент расположен на уровне <tex>i</tex>, то он также расположен на всех уровнях с номерами меньших <tex>i</tex>.
+
Список с пропусками состоит из нескольких уровней, на каждом из которых находится отсортированный связный список. На самом нижнем (первом) уровне располагаются все элементы. Дальше около половины элементов в таком же порядке располагаются на втором, почти четверть на третьем и так далее, но при этом известно, что если элемент расположен на уровне <tex>i</tex>, то он также расположен на всех уровнях, номера которых меньше <tex>i</tex>.
  
 
==Построение==
 
==Построение==
Строка 11: Строка 11:
 
Допустим, что нам задан односвязный отсортированный список и мы хотим построить на его основе список с пропусками, позволяющий в среднем за <tex>O(\log{n})</tex> времени выполнять операции добавления, удаления и поиска элементов.
 
Допустим, что нам задан односвязный отсортированный список и мы хотим построить на его основе список с пропусками, позволяющий в среднем за <tex>O(\log{n})</tex> времени выполнять операции добавления, удаления и поиска элементов.
  
На самом нижнем уровне списка с пропусками мы расположим исходный список. На втором уровне <tex>-</tex> всё элементы с чётными номерами, причём каждый элемент будет ссылаться на соответствующий ему элемент на нижнем уровне. Таким же образом построим и третий уровень, куда будем добавлять только те элементы, номера которых кратны четырём. Аналогичным образом построим и последующие уровни.
+
На самом нижнем уровне списка с пропусками мы расположим исходный список. На втором уровне всё элементы с чётными номерами, причём каждый элемент будет ссылаться на соответствующий ему элемент на нижнем уровне. Таким же образом построим и третий уровень, куда будем добавлять только те элементы, номера которых кратны четырём. Аналогичным образом построим и последующие уровни.
  
 
====Псевдокод====
 
====Псевдокод====
  
Каждый уровень списка с пропусками содержит отсортированный односвязный список, у которое есть начало <tex>\mathtt{head}</tex> и конец <tex>\mathtt{tail}</tex>. Для выполнения операций на списке с пропусками необходимо передавать в качестве аргумента ссылку на начало односвязного списка, расположенного на самом верхнем уровне.
+
Каждый уровень списка с пропусками содержит отсортированный односвязный список, у которого есть начало <tex>\mathtt{head}</tex> и конец <tex>\mathtt{tail}</tex>. Для выполнения операций на списке с пропусками необходимо передавать в качестве аргумента ссылку на начало односвязного списка, расположенного на самом верхнем уровне.
  
Элементы односвязного списка <tex>-</tex> вершины <tex>\mathtt{node}</tex>, у которых есть <tex>3</tex> поля:
+
Элементы односвязного списка вершины <tex>\mathtt{node}</tex>, у которых есть <tex>3</tex> поля:
* <tex>\mathtt{next}</tex> <tex>-</tex> ссылка на следующий элемент списка
+
* <tex>\mathtt{next}</tex> ссылка на следующий элемент списка
* <tex>\mathtt{key}</tex> <tex>-</tex> ключ, который хранится в данной вершине
+
* <tex>\mathtt{key}</tex> ключ, который хранится в данной вершине
* <tex>\mathtt{down}</tex> <tex>-</tex> ссылка на соответственный элемент, лежащий уровнем ниже
+
* <tex>\mathtt{down}</tex> ссылка на соответственный элемент, лежащий уровнем ниже
  
 
Также известно, что <tex>\mathtt{head{.}key} = -\infty \ </tex> и <tex>\mathtt{tail{.}key} = \infty</tex>.
 
Также известно, что <tex>\mathtt{head{.}key} = -\infty \ </tex> и <tex>\mathtt{tail{.}key} = \infty</tex>.
Строка 26: Строка 26:
 
Функция <tex>\ \mathtt{build\_lvl} \ </tex> возвращает новый уровень списка с пропусками на основе предыдущего построенного уровня.
 
Функция <tex>\ \mathtt{build\_lvl} \ </tex> возвращает новый уровень списка с пропусками на основе предыдущего построенного уровня.
  
     '''list''' build_lvl ('''list''' lvl)                  <font color=darkgreen>// Отсеивание нечётных элементов</font>
+
     '''list''' build_lvl('''list''' lvl)                   
 
         '''list''' next_lvl  
 
         '''list''' next_lvl  
 
         '''node''' i = lvl.head                       
 
         '''node''' i = lvl.head                       
         '''while''' (i != ''null'') '''and''' (i != lvl.tail)
+
        '''node''' cur = next_lvl.head
             next_lvl.push_back(node(i.key, i)<font color=darkgreen>// Конструктор node(key, down) возвращает новую вершину с ключом key и ссылкой down на соответствующую вершину предыдущего уровеня</font>
+
         '''while''' i <tex>\neq</tex> ''null'' '''and''' i.next <tex>\neq</tex> ''null''
             i = i.next.next                     <font color=darkgreen>// Переход к следующему чётному элементу</font>
+
             cur.next = node(key, i, cur.next)                 <font color=darkgreen>// Конструктор node(key, down, next) возвращает новую вершину с ключом key, ссылками down на нижний и next на следующий элемент</font>
 +
            cur = cur.next
 +
             i = i.next.next                                   <font color=darkgreen>// Переход к следующему чётному элементу</font>
 
         '''return''' next_lvl  
 
         '''return''' next_lvl  
  
 
Функция <tex>\ \mathtt{skip\_list} \ </tex> принимает в качестве аргумента односвязный отсортированный список и возвращает новый список с пропусками, построенный на его основе.
 
Функция <tex>\ \mathtt{skip\_list} \ </tex> принимает в качестве аргумента односвязный отсортированный список и возвращает новый список с пропусками, построенный на его основе.
  
     '''list''' skip_list ('''list''' l):
+
     '''list''' skip_list('''list''' l):
         '''list''' lvl = build_lvl(l)               <font color=darkgreen>// Построение первого уровня</font>
+
         '''list''' lvl = build_lvl(l)                               <font color=darkgreen>// Построение первого уровня</font>
         '''while''' lvl.size > 2                    <font color=darkgreen>// Добавление следующих уровней; последний содержит не более двух элементов</font>
+
         '''while''' lvl.size > 2                     
 
             lvl = build_lvl(lvl)                       
 
             lvl = build_lvl(lvl)                       
         '''return''' lvl                             <font color=darkgreen>// Возвращает ссылку на начало верхнего уровня</font>
+
         '''return''' lvl                                             <font color=darkgreen>// Возвращает ссылку на начало верхнего уровня</font>
  
 
==Операции над структурой==
 
==Операции над структурой==
Строка 50: Строка 52:
 
# Начинаем поиск элемента в самом верхнем уровне
 
# Начинаем поиск элемента в самом верхнем уровне
 
# Переходим к следующему элементу списка, пока значение в следующей ячейке меньше
 
# Переходим к следующему элементу списка, пока значение в следующей ячейке меньше
# Переместиться на один уровень вниз и перейти к шагу <tex>2</tex>. Если мы уже на первом уровне <tex>-</tex> прекратить поиск и вернуть ссылку на текущую вершину
+
# Переместиться на один уровень вниз и перейти к шагу <tex>2</tex>. Если мы уже на первом уровне прекратить поиск и вернуть ссылку на текущую вершину
  
 
В конце алгоритма функция вернёт элемент, значение которого не меньше ключа <tex>\mathtt{key}</tex> или ссылку на конец списка на первом уровне.
 
В конце алгоритма функция вернёт элемент, значение которого не меньше ключа <tex>\mathtt{key}</tex> или ссылку на конец списка на первом уровне.
Строка 60: Строка 62:
 
Функция <tex>\mathtt{find}</tex> возвращает ссылку на элемент, значение которого не меньше <tex>\mathtt{key}</tex>. В случае, если все элементы в списке с пропусками меньше <tex>\mathtt{key}</tex>, то возвращается ссылка на конец списка с пропусками.
 
Функция <tex>\mathtt{find}</tex> возвращает ссылку на элемент, значение которого не меньше <tex>\mathtt{key}</tex>. В случае, если все элементы в списке с пропусками меньше <tex>\mathtt{key}</tex>, то возвращается ссылка на конец списка с пропусками.
 
   
 
   
     '''T''' find ('''node''' res, '''K''' key)
+
     '''T''' find('''node''' res, '''K''' key)
         '''while''' res.key < key                                        <font color=darkgreen>// Пока значение вершины меньше ключа</font>
+
         '''while''' res.key < key                                         
             res = res.next                                        <font color=darkgreen>// Перейдём к следующей вершине в текущем уровне</font>
+
             res = res.next                                         
         '''if''' res.down = ''null''                                         <font color=darkgreen>// Если мы находимся на первом уровне</font>
+
         '''if''' res.down = ''null''                                   <font color=darkgreen>// Если мы находимся на первом уровне</font>
             '''return''' res                                             <font color=darkgreen>// Мы нашли искомый элемент</font>
+
             '''return''' res                                       <font color=darkgreen>// Мы нашли искомый элемент</font>
         '''return''' find(res.down, key)                                 <font color=darkgreen>// Иначе спустимся на один уровень ниже</font>
+
         '''return''' find(res.down, key)                           <font color=darkgreen>// Иначе спустимся на один уровень ниже</font>
  
 
Для того, чтобы найти элемент с ключом <tex>\mathtt{key}</tex> в списке с пропусками <tex>\mathtt{skip}</tex> необходимо запустить <tex>\mathtt{find}</tex> следующим образом
 
Для того, чтобы найти элемент с ключом <tex>\mathtt{key}</tex> в списке с пропусками <tex>\mathtt{skip}</tex> необходимо запустить <tex>\mathtt{find}</tex> следующим образом
Строка 72: Строка 74:
  
 
===Вставка элемента===
 
===Вставка элемента===
Добавить элемент в список можно следующим образом:
+
Алгоритм вставки элементов в список с пропусками состоит из следующих шагов:
# Начинаем вставку в самом верхнем уровне
+
# Начинаем вставку на самом верхнем уровне
# На каждом уровне находим место, куда надо вставить элемент
+
# Пока значение следующего элемента меньше ключа — переходим к следующему элементу.
# Если мы на первом уровне <tex>-</tex> вставляем элемент и кидаем монету. Если выпал «Орёл», то возвращаем ссылку на только что вставленный элемент, иначе возвращаем ''null''
+
# Если мы на первом уровне — вставляем элемент. Иначе спускаемся ниже и возвращаемся к шагу <tex>2</tex>.
# Если мы не на первом уровне, то вызываем рекурсивно функцию от нижнего уровня, и если нам вернулась ссылка на элемент <tex>-</tex> вставляем его на текущем уровне и снова кидаем монету, иначе просто возвращаем ''null''
+
# Кидаем монетку и если выпал «Орёл», то возвращаем ссылку на текущий элемент, иначе ''null''. Если мы были не на первом уровне и нам вернули ''null'' — возвращаем его без броска монетки.
 +
 
 +
Отдельно стоит обработать случай, когда вставка нового элемента увеличивает число уровней. Тогда необходимо создать ещё один отсортированный список, в котором будет всего один текущий элемент, и не забыть присвоить списку с пропусками новую ссылку на верхний уровень. Будем считать, что вставка каждого нового элемента увеличивает число уровней не более, чем на один.
  
 
====Псевдокод====
 
====Псевдокод====
 +
Функция <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.key < key
 +
            res = res.next                                   
 +
        '''node''' down_node
 +
        '''if''' res.down = ''null''
 +
            down_node = ''null''
 +
        '''else'''
 +
            down_node = insert(res.down, key)
 +
        '''if''' down_node <tex>\neq</tex> ''null''
 +
            res.next = node(key, down_node, res.next)
 +
            '''if''' random(0, 1) > 0.5                              <font color=darkgreen>// Бросок монеты</font>
 +
                '''return''' res.next
 +
            '''return''' ''null''
 +
        '''return''' ''null''
 +
 +
Для того, чтобы вставить элемент с ключом <tex>\mathtt{key}</tex> в список с пропусками <tex>\mathtt{skip}</tex> необходимо вызвать следующую функцию
 +
 +
    '''function''' insert_element('''list''' skip, '''K''' key)
 +
        '''node''' res = insert(skip.head, key)
 +
        '''if''' res <tex>\neq</tex> ''null''
 +
            '''list''' lvl;
 +
            lvl.head.next = node(key, res, lvl.tail)
 +
            skip = lvl
  
 
===Удаление элемента===
 
===Удаление элемента===
 
Алгоритм удаления элемента выглядит следующим образом:
 
Алгоритм удаления элемента выглядит следующим образом:
 
# Начинаем удалять элемент с верхнего уровня
 
# Начинаем удалять элемент с верхнего уровня
# Если мы на первом уровне и нашли элемент <tex>-</tex> то просто удаляем элемент
+
# Переходим к следующему элементу, пока значение следующего элемента меньше ключа
# Иначе спускаемся ниже и также удаляем элемент с текущего уровня, если он есть
+
# Если элемент существует на данном уровне — удаляем его с этого уровня. Если мы не на первом уровне, то удаляем элемент ещё с нижнего уровня.
 
====Псевдокод====
 
====Псевдокод====
 +
    '''function''' delete('''node''' res, '''K''' key)
 +
        '''while''' res.next <tex>\neq</tex> ''null'' '''and''' res.next.key < key
 +
            res = res.next
 +
        '''if''' res.down <tex>\neq</tex> ''null''
 +
            delete(res.down, key)
 +
        '''if''' res.next <tex>\neq</tex> ''null'' '''and''' res.next.key = key
 +
            res.next = res.next.next;
  
 
==Использование нечестной монеты==
 
==Использование нечестной монеты==
Строка 91: Строка 127:
  
 
Для крайних распределений:
 
Для крайних распределений:
* <tex>\{0, 1\}</tex> {{---}} <tex>O(n)</tex> <tex>-</tex> поиск, добавление и удаления элемента, поскольку мы вместо нескольких списком используем по факту всего один список.
+
* <tex>\{0, 1\}</tex> <tex>O(n)</tex> поиск, добавление и удаления элемента, поскольку мы вместо нескольких списков используем по факту один.
* <tex>\{1, 0\}</tex> {{---}} зависит от реализации алгоритма. Если при каждой вставке у нас образуется не более одного уровня, то количество уровней будет равным <tex>n</tex>, значит время поиска будет равным <tex>O(n)</tex>.
+
* <tex>\{1, 0\}</tex> зависит от реализации алгоритма. Если при каждой вставке у нас образуется не более одного уровня, то количество уровней будет равным <tex>n</tex>, значит время поиска будет равным <tex>O(n)</tex>.
  
 
==Применение==
 
==Применение==
Строка 99: Строка 135:
 
* Быстрая вставка элемента, поскольку не требуется каким-либо образом изменять другие элементы (только предыдущий элемент)
 
* Быстрая вставка элемента, поскольку не требуется каким-либо образом изменять другие элементы (только предыдущий элемент)
 
* Проще реализовать, чем сбалансированные деревья или хеш-таблицы
 
* Проще реализовать, чем сбалансированные деревья или хеш-таблицы
* Следующий элемент достаётся за <tex>O(1)</tex>
+
* Следующий элемент достаётся за <tex>O(1)</tex> (при условии, что у нас есть ссылка не текущий)
 
* Легко модифицировать под различные задачи
 
* Легко модифицировать под различные задачи
  
 
===Нахождение всех интервалов, покрывающих данную точку===
 
===Нахождение всех интервалов, покрывающих данную точку===
 +
 
{{Задача
 
{{Задача
|definition = Есть <tex>q</tex> запросов двух видов:
+
|definition = Поступают запросы двух видов:
 
# Добавить интервал <tex>[L, R]</tex>
 
# Добавить интервал <tex>[L, R]</tex>
# Задана точка <tex>x</tex>.
+
# Для заданной точки <tex>x</tex> вычислить количество интервалов, которые её покрывают.  
На каждый запрос второго типа необходимо вывести количество интервалов, которые покрывают данную точку.
 
 
}}
 
}}
  
Решение данной задачи заключается в том, что когда приходит запрос добавления интервала, то мы добавляем левую и правую границу (если до этого они не были добавлены). Потом идём от левой точки до правой и на пути прибавляем <tex>1</tex>. Когда нам приходит запрос второго типа, то рекурсивно спускаемся сверху вниз и суммируем все числа, лежащие на пути между двумя ближайшими вершинами, между которыми находится заданная точка. На каждый запрос мы отвечаем онлайн за <tex>O(\log{n})</tex> времени.
+
Для решения данной задачи воспользуемся списком с пропусками. Когда нам приходит запрос первого типа, то мы просто добавляем числа <tex>L</tex> и <tex>R</tex> в список с пропусками (если какое-то из чисел уже было добавлено, то второй раз мы его не добавляем). После этого идём с самого верхнего уровня и до нижнего и если два элемента на каком-либо уровне находятся между <tex>L</tex> и <tex>R</tex>, то прибавляем <tex>1</tex> к отрезку и переходим к следующему элементу на том же уровне, иначе спускаемся вниз. Когда нам приходит запрос второго типа — на каждом уровне находим такие два элемента, что <tex>x</tex> лежит между ними, и к ответу прибавляем значение на данном отрезке. Итого оба запросы мы обрабатываем за <tex>O(\log{n})</tex>.
  
 
==См. также==
 
==См. также==

Версия 13:01, 24 марта 2019

Пример списка с пропусками

Список с пропусками (англ. skip list) — вероятностная структура данных, позволяющая в среднем за [math]O(\log(n))[/math] времени выполнять операции добавления, удаления и поиска элементов.

Список с пропусками состоит из нескольких уровней, на каждом из которых находится отсортированный связный список. На самом нижнем (первом) уровне располагаются все элементы. Дальше около половины элементов в таком же порядке располагаются на втором, почти четверть — на третьем и так далее, но при этом известно, что если элемент расположен на уровне [math]i[/math], то он также расположен на всех уровнях, номера которых меньше [math]i[/math].

Построение

Односвязный отсортированный список
Получившийся список с пропусками

Допустим, что нам задан односвязный отсортированный список и мы хотим построить на его основе список с пропусками, позволяющий в среднем за [math]O(\log{n})[/math] времени выполнять операции добавления, удаления и поиска элементов.

На самом нижнем уровне списка с пропусками мы расположим исходный список. На втором уровне — всё элементы с чётными номерами, причём каждый элемент будет ссылаться на соответствующий ему элемент на нижнем уровне. Таким же образом построим и третий уровень, куда будем добавлять только те элементы, номера которых кратны четырём. Аналогичным образом построим и последующие уровни.

Псевдокод

Каждый уровень списка с пропусками содержит отсортированный односвязный список, у которого есть начало [math]\mathtt{head}[/math] и конец [math]\mathtt{tail}[/math]. Для выполнения операций на списке с пропусками необходимо передавать в качестве аргумента ссылку на начало односвязного списка, расположенного на самом верхнем уровне.

Элементы односвязного списка — вершины [math]\mathtt{node}[/math], у которых есть [math]3[/math] поля:

  • [math]\mathtt{next}[/math] — ссылка на следующий элемент списка
  • [math]\mathtt{key}[/math] — ключ, который хранится в данной вершине
  • [math]\mathtt{down}[/math] — ссылка на соответственный элемент, лежащий уровнем ниже

Также известно, что [math]\mathtt{head{.}key} = -\infty \ [/math] и [math]\mathtt{tail{.}key} = \infty[/math].

Функция [math]\ \mathtt{build\_lvl} \ [/math] возвращает новый уровень списка с пропусками на основе предыдущего построенного уровня.

   list build_lvl(list lvl)                   
       list next_lvl 
       node i = lvl.head                      
       node cur = next_lvl.head 
       while i [math]\neq[/math] null and i.next [math]\neq[/math] null
           cur.next = node(key, i, cur.next)                  // Конструктор node(key, down, next) возвращает новую вершину с ключом key, ссылками down на нижний и next на следующий элемент
           cur = cur.next
           i = i.next.next                                    // Переход к следующему чётному элементу
       return next_lvl 

Функция [math]\ \mathtt{skip\_list} \ [/math] принимает в качестве аргумента односвязный отсортированный список и возвращает новый список с пропусками, построенный на его основе.

   list skip_list(list l):
       list lvl = build_lvl(l)                                // Построение первого уровня
       while lvl.size > 2                     
           lvl = build_lvl(lvl)                       
       return lvl                                             // Возвращает ссылку на начало верхнего уровня

Операции над структурой

Поиск элемента

Пример поиска числа [math]8[/math]

Алгоритм поиска элементе в списке с пропусками состоит из следующих операций:

  1. Начинаем поиск элемента в самом верхнем уровне
  2. Переходим к следующему элементу списка, пока значение в следующей ячейке меньше
  3. Переместиться на один уровень вниз и перейти к шагу [math]2[/math]. Если мы уже на первом уровне — прекратить поиск и вернуть ссылку на текущую вершину

В конце алгоритма функция вернёт элемент, значение которого не меньше ключа [math]\mathtt{key}[/math] или ссылку на конец списка на первом уровне.

Если в качестве случайного источника мы будем использовать честную монету, то в среднем случае будет [math]\log{n}[/math] уровне. На самом верхнем уровне будет не более двух элементов. Тогда на каждом уровне в среднем нужно проверить не более двух элементов (в противном случае могли бы вместо двух нижних элементов проверить ещё один уровнем выше). Уровней всего [math]\log{n}[/math], откуда вытекает оценка времени поиска элемента в [math]O(\log{n})[/math].

Псевдокод

Функция [math]\mathtt{find}[/math] возвращает ссылку на элемент, значение которого не меньше [math]\mathtt{key}[/math]. В случае, если все элементы в списке с пропусками меньше [math]\mathtt{key}[/math], то возвращается ссылка на конец списка с пропусками.

   T find(node res, K key)
       while res.key < key                                        
           res = res.next                                         
       if res.down = null                                    // Если мы находимся на первом уровне
           return res                                        // Мы нашли искомый элемент
       return find(res.down, key)                            // Иначе спустимся на один уровень ниже

Для того, чтобы найти элемент с ключом [math]\mathtt{key}[/math] в списке с пропусками [math]\mathtt{skip}[/math] необходимо запустить [math]\mathtt{find}[/math] следующим образом

   find(skip.head, key)

Вставка элемента

Алгоритм вставки элементов в список с пропусками состоит из следующих шагов:

  1. Начинаем вставку на самом верхнем уровне
  2. Пока значение следующего элемента меньше ключа — переходим к следующему элементу.
  3. Если мы на первом уровне — вставляем элемент. Иначе спускаемся ниже и возвращаемся к шагу [math]2[/math].
  4. Кидаем монетку и если выпал «Орёл», то возвращаем ссылку на текущий элемент, иначе — null. Если мы были не на первом уровне и нам вернули null — возвращаем его без броска монетки.

Отдельно стоит обработать случай, когда вставка нового элемента увеличивает число уровней. Тогда необходимо создать ещё один отсортированный список, в котором будет всего один текущий элемент, и не забыть присвоить списку с пропусками новую ссылку на верхний уровень. Будем считать, что вставка каждого нового элемента увеличивает число уровней не более, чем на один.

Псевдокод

Функция [math]\mathtt{insert}[/math] возвращаем ссылку на вставленный элемент в списке, в котором находится [math]\mathtt{res}[/math], или null, если на монете выпала «Решка».

   node insert(node res, K key)
       while res.next [math]\neq[/math] null and res.next.key < key
           res = res.next                                    
       node down_node
       if res.down = null
           down_node = null
       else
           down_node = insert(res.down, key)
       if down_node [math]\neq[/math] null
           res.next = node(key, down_node, res.next)
           if random(0, 1) > 0.5                              // Бросок монеты
               return res.next
           return null
       return null

Для того, чтобы вставить элемент с ключом [math]\mathtt{key}[/math] в список с пропусками [math]\mathtt{skip}[/math] необходимо вызвать следующую функцию

   function insert_element(list skip, K key)
       node res = insert(skip.head, key)
       if res [math]\neq[/math] null
           list lvl;
           lvl.head.next = node(key, res, lvl.tail)
           skip = lvl

Удаление элемента

Алгоритм удаления элемента выглядит следующим образом:

  1. Начинаем удалять элемент с верхнего уровня
  2. Переходим к следующему элементу, пока значение следующего элемента меньше ключа
  3. Если элемент существует на данном уровне — удаляем его с этого уровня. Если мы не на первом уровне, то удаляем элемент ещё с нижнего уровня.

Псевдокод

   function delete(node res, K key)
       while res.next [math]\neq[/math] null and res.next.key < key
           res = res.next
       if res.down [math]\neq[/math] null
           delete(res.down, key)
       if res.next [math]\neq[/math] null and res.next.key = key
           res.next = res.next.next;

Использование нечестной монеты

Вместо честной монеты с распределением [math]\left\{\dfrac{1}{2}, \ \dfrac{1}{2}\right\}[/math] можно взять в качестве случайного источника нечестную монету с распределением [math]\{p,q\}[/math] (с вероятностью [math]p[/math] выпадает «Орёл»). Тогда математическим ожиданием количества элементов на уровне [math]k[/math] будет [math]n \cdot p^k[/math]. Время поиска будет равно [math]O\left( \dfrac{1}{p} \log_{\frac{1}{p}} {n} \right)[/math] (на [math]i[/math]-ом уровне элементов будет почти в [math]\dfrac{1}{p}[/math] раз больше, чем на [math](i+1)[/math]-ом, значит на каждом уровне пройдём не более [math]\dfrac{1}{p}[/math] элементов, а уровней всего [math]\log_{\frac{1}{p}} {n}[/math]).

Для крайних распределений:

  • [math]\{0, 1\}[/math][math]O(n)[/math] — поиск, добавление и удаления элемента, поскольку мы вместо нескольких списков используем по факту один.
  • [math]\{1, 0\}[/math] — зависит от реализации алгоритма. Если при каждой вставке у нас образуется не более одного уровня, то количество уровней будет равным [math]n[/math], значит время поиска будет равным [math]O(n)[/math].

Применение

Список с пропусками применяется во многих приложениях, поскольку имеет ряд преимуществ.

  • Быстрая вставка элемента, поскольку не требуется каким-либо образом изменять другие элементы (только предыдущий элемент)
  • Проще реализовать, чем сбалансированные деревья или хеш-таблицы
  • Следующий элемент достаётся за [math]O(1)[/math] (при условии, что у нас есть ссылка не текущий)
  • Легко модифицировать под различные задачи

Нахождение всех интервалов, покрывающих данную точку

Задача:
Поступают запросы двух видов:
  1. Добавить интервал [math][L, R][/math]
  2. Для заданной точки [math]x[/math] вычислить количество интервалов, которые её покрывают.


Для решения данной задачи воспользуемся списком с пропусками. Когда нам приходит запрос первого типа, то мы просто добавляем числа [math]L[/math] и [math]R[/math] в список с пропусками (если какое-то из чисел уже было добавлено, то второй раз мы его не добавляем). После этого идём с самого верхнего уровня и до нижнего и если два элемента на каком-либо уровне находятся между [math]L[/math] и [math]R[/math], то прибавляем [math]1[/math] к отрезку и переходим к следующему элементу на том же уровне, иначе спускаемся вниз. Когда нам приходит запрос второго типа — на каждом уровне находим такие два элемента, что [math]x[/math] лежит между ними, и к ответу прибавляем значение на данном отрезке. Итого оба запросы мы обрабатываем за [math]O(\log{n})[/math].

См. также

Источники информации