<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="ru">
		<id>http://neerc.ifmo.ru/wiki/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=217.66.159.60&amp;*</id>
		<title>Викиконспекты - Вклад участника [ru]</title>
		<link rel="self" type="application/atom+xml" href="http://neerc.ifmo.ru/wiki/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=217.66.159.60&amp;*"/>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=%D0%A1%D0%BB%D1%83%D0%B6%D0%B5%D0%B1%D0%BD%D0%B0%D1%8F:%D0%92%D0%BA%D0%BB%D0%B0%D0%B4/217.66.159.60"/>
		<updated>2026-05-19T18:00:41Z</updated>
		<subtitle>Вклад участника</subtitle>
		<generator>MediaWiki 1.30.0</generator>

	<entry>
		<id>http://neerc.ifmo.ru/wiki/index.php?title=%D0%A1%D0%B2%D0%B5%D1%80%D1%85%D0%B1%D1%8B%D1%81%D1%82%D1%80%D1%8B%D0%B9_%D1%86%D0%B8%D1%84%D1%80%D0%BE%D0%B2%D0%BE%D0%B9_%D0%B1%D0%BE%D1%80&amp;diff=31771</id>
		<title>Сверхбыстрый цифровой бор</title>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=%D0%A1%D0%B2%D0%B5%D1%80%D1%85%D0%B1%D1%8B%D1%81%D1%82%D1%80%D1%8B%D0%B9_%D1%86%D0%B8%D1%84%D1%80%D0%BE%D0%B2%D0%BE%D0%B9_%D0%B1%D0%BE%D1%80&amp;diff=31771"/>
				<updated>2013-06-11T11:56:48Z</updated>
		
		<summary type="html">&lt;p&gt;217.66.159.60: /* Ссылки */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Цифровой бор==&lt;br /&gt;
Работаем с целыми числами, которые представляются с помощью &amp;lt;tex&amp;gt;w&amp;lt;/tex&amp;gt; битов, аналогично [[Дерево ван Эмде Боаса | дереву Ван Эмде Боаса]]. Мы можем их складывать, вычитать, умножать, сдвигать, производить с ними логические операции, адресоваться ими. В модели памяти &amp;lt;tex&amp;gt;unit~cost~RAM&amp;lt;/tex&amp;gt;, которая сейчас применима к большинству процессоров, эти операции могут быть выполнены за &amp;lt;tex&amp;gt;O(1)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Цифровой бор {{---}} [[Бор | бор]], в котором в качестве строк используются двоичные записи чисел, включая ведущие нули.&lt;br /&gt;
Таким образом он имеет глубину &amp;lt;tex&amp;gt;w&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Цифровой бор поддерживает операции &amp;lt;tex&amp;gt;insert, ~remove, ~find, ~succ, ~pred&amp;lt;/tex&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
Добавление вершины происходит так же, как и в обычном боре.&lt;br /&gt;
Удаление можно выполнять лениво {{---}} просто убирая пометку с листа. А можно хранить число пометок в поддереве и удалять вершину, если это число стало равным нулю.&lt;br /&gt;
&lt;br /&gt;
===succ===&lt;br /&gt;
Поиск следующего элемента осуществляется проходом от корня до вершины, из которой не можем пойти в нужную сторону. Если не смогли пойти влево &amp;lt;tex&amp;gt;(&amp;lt;/tex&amp;gt;по ребру &amp;lt;tex&amp;gt;0)&amp;lt;/tex&amp;gt;, то ответ {{---}} минимум в правом поддереве. Если не смогли пойти вправо, поднимаемся наверх, пока являемся правым ребенком; если стали левым, то поднимаемся, пока у вершины нет правого ребенка (если такой ситуации нет, то запрос больше всех элементов). Тогда ответ {{---}} минимум в правом поддереве.&lt;br /&gt;
&lt;br /&gt;
Преимущества: простая реализация, занимает &amp;lt;tex&amp;gt;O(n \cdot w)&amp;lt;/tex&amp;gt; памяти, все операции выполняются за &amp;lt;tex&amp;gt;O(w)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Хуже дерева Ван Эмде Боаса по скорости, но памяти занимает меньше.&lt;br /&gt;
&lt;br /&gt;
==Быстрый цифровой бор (x-fast-trie)==&lt;br /&gt;
[[File:Tsifrovoybor.jpg|thumb|500px|x-fast-trie]]&lt;br /&gt;
[[File:Min amp amp max.jpg|thumb|right|500px|Ссылки на минимум и максимум]]&lt;br /&gt;
Он по-прежнему будет занимать &amp;lt;tex&amp;gt;O(n \cdot w)&amp;lt;/tex&amp;gt; памяти, но немодифицирующие операции &amp;lt;tex&amp;gt;(read{-}only)&amp;lt;/tex&amp;gt; будут выполняться за &amp;lt;tex&amp;gt;O(\log w)&amp;lt;/tex&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
Улучшим структуру: было два слабых места {{---}} подниматься вверх и искать минимум.&lt;br /&gt;
&lt;br /&gt;
===succOrPred===&lt;br /&gt;
Первая модификация {{---}} занесем все элементы  в двусвязный список в порядке, в котором они лежат в боре. Добавим операцию &amp;lt;tex&amp;gt;succOrPred&amp;lt;/tex&amp;gt;, которая возвращает следующий или предыдущий в зависимости от того, что проще. Работает она так: спускается вниз до наибольшего общего префикса, а потом до минимума в правом дереве или же до максимума в левом. Тогда мы получим какой-то элемент списка и не более чем за два шага в списке сможем получить ответ на запрос.&lt;br /&gt;
&lt;br /&gt;
Вторая модификация {{---}} добавим ссылки.&lt;br /&gt;
Операции поиска минимума и максимума дорогие, выполним их за &amp;lt;tex&amp;gt;O(1)&amp;lt;/tex&amp;gt;. Теперь становится понятно, что необязательно спускаться до минимума или максимума в дереве. Если у вершину нет левого сына, отметим это одним битом, а вместо ссылки на левого сына сделаем ссылку на минимум в правом поддереве, что удобно для нашей реализации &amp;lt;tex&amp;gt;succOrPred&amp;lt;/tex&amp;gt;. Если нет правого сына, то храним ссылку на максимум в левом поддереве. &lt;br /&gt;
&lt;br /&gt;
===insert===&lt;br /&gt;
При вставке с помощью &amp;lt;tex&amp;gt;succOrPred&amp;lt;/tex&amp;gt; и двусвязного списка находим следующий и предыдущий элементы и вставляем нужный элемент между ними. А также при создании новой вершины (у которой будет только один ребенок) на обратном пути рекурсии заменяем ссылки.&lt;br /&gt;
&amp;lt;code style = &amp;quot;display: inline-block;&amp;quot;&amp;gt;&lt;br /&gt;
  // prefixes {{---}} HashMap всех префиксов бора&lt;br /&gt;
  insert(x)&lt;br /&gt;
    '''if''' prefixes.contains(x): // ''x'' содержится в боре&lt;br /&gt;
      '''return'''&lt;br /&gt;
    Node ''left'' = pred(x), ''right'' = succ(x), ''nodex'' = new Node(x)&lt;br /&gt;
    insert ''nodex'' между ''left'' и ''right'' в двусвязном списке листьев&lt;br /&gt;
    root = insertNode(root, w, ''nodex)&lt;br /&gt;
    prefixes.addAll(allPrefixes(x))&lt;br /&gt;
