Рандомизированное бинарное дерево поиска — различия между версиями
| м | м (rollbackEdits.php mass rollback) | ||
| (не показано 10 промежуточных версий 5 участников) | |||
| Строка 2: | Строка 2: | ||
| ==Основная идея и связанные определения== | ==Основная идея и связанные определения== | ||
| − | Как известно, можно подобрать такую последовательность операций с [[Дерево поиска, наивная реализация|бинарным деревом поиска в наивной реализации]], что его глубина будет пропорциональна количеству ключей, а следовательно  | + | Как известно, можно подобрать такую последовательность операций с [[Дерево поиска, наивная реализация|бинарным деревом поиска в наивной реализации]], что его глубина будет пропорциональна количеству ключей, а следовательно операции будут выполняться за <tex>O(n)</tex>. Поэтому, если поддерживать инвариант "случайности" в дереве, то можно добиться того, что математическое ожидание глубины дерева будет небольшим. | 
| Дадим рекурсивное определение '''рандомизированного бинарного дерева поиска (RBST)'''. | Дадим рекурсивное определение '''рандомизированного бинарного дерева поиска (RBST)'''. | ||
| {{Определение | {{Определение | ||
| |definition= | |definition= | ||
| Пусть <tex>T</tex> {{---}} бинарное дерево поиска. Тогда | Пусть <tex>T</tex> {{---}} бинарное дерево поиска. Тогда | ||
| − | # Если <tex>T</tex> пусто, то оно является рандомизированным бинарным деревом поиска. | + | # Если <tex>T</tex> пусто, то оно является '''рандомизированным бинарным деревом поиска'''. | 
| − | # Если <tex>T</tex> непусто (содержит <tex>n</tex> вершин, <tex>n > 0</tex>), то <tex>T</tex> {{---}} рандомизированное бинарное дерево поиска тогда и только тогда, когда его левое и правое поддеревья (<tex>L</tex> и <tex>R</tex>) оба являются RBST, а также выполняется соотношение <tex>P[size(L) = i] = \ | + | # Если <tex>T</tex> непусто (содержит <tex>n</tex> вершин, <tex>n > 0</tex>), то <tex>T</tex> {{---}} '''рандомизированное бинарное дерево поиска''' тогда и только тогда, когда его левое и правое поддеревья (<tex>L</tex> и <tex>R</tex>) оба являются '''RBST''', а также выполняется соотношение <tex>P[size(L) = i] = \dfrac{1}n, i = 1..n</tex>. | 
| }} | }} | ||
| − | Из определения следует, что каждый ключ в RBST размера <tex>n</tex> может оказаться корнем с вероятностью <tex  | + | Из определения следует, что каждый ключ в RBST размера <tex>n</tex> может оказаться корнем с вероятностью <tex>\dfrac{1}{n}</tex>. | 
| Идея RBST состоит в том, что хранимое дерево постоянно является рандомизированным бинарным деревом поиска. Далее подробно будет описана реализация операций над RBST, которая позволит добиться этой цели. Заметим лишь, что хранение RBST в памяти ничем не отличается от хранения обычного дерева поиска: хранится указатель на корень; в каждой вершине хранятся указатели на её сыновей. | Идея RBST состоит в том, что хранимое дерево постоянно является рандомизированным бинарным деревом поиска. Далее подробно будет описана реализация операций над RBST, которая позволит добиться этой цели. Заметим лишь, что хранение RBST в памяти ничем не отличается от хранения обычного дерева поиска: хранится указатель на корень; в каждой вершине хранятся указатели на её сыновей. | ||
| Строка 22: | Строка 22: | ||
| ===Вставка=== | ===Вставка=== | ||
| − | Рассмотрим рекурсивный алгоритм вставки ключа <tex>x</tex> в RBST, состоящее из <tex>n</tex> вершин. С вероятностью <tex  | + | Рассмотрим рекурсивный алгоритм вставки ключа <tex>x</tex> в RBST, состоящее из <tex>n</tex> вершин. С вероятностью <tex>\dfrac{1}{n+1}</tex> вставим ключ в корень дерева (разделим дерево по данному ключу и подвесим получившиеся деревья к новому корню), используя процедуру <tex>\mathrm{insertAtRoot}</tex>. С вероятностью <tex>1 - \dfrac{1}{n+1} = \dfrac{n}{n+1}</tex> вставим его в правое поддерево, если он больше корня, или в левое поддерево, если меньше.   | 
| + | Ниже приведён псевдокод процедуры вставки <tex>\mathrm{insert}</tex>, процедуры <tex>\mathrm{insertAtRoot}</tex>, а также процедуры <tex>\mathrm{split(k)}</tex>, разбивающей дерево на два поддерева, в одном из которых все ключи строго меньше <tex>k</tex>, а в другом больше, либо равны; приведена достаточно очевидная рекурсивная реализация (через <tex>Node</tex> обозначен тип вершины дерева, дерево представляется как указатель на корень). | ||
| − |   '''Node''' insert( | + |   '''Node''' insert(t : '''Node''', x : '''T'''): | 
| − |      '''int''' r = '''random'''(0 | + |      '''int''' r = '''random'''(0 <tex>\dots</tex> t.size) | 
| − |      '''if''' r ==  | + |      '''if''' r == t.size | 
| − | + |         t = insertAtRoot(t, x) | |
| − |      '''if''' x <  | + |      '''if''' x < t.key | 
| − | + |         t = insert(t.left, x) | |
|      '''else''' |      '''else''' | ||
| − | + |         t = insert(t.right, x) | |
| − |      '''return'''  | + |     t.size = 1 + t.size | 
| + |      '''return''' t | ||
| Заметим, что если дерево пусто, то <tex>\mathrm{insert}</tex> с вероятностью 1 делает <tex>x</tex> корнем. | Заметим, что если дерево пусто, то <tex>\mathrm{insert}</tex> с вероятностью 1 делает <tex>x</tex> корнем. | ||
| − |   '''Node''' insertAtRoot( | + |   '''Node''' insertAtRoot(t : '''Node''', x : '''T'''):        <font color="green">// вставляем в дерево t ключ x</font> | 
| − | + |      <l, r> = split(t, x) | |
| − | + |      t.key = x | |
| − | + |      t.left = l | |
| − | + |      t.right = r | |
| − | + |      '''return''' t | |
| − | |||
| − | |||
| − |      '''return'''  | ||
| − |   split( | + |   '''<Node''', '''Node>''' split(t : '''Node''', x : '''T'''):               <font color="green"> // разделяет дерево t по x, результат {{---}} пара деревьев r и l</font> | 
| − |      '''if'''  | + |      '''if''' t.size == 0 | 
| − | + |         '''return''' <''null'', ''null''> | |
| − | + |      '''else if''' x < t.key | |
| − |      '''else if''' x <  | + |         <l, r> = split(t.left, x) | 
| − | + |        t.left = r | |
| − | + |        t.size = 1 + t.left.size + t.right.size | |
| + |        r = t | ||
| + |        '''return''' <l, r> | ||
|      '''else''' |      '''else''' | ||
| − | + |         <l, r> = split(t.right, x) | |
| − | + |        t.right = l | |
| + |        t.size = 1 + t.left.size + t.right.size | ||
| + |        l = t | ||
| + |        '''return''' <l, r> | ||
| Далее рассмотрим как меняется свойство дерева быть рандомизированным при вставке в него ключей. | Далее рассмотрим как меняется свойство дерева быть рандомизированным при вставке в него ключей. | ||
| Строка 66: | Строка 70: | ||
| Пусть <tex>n > 0</tex>, и лемма верна при всех меньших размерах дерева.. Пусть также <tex>y = T.key, L = T.left, R = T.right</tex>. Если <tex>x > y</tex>, то <tex>y</tex> {{---}} корень <tex>T_{L}</tex>, <tex>L</tex> {{---}} левое поддерево <tex>T_{L}</tex>, а <tex>\mathrm{split}</tex> рекурсивно вызовется от <tex>R</tex>, разделив его на <tex>R'</tex> {{---}} правое поддерево <tex>T_{L}</tex> {{---}}, и <tex>T_{R}</tex>, которые по предположению индукции будут рандомизированными бинарными деревьями поиска. Но <tex>L</tex> также является RBST, т.к. является поддеревом <tex>T</tex>. | Пусть <tex>n > 0</tex>, и лемма верна при всех меньших размерах дерева.. Пусть также <tex>y = T.key, L = T.left, R = T.right</tex>. Если <tex>x > y</tex>, то <tex>y</tex> {{---}} корень <tex>T_{L}</tex>, <tex>L</tex> {{---}} левое поддерево <tex>T_{L}</tex>, а <tex>\mathrm{split}</tex> рекурсивно вызовется от <tex>R</tex>, разделив его на <tex>R'</tex> {{---}} правое поддерево <tex>T_{L}</tex> {{---}}, и <tex>T_{R}</tex>, которые по предположению индукции будут рандомизированными бинарными деревьями поиска. Но <tex>L</tex> также является RBST, т.к. является поддеревом <tex>T</tex>. | ||
| − | Итак для того, чтобы доказать, что <tex>T_{L}</tex> {{---}} рандомизированное бинарное дерево поиска, осталось показать, что любая его вершина <tex>z</tex> с вероятностью <tex  | + | Итак для того, чтобы доказать, что <tex>T_{L}</tex> {{---}} рандомизированное бинарное дерево поиска, осталось показать, что любая его вершина <tex>z</tex> с вероятностью <tex>\dfrac{1}{m}</tex> окажется в корне, где <tex>m</tex> {{---}} размер <tex>T_{L}</tex>. Действительно: | 
| (пусть событие <tex>A</tex> {{---}} <tex>z</tex> является коренем <tex>T_{L}</tex>) | (пусть событие <tex>A</tex> {{---}} <tex>z</tex> является коренем <tex>T_{L}</tex>) | ||
| − | <tex dpi = "150">P[A \mid y < x] = \ | + | <tex dpi = "150">P[A \mid y < x] = \dfrac{P[A \; \wedge \; y < x]}{P[y < x]} = \dfrac{1 / n}{m / n} = \dfrac{1}{m}</tex> | 
| Случай, когда <tex>x < y</tex> симметричен рассмотренному. | Случай, когда <tex>x < y</tex> симметричен рассмотренному. | ||
| Строка 76: | Строка 80: | ||
| {{Теорема   | {{Теорема   | ||
| − | |statement= Если <tex>T</tex> {{---}} рандомизированное бинарное дерево поиска, содержащее множество ключей <tex>K</tex>, <tex>x \notin K</tex>, тогда процедура <tex>\mathrm{insert(x, T)}</tex> вернёт рандомизированное бинарное дерево поиска <tex>T</tex>, содержащее множество ключей <tex>K \ | + | |statement= Если <tex>T</tex> {{---}} рандомизированное бинарное дерево поиска, содержащее множество ключей <tex>K</tex>, <tex>x \notin K</tex>, тогда процедура <tex>\mathrm{insert(x, T)}</tex> вернёт рандомизированное бинарное дерево поиска <tex>T</tex>, содержащее множество ключей <tex>K \cup x</tex>. | 
| |proof= | |proof= | ||
| Применим индукцию по <tex>n</tex> {{---}} размеру дерева. Если <tex>n = 0</tex>, то теорема верна: после операции <tex>\mathrm{insert(x, T)}</tex> получим дерево с корнем <tex>x</tex> и двумя пустыми поддеревьями. | Применим индукцию по <tex>n</tex> {{---}} размеру дерева. Если <tex>n = 0</tex>, то теорема верна: после операции <tex>\mathrm{insert(x, T)}</tex> получим дерево с корнем <tex>x</tex> и двумя пустыми поддеревьями. | ||
| Строка 82: | Строка 86: | ||
| Пусть <tex>n > 0</tex>, и теорема верна при всех меньших размерах дерева. Возможны два случая: <tex>x</tex> вставляется в корень или рекурсивно в одно из поддеревьев. | Пусть <tex>n > 0</tex>, и теорема верна при всех меньших размерах дерева. Возможны два случая: <tex>x</tex> вставляется в корень или рекурсивно в одно из поддеревьев. | ||
| − | В первом случае правое и левое поддеревья <tex>x</tex> по лемме являются рандомизированными BST, а также вероятность того, что <tex>x</tex> окажется в корне, равна <tex  | + | В первом случае правое и левое поддеревья <tex>x</tex> по лемме являются рандомизированными BST, а также вероятность того, что <tex>x</tex> окажется в корне, равна <tex>\dfrac{1}{n + 1}</tex>. Т.е. новое дерево {{---}} рандомизированное BST. | 
| − | Во втором случае корень у дерева останется прежнем. Заметим, что для каждого <tex>y \in K</tex> вероятность быть корнем была <tex  | + | Во втором случае корень у дерева останется прежнем. Заметим, что для каждого <tex>y \in K</tex> вероятность быть корнем была <tex>\dfrac{1}{n}</tex>, а корнем он останется с вероятностью <tex>\dfrac{n}{n + 1}</tex>, тогда для каждого <tex>y \in K</tex> вероятность быть корнем равна <tex>\dfrac{1}{n} \cdot \dfrac{n}{n + 1} = \dfrac{1}{n + 1}</tex>. По предположению же индукции поддерево, в которое вставляется <tex>x</tex> становится рандомизированным бинарным деревом поиска; а т.к. другое поддерево корня было рандомизированным, то новое дерево {{---}} рандомизированное BST. | 
| }} | }} | ||
| Строка 93: | Строка 97: | ||
| Алгоритм удаления использует операцию <tex>\mathrm{merge}</tex> {{---}} слияние двух деревьев, удовлетворяющих условию: все ключи в одном из деревьев меньше ключей во втором. Для того, чтобы удалить некоторый ключ <tex>x</tex> из RBST сначала найдём вершину с этим ключом в дереве, используя стандартный алгоритм поиска. Если вершина не найдена, то выходим из алгоритма; в противном случае сливаем правое и левое поддеревья <tex>x</tex> (заметим, что ключи в левом поддереве меньше ключей в правом), удаляем <tex>x</tex>, а корень образовавшегося дерева делаем новым сыном родителя <tex>x</tex>. Псевдокод процедур удаления и слияния приведён ниже. | Алгоритм удаления использует операцию <tex>\mathrm{merge}</tex> {{---}} слияние двух деревьев, удовлетворяющих условию: все ключи в одном из деревьев меньше ключей во втором. Для того, чтобы удалить некоторый ключ <tex>x</tex> из RBST сначала найдём вершину с этим ключом в дереве, используя стандартный алгоритм поиска. Если вершина не найдена, то выходим из алгоритма; в противном случае сливаем правое и левое поддеревья <tex>x</tex> (заметим, что ключи в левом поддереве меньше ключей в правом), удаляем <tex>x</tex>, а корень образовавшегося дерева делаем новым сыном родителя <tex>x</tex>. Псевдокод процедур удаления и слияния приведён ниже. | ||
| − |   '''Node''' remove( | + |   '''Node''' remove(t : '''Node''', x : '''T'''):       <font color="green">// удаляет ключ x из дерева T</font> | 
| − |      '''if'''  | + |      '''if''' t.size == 0 | 
| − | + |         t = ''null'' | |
| − |         '''return'''  | + |         '''return''' t                      <font color="green">// вернуть пустое поддерево</font> | 
| − |      '''if''' x <  | + |      '''if''' x < t.key | 
| − | + |         t.left = remove(t.left, x) | |
| − |      '''else if''' x >  | + |      '''else if''' x > t.key | 
| − | + |         t.right = remove(t.right, x) | |
|      '''else''' |      '''else''' | ||
| − | + |         q = merge(t.left, t.right) | |
| − | + |         t = q | |
| − | + |      '''return''' t | |
| − |      '''return'''  | ||
| − |   '''Node''' merge( | + |   '''Node''' merge(l : '''Node''', r : '''Node'''):            <font color="green">// сливает деревья l и r, результат {{---}} дерево t</font> | 
| − |      '''int''' m =  | + |      '''int''' m = l.size | 
| − |      '''int''' n =  | + |      '''int''' n = r.size | 
|      '''int''' total = m + n |      '''int''' total = m + n | ||
|      '''if''' total == 0 |      '''if''' total == 0 | ||
| − | + |         t = ''null'' | |
| − |         '''return'''  | + |         '''return''' t                             <font color="green">// вернуть пустое поддерево</font> | 
| − |      '''int''' r = '''random'''(1 | + |      '''int''' r = '''random'''(1 <tex>\dots</tex> total) | 
|      '''if''' r < m |      '''if''' r < m | ||
| − | + |         l.right = merge(l.right, r)          <font color="green">// с вероятностью m / (m + n)</font> | |
| − |         '''return'''  | + |        l.size = 1 + l.left.size + l.right.size | 
| − | + |         '''return''' l | |
| − | + |      r.left = merge(l, r.left)            <font color="green">// с вероятностью n / (m + n)</font> | |
| − | + |     r.size = 1 + r.left.size + r.right.size | |
| + |     '''return''' r | ||
| Докажем, что данный алгоритм оставляет рандомизированное дерево рандомизированным. | Докажем, что данный алгоритм оставляет рандомизированное дерево рандомизированным. | ||
| {{Лемма | {{Лемма | ||
| − | |statement= Пусть <tex>L</tex> и <tex>R</tex> {{---}} рандомизированные бинарные деревья поиска, содержащие соответственно множества ключей <tex>K_{L}</tex> и <tex>K_{R}</tex>, причём <tex>K_{L} < K_{R}</tex> (то есть каждый элемент <tex>K_{L}</tex> меньше каждого элемента <tex>K_{R}</tex>). Тогда операция <tex>\mathrm{merge(L, R)}</tex> вернёт рандомизированное бинарное дерево поиска, содержащее множество ключей <tex>K</tex> = <tex>K_{L} \ | + | |statement= Пусть <tex>L</tex> и <tex>R</tex> {{---}} рандомизированные бинарные деревья поиска, содержащие соответственно множества ключей <tex>K_{L}</tex> и <tex>K_{R}</tex>, причём <tex>K_{L} < K_{R}</tex> (то есть каждый элемент <tex>K_{L}</tex> меньше каждого элемента <tex>K_{R}</tex>). Тогда операция <tex>\mathrm{merge(L, R)}</tex> вернёт рандомизированное бинарное дерево поиска, содержащее множество ключей <tex>K</tex> = <tex>K_{L} \cup K_{R}</tex>. | 
| |proof= | |proof= | ||
| Пусть <tex>m</tex> и <tex>n</tex> {{---}} размеры <tex>L</tex> и <tex>R</tex> соответственно. Применим индукцию по <tex>m</tex> и <tex>n</tex>. Если <tex>m = 0</tex> или <tex>n = 0</tex>, то лемма верна. | Пусть <tex>m</tex> и <tex>n</tex> {{---}} размеры <tex>L</tex> и <tex>R</tex> соответственно. Применим индукцию по <tex>m</tex> и <tex>n</tex>. Если <tex>m = 0</tex> или <tex>n = 0</tex>, то лемма верна. | ||
| − | Пусть <tex>m > 0</tex> и <tex>n > 0</tex>, пусть также <tex>L.key = a</tex> или <tex>L.key = b</tex>. Без потери общности делаем корнем <tex>a</tex>. После рекурсивного слияния правого поддерева <tex>L</tex> и <tex>R</tex> получим рандомизированное бинарное дерево поиска (которое является правым поддеревом нового дерева). Левое же поддерево нового дерева тоже рандомизированное. Также верно, что для любого <tex>x \in K_{L}</tex> вероятность быть корнем равна <tex  | + | Пусть <tex>m > 0</tex> и <tex>n > 0</tex>, пусть также <tex>L.key = a</tex> или <tex>L.key = b</tex>. Без потери общности делаем корнем <tex>a</tex>. После рекурсивного слияния правого поддерева <tex>L</tex> и <tex>R</tex> получим рандомизированное бинарное дерево поиска (которое является правым поддеревом нового дерева). Левое же поддерево нового дерева тоже рандомизированное. Также верно, что для любого <tex>x \in K_{L}</tex> вероятность быть корнем равна <tex>\dfrac{1}{m + n}</tex>: действительно, вероятность оказаться в корне в <tex>L</tex> до слияния равна <tex>\dfrac{1}{m}</tex>, вероятность того, что элемент останется корнем после слияния равна <tex>\dfrac{m}{m + n}</tex>; осталось применить правило умножения. | 
| }} | }} | ||
| Строка 139: | Строка 143: | ||
| Пусть <tex>x \in T</tex> (дерево не пусто), <tex>n</tex> {{---}} размер <tex>T</tex>. Докажем теорему по индукции по <tex>n</tex>. Для <tex>n = 1</tex> теорема очевидным образом верна. Пусть <tex>n > 1</tex>, и предположим, что теорема верна для всех деревьев размера меньше <tex>n</tex>. | Пусть <tex>x \in T</tex> (дерево не пусто), <tex>n</tex> {{---}} размер <tex>T</tex>. Докажем теорему по индукции по <tex>n</tex>. Для <tex>n = 1</tex> теорема очевидным образом верна. Пусть <tex>n > 1</tex>, и предположим, что теорема верна для всех деревьев размера меньше <tex>n</tex>. | ||
| − | Возможно два случая: если <tex>x</tex> {{---}} корень <tex>T</tex>, то по лемме, после удаления получим рандомизированное бинарное дерево поиска; если же <tex>x</tex> {{---}} не корень <tex>T</tex>, то <tex>x</tex> рекурсивно удаляется из поддерева исходного дерева, и по предположению индукции после удаления получаем рандомизированное BST. Осталось лишь показать, что для любого <tex>y \in T, y \neq x</tex> вероятность оказаться корнем после удаления равна <tex  | + | Возможно два случая: если <tex>x</tex> {{---}} корень <tex>T</tex>, то по лемме, после удаления получим рандомизированное бинарное дерево поиска; если же <tex>x</tex> {{---}} не корень <tex>T</tex>, то <tex>x</tex> рекурсивно удаляется из поддерева исходного дерева, и по предположению индукции после удаления получаем рандомизированное BST. Осталось лишь показать, что для любого <tex>y \in T, y \neq x</tex> вероятность оказаться корнем после удаления равна <tex>\dfrac{1}{n - 1}</tex>. | 
| Введём обозначения: | Введём обозначения: | ||
| Строка 154: | Строка 158: | ||
| <tex>P[A] = P[A \mid B] \cdot P[B] + P[A\mid\neg B] \cdot P[\neg B] = P[C] \cdot 1/n + P[D\mid\neg B] \cdot (n - 1)/n = </tex> | <tex>P[A] = P[A \mid B] \cdot P[B] + P[A\mid\neg B] \cdot P[\neg B] = P[C] \cdot 1/n + P[D\mid\neg B] \cdot (n - 1)/n = </tex> | ||
| − | <tex  | + | <tex>\dfrac{1}{n - 1} \cdot \dfrac{1}{n} + \dfrac{1}{n - 1} \cdot \dfrac{n - 1}{n} = \dfrac{1}{n - 1}</tex>. | 
| }} | }} | ||
| ==Анализ времени работы== | ==Анализ времени работы== | ||
| − | Очевидно, что время работы приведённых алгоритмов пропорционально глубине дерева. Но т.к. математическое ожидание глубины рандомизированного бинарного дерева поиска есть <tex>O (\log n)</tex>, где <tex>n</tex> {{---}} число вершин в дереве, то математическое ожидание времени работы поиска, вставки и удаления {{---}} также <tex>O (\log n)</tex>. | + | Очевидно, что время работы приведённых алгоритмов пропорционально глубине дерева. Но т.к. математическое ожидание глубины рандомизированного бинарного дерева поиска есть <tex>O (\log n)</tex>, где <tex>n</tex> {{---}} число вершин в дереве<ref>Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest, Clifford Stei, "Introduction to Algorithms", Second Edition {{---}} Chapter 12.4</ref>, то математическое ожидание времени работы поиска, вставки и удаления {{---}} также <tex>O (\log n)</tex>. | 
| + | |||
| + | == См. также == | ||
| + | * [[2-3 дерево]] | ||
| + | * [[B-дерево]] | ||
| + | * [[Splay-дерево]] | ||
| + | * [[АВЛ-дерево]] | ||
| + | * [[Красно-черное дерево]] | ||
| − | == | + | ==Примечания== | 
| − | + | <references /> | |
| − | |||
| − | |||
| − | |||
| == Источники информации == | == Источники информации == | ||
| Строка 173: | Строка 181: | ||
| * Seidel, Raimund; Aragon, Cecilia R. [http://people.ischool.berkeley.edu/~aragon/pubs/rst96.pdf «Randomized Search Trees»], 1996 г. | * Seidel, Raimund; Aragon, Cecilia R. [http://people.ischool.berkeley.edu/~aragon/pubs/rst96.pdf «Randomized Search Trees»], 1996 г. | ||
| * [http://www.cs.uiuc.edu/class/sp09/cs473/notes/08-treaps.pdf Randomized binary search trees]. Lecture notes from a course by Jeff Erickson at UIUC. | * [http://www.cs.uiuc.edu/class/sp09/cs473/notes/08-treaps.pdf Randomized binary search trees]. Lecture notes from a course by Jeff Erickson at UIUC. | ||
| + | * [https://www.cs.princeton.edu/~rs/AlgsDS07/08BinarySearchTrees.pdf Binary Search Trees]. Robert Sedgewick and Kevin Wayne - Algorithms and Data Structures course. | ||
| [[Категория: Дискретная математика и алгоритмы]] | [[Категория: Дискретная математика и алгоритмы]] | ||
| [[Категория: Деревья поиска]] | [[Категория: Деревья поиска]] | ||
| + | [[Категория: Структуры данных]] | ||
Текущая версия на 19:17, 4 сентября 2022
Рандомизированное бинарное дерево поиска (англ. Randomized binary search tree, RBST) — структура данных, реализующая бинарное дерево поиска.
Содержание
Основная идея и связанные определения
Как известно, можно подобрать такую последовательность операций с бинарным деревом поиска в наивной реализации, что его глубина будет пропорциональна количеству ключей, а следовательно операции будут выполняться за . Поэтому, если поддерживать инвариант "случайности" в дереве, то можно добиться того, что математическое ожидание глубины дерева будет небольшим. Дадим рекурсивное определение рандомизированного бинарного дерева поиска (RBST).
| Определение: | 
| Пусть  — бинарное дерево поиска. Тогда 
 | 
Из определения следует, что каждый ключ в RBST размера может оказаться корнем с вероятностью .
Идея RBST состоит в том, что хранимое дерево постоянно является рандомизированным бинарным деревом поиска. Далее подробно будет описана реализация операций над RBST, которая позволит добиться этой цели. Заметим лишь, что хранение RBST в памяти ничем не отличается от хранения обычного дерева поиска: хранится указатель на корень; в каждой вершине хранятся указатели на её сыновей.
Похожие идеи используются в декартовом дереве, поэтому во многих русскоязычных ресурсах термин рандомизированное бинарное дерево поиска используется как синонимическое название декартового дерева и декартового дерева по неявному ключу.
Операции
Операции обхода дерева, поиска ключа, поиска максимума/минимума, поиск следующего/предыдущего элемента выполняются как в обычном дереве поиска, т.к. не меняют структуру дерева.
Вставка
Рассмотрим рекурсивный алгоритм вставки ключа в RBST, состоящее из вершин. С вероятностью вставим ключ в корень дерева (разделим дерево по данному ключу и подвесим получившиеся деревья к новому корню), используя процедуру . С вероятностью вставим его в правое поддерево, если он больше корня, или в левое поддерево, если меньше. Ниже приведён псевдокод процедуры вставки , процедуры , а также процедуры , разбивающей дерево на два поддерева, в одном из которых все ключи строго меньше , а в другом больше, либо равны; приведена достаточно очевидная рекурсивная реализация (через обозначен тип вершины дерева, дерево представляется как указатель на корень).
Node insert(t : Node, x : T):
   int r = random(0  t.size)
   if r == t.size
      t = insertAtRoot(t, x)
   if x < t.key
      t = insert(t.left, x)
   else
      t = insert(t.right, x)
   t.size = 1 + t.size
   return t
Заметим, что если дерево пусто, то с вероятностью 1 делает корнем.
Node insertAtRoot(t : Node, x : T): // вставляем в дерево t ключ x <l, r> = split(t, x) t.key = x t.left = l t.right = r return t
<Node, Node> split(t : Node, x : T):                // разделяет дерево t по x, результат — пара деревьев r и l
   if t.size == 0
      return <null, null>
   else if x < t.key
      <l, r> = split(t.left, x)
      t.left = r
      t.size = 1 + t.left.size + t.right.size
      r = t
      return <l, r>
   else
      <l, r> = split(t.right, x)
      t.right = l
      t.size = 1 + t.left.size + t.right.size
      l = t
      return <l, r>
Далее рассмотрим как меняется свойство дерева быть рандомизированным при вставке в него ключей.
| Лемма: | 
| Пусть после операции  от дерева  по ключу  были получены деревья  и . Тогда если  было рандомизированным бинарным деревом поиска, содержащим множество ключей , то деревья  и  — рандомизированные бинарные деревья поиска, содержащие соответственно множества ключей  и . | 
| Доказательство: | 
| Применим индукцию по — размеру дерева. Если , то лемма верна (получим два пустых дерева). Пусть , и лемма верна при всех меньших размерах дерева.. Пусть также . Если , то — корень , — левое поддерево , а рекурсивно вызовется от , разделив его на — правое поддерево —, и , которые по предположению индукции будут рандомизированными бинарными деревьями поиска. Но также является RBST, т.к. является поддеревом . Итак для того, чтобы доказать, что — рандомизированное бинарное дерево поиска, осталось показать, что любая его вершина с вероятностью окажется в корне, где — размер . Действительно: (пусть событие — является коренем ) Случай, когда симметричен рассмотренному. | 
| Теорема: | 
| Если  — рандомизированное бинарное дерево поиска, содержащее множество ключей , , тогда процедура  вернёт рандомизированное бинарное дерево поиска , содержащее множество ключей . | 
| Доказательство: | 
| Применим индукцию по — размеру дерева. Если , то теорема верна: после операции получим дерево с корнем и двумя пустыми поддеревьями. Пусть , и теорема верна при всех меньших размерах дерева. Возможны два случая: вставляется в корень или рекурсивно в одно из поддеревьев. В первом случае правое и левое поддеревья по лемме являются рандомизированными BST, а также вероятность того, что окажется в корне, равна . Т.е. новое дерево — рандомизированное BST.Во втором случае корень у дерева останется прежнем. Заметим, что для каждого вероятность быть корнем была , а корнем он останется с вероятностью , тогда для каждого вероятность быть корнем равна . По предположению же индукции поддерево, в которое вставляется становится рандомизированным бинарным деревом поиска; а т.к. другое поддерево корня было рандомизированным, то новое дерево — рандомизированное BST. | 
Пусть — множество ключей, — какая-то фиксированная перестановка элементов . Из приведённой выше теоремы следует, что если в изначально пустое дерево добавлять ключи P по порядку, то получим дерево , являющееся RBST.
Удаление
Алгоритм удаления использует операцию — слияние двух деревьев, удовлетворяющих условию: все ключи в одном из деревьев меньше ключей во втором. Для того, чтобы удалить некоторый ключ из RBST сначала найдём вершину с этим ключом в дереве, используя стандартный алгоритм поиска. Если вершина не найдена, то выходим из алгоритма; в противном случае сливаем правое и левое поддеревья (заметим, что ключи в левом поддереве меньше ключей в правом), удаляем , а корень образовавшегося дерева делаем новым сыном родителя . Псевдокод процедур удаления и слияния приведён ниже.
Node remove(t : Node, x : T):       // удаляет ключ x из дерева T
   if t.size == 0
      t = null
      return t                      // вернуть пустое поддерево
   if x < t.key
      t.left = remove(t.left, x)
   else if x > t.key
      t.right = remove(t.right, x)
   else
      q = merge(t.left, t.right)
      t = q
   return t
Node merge(l : Node, r : Node):            // сливает деревья l и r, результат — дерево t
   int m = l.size
   int n = r.size
   int total = m + n
   if total == 0
      t = null
      return t                             // вернуть пустое поддерево
   int r = random(1  total)
   if r < m
      l.right = merge(l.right, r)          // с вероятностью m / (m + n)
      l.size = 1 + l.left.size + l.right.size
      return l
   r.left = merge(l, r.left)            // с вероятностью n / (m + n)
   r.size = 1 + r.left.size + r.right.size
   return r
Докажем, что данный алгоритм оставляет рандомизированное дерево рандомизированным.
| Лемма: | 
| Пусть  и  — рандомизированные бинарные деревья поиска, содержащие соответственно множества ключей  и , причём  (то есть каждый элемент  меньше каждого элемента ). Тогда операция  вернёт рандомизированное бинарное дерево поиска, содержащее множество ключей  = . | 
| Доказательство: | 
| Пусть и — размеры и соответственно. Применим индукцию по и . Если или , то лемма верна.Пусть и , пусть также или . Без потери общности делаем корнем . После рекурсивного слияния правого поддерева и получим рандомизированное бинарное дерево поиска (которое является правым поддеревом нового дерева). Левое же поддерево нового дерева тоже рандомизированное. Также верно, что для любого вероятность быть корнем равна : действительно, вероятность оказаться в корне в до слияния равна , вероятность того, что элемент останется корнем после слияния равна ; осталось применить правило умножения. | 
| Теорема: | 
| Если  — рандомизированное бинарное дерево поиска, содержащее множество ключей , тогда процедура  вернёт рандомизированное бинарное дерево поиска , содержащее множество ключей  | 
| Доказательство: | 
| Если удаляемый элемент отсутствует в дереве, то теорема верна. Пусть (дерево не пусто), — размер . Докажем теорему по индукции по . Для теорема очевидным образом верна. Пусть , и предположим, что теорема верна для всех деревьев размера меньше . Возможно два случая: если — корень , то по лемме, после удаления получим рандомизированное бинарное дерево поиска; если же — не корень , то рекурсивно удаляется из поддерева исходного дерева, и по предположению индукции после удаления получаем рандомизированное BST. Осталось лишь показать, что для любого вероятность оказаться корнем после удаления равна . Введём обозначения: событие — является коренем ; событие — был корнем (до операции ); событие — стал корнем после операции (но до этого им не являлся); событие — был корнем (до операции ); Тогда: . | 
Анализ времени работы
Очевидно, что время работы приведённых алгоритмов пропорционально глубине дерева. Но т.к. математическое ожидание глубины рандомизированного бинарного дерева поиска есть , где — число вершин в дереве[1], то математическое ожидание времени работы поиска, вставки и удаления — также .
См. также
Примечания
- ↑ Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest, Clifford Stei, "Introduction to Algorithms", Second Edition — Chapter 12.4
Источники информации
- Wikipedia — Random binary tree
- Wikipedia — Treap
- Martinez, Conrado; Roura, Salvador (1997), "Randomized binary search trees", Journal of the ACM 45
- Seidel, Raimund; Aragon, Cecilia R. «Randomized Search Trees», 1996 г.
- Randomized binary search trees. Lecture notes from a course by Jeff Erickson at UIUC.
- Binary Search Trees. Robert Sedgewick and Kevin Wayne - Algorithms and Data Structures course.
