Splay-дерево — различия между версиями

Материал из Викиконспекты
Перейти к: навигация, поиск
Строка 1: Строка 1:
'''Сплей-дерево''' (англ. ''Splay-tree'') — это [[Дерево поиска, наивная реализация | двоичное дерево поиска]]. Оно позволяет находить быстрее те данные, которые использовались недавно. Относится к разряду сливаемых деревьев. Сплей-дерево было придумано Робертом Тарьяном и Даниелем Слейтером в 1983 году.
+
'''Сплей-дерево''' (англ. ''Splay-tree'') — это [[Дерево поиска, наивная реализация | двоичное дерево поиска]]. Оно позволяет находить быстрее те данные, которые использовались недавно. Относится к разряду сливаемых деревьев. Сплей-дерево было придумано Робертом Тарьяном и Даниелем Слейтером в <tex>1983</tex> году.
  
 
==Эвристики==
 
==Эвристики==
Строка 6: Строка 6:
 
* '''Splay''' {{---}} также совершает повороты, но чередует различные виды поворотов, благодаря чему достигается логарифмическая амортизированная оценка. Она будет подробно описана ниже.
 
* '''Splay''' {{---}} также совершает повороты, но чередует различные виды поворотов, благодаря чему достигается логарифмическая амортизированная оценка. Она будет подробно описана ниже.
  
'''Пример''': При последовательном использовании операций "move to root" для вершин <tex>A</tex> и <tex>B</tex> требуется по 6 поворотов, в то время как при использовании операции "splay" для вершины <tex>B</tex> достаточно 3 поворотов.  
+
'''Пример''': При последовательном использовании операций "move to root" для вершин <tex>A</tex> и <tex>B</tex> требуется по <tex>6</tex> поворотов, в то время как при использовании операции "splay" для вершины <tex>B</tex> достаточно <tex>3</tex> поворотов.  
  
 
[[file:Move_to_root.png|750px]]
 
[[file:Move_to_root.png|750px]]
Строка 15: Строка 15:
  
 
===splay(tree, x)===
 
===splay(tree, x)===
"splay" делится на 3 случая:
+
"splay" делится на <tex>3</tex> случая:
 
====zig====
 
====zig====
 
Если <tex>p</tex> — корень дерева с сыном <tex>x</tex>, то совершаем один поворот вокруг ребра <tex>(x, p)</tex>, делая <tex>x</tex> корнем дерева. Данный случай является крайним и выполняется только один раз в конце, если изначальная глубина <tex>x</tex> была нечетной.
 
Если <tex>p</tex> — корень дерева с сыном <tex>x</tex>, то совершаем один поворот вокруг ребра <tex>(x, p)</tex>, делая <tex>x</tex> корнем дерева. Данный случай является крайним и выполняется только один раз в конце, если изначальная глубина <tex>x</tex> была нечетной.

Версия 03:45, 22 апреля 2018

Сплей-дерево (англ. Splay-tree) — это двоичное дерево поиска. Оно позволяет находить быстрее те данные, которые использовались недавно. Относится к разряду сливаемых деревьев. Сплей-дерево было придумано Робертом Тарьяном и Даниелем Слейтером в 1983 году.

Эвристики

Для того, чтобы доступ к недавно найденным данным был быстрее, надо, чтобы эти данные находились ближе к корню. Этого мы можем добиться, используя различные эвристики:

  • Move to Root — совершает повороты вокруг ребра (x,p), где x — найденная вершина, p — ее предок, пока x не окажется корнем дерева. Однако можно построить такую последовательность операций, что амортизированное время доступа к вершине будет Ω(n).
  • Splay — также совершает повороты, но чередует различные виды поворотов, благодаря чему достигается логарифмическая амортизированная оценка. Она будет подробно описана ниже.

Пример: При последовательном использовании операций "move to root" для вершин A и B требуется по 6 поворотов, в то время как при использовании операции "splay" для вершины B достаточно 3 поворотов.

Move to root.png

Splay.png

Операции со splay-деревом

splay(tree, x)

"splay" делится на 3 случая:

zig

Если p — корень дерева с сыном x, то совершаем один поворот вокруг ребра (x,p), делая x корнем дерева. Данный случай является крайним и выполняется только один раз в конце, если изначальная глубина x была нечетной.

zig — поворот

zig-zig

Если p — не корень дерева, а x и p — оба левые или оба правые дети, то делаем поворот ребра (p,g), где g отец p, а затем поворот ребра (x,p).

zig-zig — поворот

zig-zag

Если p — не корень дерева и x — левый ребенок, а p — правый, или наоборот, то делаем поворот вокруг ребра (x,p), а затем поворот нового ребра (x,g), где g — бывший родитель p.