&lt;br /&gt;
  insertNode(vertex, depth, x)&lt;br /&gt;
    '''if''' ''vertex'' == &amp;lt;tex&amp;gt; \varnothing &amp;lt;/tex&amp;gt;:&lt;br /&gt;
      ''vertex'' = new Node(left = &amp;lt;tex&amp;gt;\varnothing&amp;lt;/tex&amp;gt;, right = &amp;lt;tex&amp;gt;\varnothing&amp;lt;/tex&amp;gt;, terminal = depth == 0)&lt;br /&gt;
    '''if''' ''depth'' == 0:&lt;br /&gt;
      '''return''' ''vertex''&lt;br /&gt;
    '''if''' depthBit(x) == 0: // depth-й бит, т. е. соответствующий текущей глубине&lt;br /&gt;
      vertex.left = insertNode(vertex.left, depth - 1, x)&lt;br /&gt;
    '''else''':&lt;br /&gt;
      vertex.right = insertNode(vertex.right, depth - 1, x)&lt;br /&gt;
    '''if''' vertex.left == &amp;lt;tex&amp;gt; \varnothing &amp;lt;/tex&amp;gt;:&lt;br /&gt;
      vertex.mark = ''HASNOLEFTSON''&lt;br /&gt;
      vertex.left = ''x''&lt;br /&gt;
    '''else if''' vertex.right == &amp;lt;tex&amp;gt; \varnothing &amp;lt;/tex&amp;gt;:&lt;br /&gt;
      vertex.mark = ''HASNORIGHTSON''&lt;br /&gt;
      vertex.right = ''x''&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===delete===&lt;br /&gt;
Удаление происходит аналогично добавлению. Модифицируем бор, чтобы в вершине был счётчик, сколько у неё вершин в поддереве. Если 0, то удаляем её совсем. Иначе же, если удалился какой-то из сыновей, то надо обновить ссылки min и max. Это сделать просто {{---}} ссылкой на минимум (максимум) в правом (левом) поддереве становится &amp;lt;tex&amp;gt; successor ~(predecessor)&amp;lt;/tex&amp;gt; удалённой вершины.&lt;br /&gt;
&lt;br /&gt;
Вставка и удаление выполняются за &amp;lt;tex&amp;gt;O(w)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===binarySearch===&lt;br /&gt;
Пока что мы не добились асимптотического выигрыша {{---}} все операции по-прежнему выполняются за &amp;lt;tex&amp;gt;O(w)&amp;lt;/tex&amp;gt;. Теперь слабое место {{---}} это поиск наибольшего общего префикса. Будем искать его двоичным поиском. Для этого занесём префиксы всех чисел в &amp;lt;tex&amp;gt; HashMap &amp;lt;/tex&amp;gt; {{---}} [[Хеш-таблица |ассоциативный массив]], который по префиксу возвращает вершину в боре &amp;lt;tex&amp;gt;(&amp;lt;/tex&amp;gt;чтобы избежать проблемы с ведущими нулями, используем при поиске маску вида &amp;lt;tex&amp;gt;0...01...1)&amp;lt;/tex&amp;gt;. Запустим двоичный поиск по длине наибольшего общего префикса. Как только он вернет максимальный префикс, переходим в вершину (у этой вершины не может быть два сына, так как тогда поиск бы не завершился) и там за &amp;lt;tex&amp;gt;O(1)&amp;lt;/tex&amp;gt; находим минимум или масимум, и за &amp;lt;tex&amp;gt;O(1)&amp;lt;/tex&amp;gt; переходим по списку, если нужно.&lt;br /&gt;
&lt;br /&gt;
Итого операции &amp;lt;tex&amp;gt;find, succ&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;pred&amp;lt;/tex&amp;gt; будут выполняться за &amp;lt;tex&amp;gt;O(\log w)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
==Сверхбыстрый цифровой бор (y-fast-trie)==&lt;br /&gt;
[[File:Sverkhbystrybor.jpg|thumb|400px|y-fast-trie]]&lt;br /&gt;
&lt;br /&gt;
Теперь усовершенствуем &amp;lt;tex&amp;gt;x{-}fast{-}trie&amp;lt;/tex&amp;gt; до &amp;lt;tex&amp;gt;y{-}fast{-}trie&amp;lt;/tex&amp;gt;, который занимает &amp;lt;tex&amp;gt;O(n)&amp;lt;/tex&amp;gt; памяти, а все операции выполняются за &amp;lt;tex&amp;gt;O(\log w)&amp;lt;/tex&amp;gt;, правда, для модифицирующих операций эта оценка будет амортизированной.&lt;br /&gt;
&lt;br /&gt;
Уменьшим количество занимаемой памяти.&lt;br /&gt;
Пусть &amp;lt;tex&amp;gt;a_1 &amp;lt; a_2 &amp;lt; a_3 &amp;lt; ... &amp;lt; a_n&amp;lt;/tex&amp;gt; {{---}} числа, которые нужно хранить в боре.&lt;br /&gt;
Выберем какое-то &amp;lt;tex&amp;gt;k&amp;lt;/tex&amp;gt; (что за &amp;lt;tex&amp;gt;k&amp;lt;/tex&amp;gt; {{---}} будет видно дальше). Разобьём их на &amp;lt;tex&amp;gt;s&amp;lt;/tex&amp;gt; блоков размером от &amp;lt;tex dpi=150&amp;gt;\frac{k}{2}&amp;lt;/tex&amp;gt; до &amp;lt;tex&amp;gt;2k&amp;lt;/tex&amp;gt;, а точнее &amp;lt;tex&amp;gt;B_{11} &amp;lt; B_{12} &amp;lt; ... &amp;lt; B_{1n_{1}} &amp;lt; B_{21} &amp;lt; ... &amp;lt; B_{2n_{2}} &amp;lt; ... &amp;lt; B_{s1} &amp;lt; ... &amp;lt; B_{sn_{s}}&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Выберем в каждом блоке какого-нибудь представителя. И поместим этих представителей в &amp;lt;tex&amp;gt;x{-}fast{-}trie&amp;lt;/tex&amp;gt;. Всего в &amp;lt;tex&amp;gt;x{-}fast{-}trie&amp;lt;/tex&amp;gt; будет &amp;lt;tex dpi=150&amp;gt;O(\frac{2 \cdot n \cdot w} {k})&amp;lt;/tex&amp;gt; элементов. Поэтому если выбрать &amp;lt;tex&amp;gt;k = \Omega(w)&amp;lt;/tex&amp;gt;, то &amp;lt;tex&amp;gt;x{-}fast{-}trie&amp;lt;/tex&amp;gt; будет занимать &amp;lt;tex&amp;gt;O(n)&amp;lt;/tex&amp;gt; памяти.&lt;br /&gt;
&lt;br /&gt;
Каждый лист &amp;lt;tex&amp;gt;x{-}fast{-}trie&amp;lt;/tex&amp;gt; является представителем блока, а все остальные элементы блока (в т. ч. и представителя) подвесим к листу как сбалансированное двоичное дерево поиска. В дереве может храниться от &amp;lt;tex dpi=150&amp;gt;\frac{w}{2}&amp;lt;/tex&amp;gt; до &amp;lt;tex&amp;gt;2w&amp;lt;/tex&amp;gt; элементов, поэтому его высота {{---}} &amp;lt;tex&amp;gt;O(\log w)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Все деревья поиска занимают &amp;lt;tex&amp;gt;O(n)&amp;lt;/tex&amp;gt; памяти, и &amp;lt;tex&amp;gt;x{-}fast{-}trie&amp;lt;/tex&amp;gt; {{---}} &amp;lt;tex&amp;gt;O(n)&amp;lt;/tex&amp;gt; памяти. Поэтому &amp;lt;tex&amp;gt;y{-}fast{-}trie&amp;lt;/tex&amp;gt; тоже занимает &amp;lt;tex&amp;gt;O(n)&amp;lt;/tex&amp;gt; памяти.&lt;br /&gt;
&lt;br /&gt;
===find===&lt;br /&gt;
Находим &amp;lt;tex&amp;gt;succ(x)&amp;lt;/tex&amp;gt; среди представителей в &amp;lt;tex&amp;gt;x{-}fast{-}trie&amp;lt;/tex&amp;gt;, а потом запускаем поиск &amp;lt;tex&amp;gt;succ(x)&amp;lt;/tex&amp;gt; в дереве, подвешенном к листу &amp;lt;tex&amp;gt;x&amp;lt;/tex&amp;gt;, а также в дереве, подвешенном к листу &amp;lt;tex&amp;gt;pred(x)&amp;lt;/tex&amp;gt; среди представителей в &amp;lt;tex&amp;gt;x{-}fast{-}trie&amp;lt;/tex&amp;gt;. Представителем дерева является необязательно минимальный или максимальный элемент, поэтому нужно запустить в двух деревьях. Заметим, что мы ищем элемент только в двух деревьях, так как искомый элемент точно находится между своими следующим и предыдущим элементами. &lt;br /&gt;
&amp;lt;tex&amp;gt;O(\log w)&amp;lt;/tex&amp;gt; на поиск в &amp;lt;tex&amp;gt;x{-}fast{-}trie&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;O(\log w)&amp;lt;/tex&amp;gt; на поиск в деревьях поиска, поэтому итогая асимптотика {{---}} &amp;lt;tex&amp;gt;O(\log w)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt;succ&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;pred&amp;lt;/tex&amp;gt; выполняются аналогично.&lt;br /&gt;
&lt;br /&gt;
===insert===&lt;br /&gt;
Вставка элемента &amp;lt;tex&amp;gt;x&amp;lt;/tex&amp;gt; происходит следующим образом: найдём &amp;lt;tex&amp;gt;succ(x)&amp;lt;/tex&amp;gt; и вставим его в подвешенное к листу дерево. Но может возникнуть плохая ситуация: размер дерева станет &amp;lt;tex&amp;gt;2 \cdot w + 1&amp;lt;/tex&amp;gt;. Тогда поступим следующим образом: удалим наше дерево из &amp;lt;tex&amp;gt;x{-}fast{-}trie&amp;lt;/tex&amp;gt;, разделим его на элементы, из которых построим два дерева размером &amp;lt;tex&amp;gt;w&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;w + 1&amp;lt;/tex&amp;gt;, и вставим в &amp;lt;tex&amp;gt;x{-}fast{-}trie&amp;lt;/tex&amp;gt; их оба. &lt;br /&gt;
&lt;br /&gt;
===delete===&lt;br /&gt;
Удаление происходит аналогично, только если размер дерева станет &amp;lt;tex dpi=150&amp;gt;\frac{w}{2} - 1&amp;lt;/tex&amp;gt;, то надо его слить с любым соседним деревом. А если после слияния размер получившегося дерева станет больше &amp;lt;tex&amp;gt;2 \cdot w&amp;lt;/tex&amp;gt;, то надо его разделить аналогично предыдущему случаю.&lt;br /&gt;
&lt;br /&gt;
[[АВЛ-дерево |АВЛ-деревья]] или [[Красно-черное дерево |красно-чёрные]] позволяют выполнять слияние и разделение за линейное время, поэтому операции вставки и удаления выполняются за &amp;lt;tex&amp;gt;O(w)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===Асимптотика===&lt;br /&gt;
Заметим, что вставка, которая не модифицирует верхний бор, выполняется за истинный &amp;lt;tex&amp;gt;\log w&amp;lt;/tex&amp;gt;, также и &amp;lt;tex&amp;gt;succ, pred&amp;lt;/tex&amp;gt;.&lt;br /&gt;
Плохие операции {{---}} которые модифицируют верхний бор. Но они не происходят слишком часто.&lt;br /&gt;
&lt;br /&gt;
Применим амортизационный анализ, используя метод предоплаты. Копим деньги на дешевых операциях. Слияние массивов осуществляется за &amp;lt;tex&amp;gt;O(w)&amp;lt;/tex&amp;gt;, как и разделение. Поэтому если мы накопим &amp;lt;tex&amp;gt;\Omega(w)&amp;lt;/tex&amp;gt; дополнительных денег на дешёвых операциях, то сумеем расплатиться за все остальные, просто положив константное число дополнительных монет во время каждой операции. Худший для разделения случай произойдет, если мы дальше будем только добавлять элементы {{---}} было &amp;lt;tex dpi=150&amp;gt;\frac{w}{2} - 1&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;2 \cdot w&amp;lt;/tex&amp;gt;, слили, стало больше &amp;lt;tex&amp;gt;2 \cdot w&amp;lt;/tex&amp;gt;, разделили, таким образом получили два дерева с &amp;lt;tex dpi=150&amp;gt;\frac{5\cdot w}{4}&amp;lt;/tex&amp;gt; элементами. Худший случай для слияния, когда у нас &amp;lt;tex&amp;gt;w&amp;lt;/tex&amp;gt; элементов (просиходит после разделения &amp;lt;tex&amp;gt;2 \cdot w + 1&amp;lt;/tex&amp;gt; дерева). Заметим, что в каждом случае дерево находится на расстоянии &amp;lt;tex&amp;gt;\Theta(w)&amp;lt;/tex&amp;gt; от границ. Следовательно, если мы будем класть определённое константное число монет, то скопим их достаточно, чтобы расплатиться за дорогие операции слияния и разделения деревьев. &lt;br /&gt;
&lt;br /&gt;
Получаем амортизированную оценку &amp;lt;tex&amp;gt;O(\log w)&amp;lt;/tex&amp;gt; и истинную {{---}} &amp;lt;tex&amp;gt;O(w)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Получилась та же оценка на операции, что и у дерева Ван Эмде Боаса, но структура данных занимает &amp;lt;tex&amp;gt;O(n)&amp;lt;/tex&amp;gt; памяти.&lt;br /&gt;
&lt;br /&gt;
==Ссылки==&lt;br /&gt;
*[[wikipedia:Y-fast_trie | Y-fast trie {{---}} Wikipedia]]&lt;br /&gt;
*[[wikipedia:X-fast_trie | X-fast trie {{---}} Wikipedia]]&lt;br /&gt;
*[http://compscicenter.ru/program/lecture/6902 Лекция А. С. Станкевича в Computer Science Center]&lt;br /&gt;
&lt;br /&gt;
[[Категория: Дискретная математика и алгоритмы]]&lt;br /&gt;
[[Категория: Деревья поиска]]&lt;/div&gt;</summary>
		<author><name>217.66.159.60</name></author>	</entry>

	<entry>
		<id>http://neerc.ifmo.ru/wiki/index.php?title=%D0%9B%D0%B5%D0%B2%D0%BE%D1%81%D1%82%D0%BE%D1%80%D0%BE%D0%BD%D0%BD%D1%8F%D1%8F_%D0%BA%D1%83%D1%87%D0%B0&amp;diff=31769</id>
		<title>Левосторонняя куча</title>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=%D0%9B%D0%B5%D0%B2%D0%BE%D1%81%D1%82%D0%BE%D1%80%D0%BE%D0%BD%D0%BD%D1%8F%D1%8F_%D0%BA%D1%83%D1%87%D0%B0&amp;diff=31769"/>
				<updated>2013-06-11T11:53:20Z</updated>
		
		<summary type="html">&lt;p&gt;217.66.159.60: /* Ссылки */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Файл:LeftistHeap.jpg|400px|thumb|right|Левосторонняя куча]]&lt;br /&gt;