zig-zag — поворот

Данная операция занимает O(d) времени, где d — длина пути от x до корня.

find(tree, x)

Эта операция выполняется как для обычного бинарного дерева, только после нее запускается операция splay.

merge(tree1, tree2)

У нас есть два дерева tree1 и tree2, причём подразумевается, что все элементы первого дерева меньше элементов второго. Запускаем splay от самого большого элемента в дереве tree1 (пусть это элемент i). После этого корень tree1 содержит элемент i, при этом у него нет правого ребёнка. Делаем tree2 правым поддеревом i и возвращаем полученное дерево.

split(tree, x)

Запускаем splay от элемента x и возвращаем два дерева, полученные отсечением правого или левого поддерева от корня, в зависимости от того, содержит корень элемент больше или не больше, чем x.

add(tree, x)

Запускаем split(tree, x), который нам возвращает деревья tree1 и tree2, их подвешиваем к x как левое и правое поддеревья соответственно.

remove(tree, x)

Запускаем splay от x элемента и возвращаем Merge от его детей.

Анализ операции splay

Амортизационный анализ сплей-дерева проводится с помощью метода потенциалов. Потенциалом рассматриваемого дерева назовём сумму рангов его вершин. Ранг вершины x — это величина, обозначаемая r(x) и равная log2C(x), где C(x) — количество вершин в поддереве с корнем в x.

Лемма:
Амортизированное время операции splay вершины x в дереве с корнем t не превосходит 3r(t)3r(x)+1
Доказательство:

Проанализируем каждый шаг операции splay. Пусть r и r — ранги вершин после шага и до него соответственно, p — предок вершины x, а g — предок p (если есть).

Разберём случаи в зависимости от типа шага:

zig. Поскольку выполнен один поворот, то амортизированное время выполнения шага T=1+r(x)+r(p)r(x)r(p) (поскольку только у вершин x и p меняется ранг). Ранг вершины p уменьшился, поэтому T1+r(x)r(x). Ранг вершины x увеличился, поэтому r(x)r(x)0. Следовательно, T1+3r(x)3r(x).

zig-zig. Выполнено два поворота, амортизированное время выполнения шага T=2+r(x)+r(p)+r(g)r(p)r(x)r(g). Поскольку после поворотов поддерево с корнем в x будет содержать все вершины, которые были в поддереве с корнем в g (и только их), поэтому r(x)=r(g). Используя это равенство, получаем: T=2+r(p)+r(g)r(x)r(p)2+r(p)+r(g)2r(x), поскольку r(x)r(p).

Далее, так как r(p)r(x), получаем, что T2+r(x)+r(g)2r(x).

Мы утверждаем, что эта сумма не превосходит 3(r(x)r(x)), то есть, что r(x)+r(g)2r(x)2. Преобразуем полученное выражение следующим образом: (r(x)r(x))+(r(g)r(x))=log2C(x)C(x)+log2C(g)C(x).

Из рисунка видно, что C(g)+C(x)C(x), значит, сумма выражений под логарифмами не превосходит единицы. Далее, рассмотрим сумму логарифмов log2a+log2b=log2ab. При a+b1 произведение ab по неравенству между средними не превышает 14. А поскольку логарифм — функция возрастающая, то log2ab2, что и является требуемым неравенством.

zig-zag. Выполнено два поворота, амортизированное время выполнения шага T=2+r(x)+r(p)+r(g)r(x)r(p)r(g). Поскольку r(x)=r(g), то T=2+r(p)+r(g)r(x)r(p). Далее, так как r(x)r(p), то T2+r(p)+r(g)2r(x).

Мы утверждаем, что эта сумма не превосходит 2(r(x)r(x)), то есть, что r(p)+r(g)2r(x)2. Но, поскольку r(p)+r(g)2r(x)=log2C(p)C(x)+log2C(g)C(x)2 - аналогично доказанному ранее, что и требовалось доказать.

Итого, получаем, что амортизированное время шага zig-zag не превосходит 2(r(x)r(x))3(r(x)r(x)).

Поскольку за время выполнения операции splay выполняется не более одного шага типа zig, то суммарное время не будет превосходить 3r(t)3r(x)+1, поскольку утроенные ранги промежуточных вершин сокращаются (входят в сумму как с плюсом, так и с минусом). Тогда суммарное время работы splay Tsplay3log2N3log2C(x)+1=O(log2N), где N — число элементов в дереве.

Статическая оптимальность сплей-дерева

Теорема:
Если к ключам 1n, сложенным в сплей-дерево выполняется m запросов, к i-му ключу осуществляется ki запросов, где ki>0, то суммарное время работы не превышает O(mH(p1,p2,,pn)), где pi=ki/m, H — шенноновская энтропия
Доказательство:

Известно, что H(p1,p2,,pn)=cni=1(pilog2pi) шенноновская энтропия.

Пусть s(x)=yw(y) — количество вершин в поддереве с корнем в x. А r(x)=log2s(x) — ранг вершины.

Обозначим за r корень splay-дерева. Из предыдущей теоремы известно, что asplay1+3(r(r)r(x))

Пусть w(xi)=pi= kim, тогда ki=pim.
m+3mr(r)3ni=1kir(xi)m+3mr(r)3ni+1kilog2w(xi)= m+3mr(r)3ni=1(pimlog2pi)=m(1+3r(r)3ni=1pilog2pi)=()

Так как вершина r — корень splay-дерева, то очевидно, что s=yw(y)=1, следовательно r(r)=log2s(r)=0. Поэтому ()=m(1+H(p1,,pn))=O(mH(p1,,pn)), ч.т.д.

Теорема о близких запросах в сплей-дереве

Теорема (о близких запросах в сплей-дереве):
Пусть в сплей-дерево сложены ключи 1,,n. Зафиксируем один из ключей f. Пусть выполняется m запросов к ключам. Тогда суммарное время на запросы есть O(nlog2n+m+mi=1log2(|qif|+1)), где qi — значение в вершине, к которой обращаются в i-ый запрос.
Доказательство:

Для доказательства теоремы воспользуемся методом потенциалов:

ai=ti+ΦiΦi1.

По условию выполняется m запросов, следовательно

T=mi=1ti=mi=1(ai+Φi1Φi)=mi=1ai+mi=1(Φi1Φi) ().

Введем следующие обозначения:

  • Весом узла с ключом q будем называть величину w(q)=1(|qf|+1)2.
  • Размером узла, содержащего ключ q, будем называть величину s(q)=yw(y), где y — узлы поддерева с корнем в q.
  • r(q)=log2s(q) — ранг узла.
  • Потенциал дерева после i-го запроса обозначим как Φi=nq=1r(q)=nq=1log2s(q).

Пусть W — вес дерева. Тогда W=nq=1w(q)=nq=11(|qf|+1)22+q=11(|qf|+1)2=O(1).

Последнее верно, так как при фиксированном f, начиная с некоторого места, а именно q=f, ряд сходится.

Из определения размера узла следует, что w(q)s(q)W.

Также заметим, что для любого q от 1 до n верно, что w(q)1n2, так как максимальное значение знаменателя в определении w(q) достигается при q=n и f=1 или наоборот.

Тогда, воспользовавшись полученными оценками, найдем изменение потенциала сплей-дерева после m запросов:

mi=1(Φi1Φi)=Φ0Φmnq=1log2Wnq=1log2w(q)=nq=1log2Ww(q) =O(nq=1log2n2)= O(2nq=1log2n)=O(nlog2n).

Первое неравенство верно, так как максимальное значение потенциала достигается при s(q)=W, а минимальное при s(q)=w(q), а значит изменение потенциала не превышает разности этих величин.

Обозначим за t корень сплей-дерева. Тогда, воспользовавшись вышеуказанной леммой (можно показать, что она верна для любого фиксированного определения веса узла) получаем, что

ai=3(r(t)r(qi))+1=O(log2s(t)s(qi))+1=O(log2Ww(qi))+1= O(log2(W(|qif|+1)2))+1=O(log2(|qif|+1))+1.

Докажем, что данное определение потенциала удовлетворяет условию теоремы о методе потенциалов.

Для любого i верно, что ai=O(log2(n)), так как |qif|+1n, и Φi=O(nlog2(n)), как было показано выше. Так как количество операций на запрос k=O(n), то ai=O(f(k,n)) и Φi=O(kf(k,n)), где f(k,n) — функция из теоремы о методе потенциалов, равная в данном случае log2n. Следовательно, потенциал удовлетворяет условию теоремы.

Тогда, подставляя найденные значения в формулу (), получаем, что

T=mi=1(O(log2(|qif|+1))+1)+O(nlog2n)= O(nlog2n+m+mi=1log2(|qif|+1)).

Данная теорема показывает, что сплей-деревья поддерживают достаточно эффективный доступ к ключам, которые находятся близко к какому-то фиксированному ключу.

Splay-деревья по неявному ключу

Splay-дерево по неявному ключу полностью аналогично декартову дереву по неявному ключу, неявным ключом также будет количество элементов дерева, меньших данного. Аналогично, будем хранить вспомогательную величину C(x) — количество вершин в поддереве. К операциям, которые уже были представлены в декартовом дереве, добавляется splay, но пересчет C(x) в ней тривиален, так как мы точно знаем, куда перемещаются изменяемые поддеревья.

См. также

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