==Определение==&lt;br /&gt;
Левосторонние деревья были изобретены Кларком Крейном (Clark Allan Crane), свое название они получили из-за того, что левое поддерево обычно длиннее правого.&lt;br /&gt;
{{Определение&lt;br /&gt;
|definition='''Левосторонняя куча (leftist heap)''' {{---}} двоичное левосторонее [[Дерево, эквивалентные определения|дерево]] (не обязательно сбалансированное), но с соблюдением [[Двоичная куча#Определение|порядка кучи]] (heap order).}}&lt;br /&gt;
{{Лемма&lt;br /&gt;
|id=lemma1&lt;br /&gt;
|about=1&lt;br /&gt;
|statement=В двоичном дереве с &amp;lt;tex&amp;gt;n&amp;lt;/tex&amp;gt; вершинами существует свободная позиция на глубине не более &amp;lt;tex&amp;gt;\log{n}&amp;lt;/tex&amp;gt;.&lt;br /&gt;
|proof=Если бы все свободные позиции были на глубине более логарифма, то мы получили бы полное дерево с количеством вершин более &amp;lt;tex&amp;gt;n&amp;lt;/tex&amp;gt;. }}&lt;br /&gt;
&lt;br /&gt;
Левосторонняя куча накладывает на двоичное дерево дополнительное условие.&lt;br /&gt;
Ближайшая свободная позиция должна быть самой правой позицией в дереве. То есть помимо обычного условия кучи выполняется следующее:&lt;br /&gt;
&lt;br /&gt;
{{Определение&lt;br /&gt;
|definition=Условие левосторонней кучи. Пусть &amp;lt;tex&amp;gt;dist(u)&amp;lt;/tex&amp;gt; {{---}} расстояние от вершины &amp;lt;tex&amp;gt;u&amp;lt;/tex&amp;gt; до ближайшей свободной позиции в ее поддереве. У пустых позиций &amp;lt;tex&amp;gt;dist = 0&amp;lt;/tex&amp;gt;. Тогда потребуем для любой вершины &amp;lt;tex&amp;gt;u : dist(u.L)\ge dict(u.R)&amp;lt;/tex&amp;gt;.}}&lt;br /&gt;
&lt;br /&gt;
Если для какой- то вершины это свойство не выполняется, то это легко устраняется: можно за &amp;lt;tex&amp;gt;O(1)&amp;lt;/tex&amp;gt; поменять местами левого и правого ребенка, что не повлияет на порядок кучи.&lt;br /&gt;
&lt;br /&gt;
==Поддерживаемые операции==&lt;br /&gt;
===merge===&lt;br /&gt;
Слияние двух куч. &lt;br /&gt;
  &lt;br /&gt;
  '''merge'''(x, y) // x, y {{---}} корни двух деревьев&lt;br /&gt;
    '''if''' x == &amp;lt;tex&amp;gt; \varnothing &amp;lt;/tex&amp;gt;: '''return''' y&lt;br /&gt;
    '''if''' y == &amp;lt;tex&amp;gt; \varnothing &amp;lt;/tex&amp;gt;: '''return''' x&lt;br /&gt;
    '''if''' y.key &amp;lt; x.key:&lt;br /&gt;
      x &amp;lt;tex&amp;gt;\leftrightarrow&amp;lt;/tex&amp;gt; y&lt;br /&gt;
    // Воспользуемся тем, что куча левосторонняя. Правая ветка {{---}} самая короткая и не длиннее логарифма.&lt;br /&gt;
    // Пойдем направо и сольем правое поддерево с у.&lt;br /&gt;
    x.R = '''merge'''(x.R, y)&lt;br /&gt;
    // Могло возникнуть  нарушение левостороннести кучи.&lt;br /&gt;
    '''if''' dist(x.R) &amp;gt; dist(x.L):&lt;br /&gt;
      x.L &amp;lt;tex&amp;gt;\leftrightarrow&amp;lt;/tex&amp;gt; x.R&lt;br /&gt;
    dist(x) = min(dist(x.L), dist(x.R)) + 1 // пересчитаем расстояние до ближайшей свободной позиции&lt;br /&gt;
    '''return''' x&lt;br /&gt;
    // Каждый раз идем из уже существующей вершины только в правое поддерево {{---}} не более логарифма вызовов (по лемме)&lt;br /&gt;
&lt;br /&gt;
Левосторонняя куча относится к сливаемым кучам: остальные операции легко реализуются с помощью операции слияния.&lt;br /&gt;
&lt;br /&gt;
===insert===&lt;br /&gt;
Вставка новой вершины в дерево. Новое левостороннее дерево, состоящее из одной вершины, сливается с исходным.&lt;br /&gt;
===extractMin===&lt;br /&gt;
Как и у любой другой двоичной кучи, минимум хранится в корне. Извлекаем минимальное значение, удаляем корень, сливаем левое и правое поддерево корня. Возвращает пару из извлеченной вершины и новой кучи.&lt;br /&gt;
===delete===&lt;br /&gt;
Аналогично удаляется любой элемент {{---}} на его место ставится результат слияния его детей. Но так просто любой элемент удалить нельзя {{---}} на пути от этого элемента к корню может нарушиться левостороннесть кучи. А до корня мы дойти не можем, так как элемент может находиться на линейной глубине. Поэтому удаление реализуется с помощью &amp;lt;tex&amp;gt;decrease key&amp;lt;/tex&amp;gt;. Уменьшаем ключ до &amp;lt;tex&amp;gt;-\infty&amp;lt;/tex&amp;gt;, затем извлекаем минимальное значение. &lt;br /&gt;
===decreaseKey===&lt;br /&gt;
{{Лемма&lt;br /&gt;
|id=lemma2&lt;br /&gt;
|about=2&lt;br /&gt;
|statement=У левостороннего дерева с правой ветвью длинны &amp;lt;tex&amp;gt;h&amp;lt;/tex&amp;gt; количество узлов &amp;lt;tex&amp;gt;n \ge 2^{h} - 1&amp;lt;/tex&amp;gt;.&lt;br /&gt;
|proof=Индукция по h.&lt;br /&gt;
&lt;br /&gt;
При &amp;lt;tex&amp;gt;h = 1&amp;lt;/tex&amp;gt; {{---}} верно.&lt;br /&gt;
&lt;br /&gt;
При &amp;lt;tex&amp;gt;h &amp;gt; 1&amp;lt;/tex&amp;gt; левое и правое поддеревья исходного дерева левосторонние, а &amp;lt;tex&amp;gt;dist&amp;lt;/tex&amp;gt; от их корней больше либо равен &amp;lt;tex&amp;gt;h - 1&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
По индукции число узлов в каждом из них больше или равно &amp;lt;tex&amp;gt;2^{h - 1} - 1&amp;lt;/tex&amp;gt;, тогда во все дереве &amp;lt;tex&amp;gt;n \ge (2^{h - 1} - 1) + (2^{h - 1} - 1) + 1 = 2^{h} - 1&amp;lt;/tex&amp;gt; узлов.}}&lt;br /&gt;
====Алгоритм====&lt;br /&gt;
* Найдем узел &amp;lt;tex&amp;gt;x&amp;lt;/tex&amp;gt;, вырежем поддерево с корнем в этом узле.&lt;br /&gt;
* Пройдем от предка вырезанной вершины, при этом пересчитывая &amp;lt;tex&amp;gt;dist&amp;lt;/tex&amp;gt;. Если &amp;lt;tex&amp;gt;dist&amp;lt;/tex&amp;gt; левого сына вершины меньше &amp;lt;tex&amp;gt;dist&amp;lt;/tex&amp;gt; правого, то меняем местами поддеревья.&lt;br /&gt;
* Уменьшаем ключ данного узла и сливаем два дерева: исходное и вырезанное. &lt;br /&gt;
{{Лемма&lt;br /&gt;
|id=lemma3&lt;br /&gt;
|about=3&lt;br /&gt;
|statement= Нужно транспонировать не более &amp;lt;tex&amp;gt;\log{n}&amp;lt;/tex&amp;gt; поддеревьев. &lt;br /&gt;
|proof=Длина пути от вершины до корня может быть и &amp;lt;tex&amp;gt;O(n)&amp;lt;/tex&amp;gt;, но нам не нужно подниматься до корня {{---}} достаточно подняться до вершины, у которой свойство левосторонней кучи уже выполнено. Транспонируем только если &amp;lt;tex&amp;gt;dist(x.L) &amp;lt; dist(x.R)&amp;lt;/tex&amp;gt;, но &amp;lt;tex&amp;gt;dist(x.R) \le \log{n}&amp;lt;/tex&amp;gt;. На каждом шаге, если нужно транспонируем и увеличиваем &amp;lt;tex&amp;gt;dist++&amp;lt;/tex&amp;gt;, тогда  &amp;lt;tex&amp;gt;dist&amp;lt;/tex&amp;gt; увеличится до &amp;lt;tex&amp;gt;\log{n}&amp;lt;/tex&amp;gt; и обменов уже не надо будет делать.}}&lt;br /&gt;
Таким образом, мы восстановили левостороннесть кучи за &amp;lt;tex&amp;gt;O(\log{n})&amp;lt;/tex&amp;gt;. Поэтому асимптотика операции &amp;lt;tex&amp;gt; decreaseKey &amp;lt;/tex&amp;gt; {{---}} &amp;lt;tex&amp;gt;O(\log{n})&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Построение кучи за O(n)==&lt;br /&gt;
Храним список левосторонних куч. Пока их количество больше &amp;lt;tex&amp;gt;1&amp;lt;/tex&amp;gt;, из начала списка достаем две кучи, сливаем их и кладем в конец списка.&lt;br /&gt;
&lt;br /&gt;
Покажем, почему это будет работать за &amp;lt;tex&amp;gt; O(n) &amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Пусть на &amp;lt;tex&amp;gt; i &amp;lt;/tex&amp;gt;-ом шаге алгоритма в нашем списке остались только кучи размера &amp;lt;tex&amp;gt; 2^i &amp;lt;/tex&amp;gt;. На нулевом шаге у нас &amp;lt;tex&amp;gt; n &amp;lt;/tex&amp;gt; куч из одного элемента, и на каждом следующем количество куч будет уменьшаться вдвое, а число вершин в куче будет увеличиваться вдвое. Слияние двух куч из &amp;lt;tex&amp;gt; n_i &amp;lt;/tex&amp;gt; элементов выполняется за &amp;lt;tex&amp;gt; O(\log{n_i}) &amp;lt;/tex&amp;gt;. Поэтому построение будет выполняться за&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex dpi = 130&amp;gt; &lt;br /&gt;
\sum\limits_{i = 1}^{\left\lceil \log{n} \right\rceil} \genfrac{}{}{}{0}{n \cdot \log{n_i}}{2^i} = &lt;br /&gt;
n \cdot \sum\limits_{i = 1}^{\left\lceil \log{n} \right\rceil} \genfrac{}{}{}{0}{\log{2^i}}{2^i} = &lt;br /&gt;
n \cdot \sum\limits_{i = 1}^{\left\lceil \log{n} \right\rceil} \genfrac{}{}{}{0}{i}{2^i}&lt;br /&gt;
&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Покажем, что сумма {{---}} &amp;lt;tex&amp;gt; O(1) &amp;lt;/tex&amp;gt;, тогда и алгоритм будет выполняться за &amp;lt;tex&amp;gt; O(n) &amp;lt;/tex&amp;gt;. Найдём сумму [[Определение суммы числового ряда|ряда]], заменив его на эквивалентный [[Определение функционального ряда|функциональный]]:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex dpi = 130&amp;gt; &lt;br /&gt;
\sum\limits_{i = 1}^{\left\lceil \log{n} \right\rceil} \genfrac{}{}{}{0}{i}{2^i} &amp;lt; \sum\limits_{i = 1}^{\infty } \genfrac{}{}{}{0}{i}{2^i} \\&lt;br /&gt;
f(x) = \sum\limits_{i = 1}^{\infty } \Bigl. i \cdot x^i \Bigr|_{x = \frac{1}{2}}, &lt;br /&gt;
~\genfrac{}{}{}{0}{f(x)}{x} = \sum\limits_{i = 1}^{\infty } i \cdot x^{i - 1},&lt;br /&gt;
~\int\genfrac{}{}{}{0}{f(x)}{x} = \sum\limits_{i = 1}^{\infty } x^i =~\genfrac{}{}{}{0}{1}{1 - x} - 1, \\&lt;br /&gt;
~\genfrac{}{}{}{0}{f(x)}{x} = (\genfrac{}{}{}{0}{1}{1 - x} - 1)' = \genfrac{}{}{}{0}{1}{(1 - x)^2},&lt;br /&gt;
~f(x) = \genfrac{}{}{}{0}{x}{(1 - x)^2}&lt;br /&gt;
&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
После подстановки &amp;lt;tex&amp;gt; x = \genfrac{}{}{}{0}{1}{2} &amp;lt;/tex&amp;gt; получаем, что сумма равна &amp;lt;tex&amp;gt; 2 &amp;lt;/tex&amp;gt;. Следовательно, построение кучи таким образом произойдёт за &amp;lt;tex&amp;gt; O(n) &amp;lt;/tex&amp;gt;.&lt;br /&gt;
==Преимущества левосторонней кучи==&lt;br /&gt;
Нигде не делается уничтожающих присваиваний. Не создается новых узлов в &amp;lt;tex&amp;gt;merge&amp;lt;/tex&amp;gt;. Эта реализация слияния является функциональной — ее легко реализовать на функциональном языке программирования. Также данная реализация &amp;lt;tex&amp;gt;merge&amp;lt;/tex&amp;gt; является персистентной.&lt;br /&gt;
&lt;br /&gt;
==Ссылки==&lt;br /&gt;
1. [http://compscicenter.ru/program/lecture/6829 Лекция &amp;quot;Приоритетные очереди&amp;quot; А. С. Станкевича в Computer Science Center]&lt;br /&gt;
&lt;br /&gt;
2. [http://www.intuit.ru/studies/courses/100/100/lecture/1539?page=1 Левосторонние кучи. Интуит.]&lt;br /&gt;
&lt;br /&gt;
3. [[wikipedia:Leftist_tree|Wikipedia {{---}} Leftist tree]]&lt;br /&gt;
[[Категория: Дискретная математика и алгоритмы]]&lt;br /&gt;
[[Категория: Приоритетные очереди]]&lt;/div&gt;</summary>
		<author><name>217.66.159.60</name></author>	</entry>

	</feed>