Декартово дерево по неявному ключу — различия между версиями
(→Split) |
м (rollbackEdits.php mass rollback) |
||
(не показано 28 промежуточных версий 9 участников) | |||
Строка 1: | Строка 1: | ||
==Основная идея== | ==Основная идея== | ||
− | Возьмем структуру данных [[Саморасширяющийся массив| | + | Возьмем структуру данных [[Саморасширяющийся массив|динамический массив]]. В её стандартной реализации мы умеем добавлять элемент в конец вектора, узнавать значение элемента, стоящего на определенной позиции, изменять элемент по номеру и удалять последний элемент. Предположим, что нам необходима структура данных с вышеуказанными свойствами, а также с операциями: добавить элемент в любое место (с соответствующим изменением нумерации элементов) и удалить любой элемент (также с соответствующим изменением нумерации). Такую структуру можно реализовать на базе декартового дерева, результат часто называют '''декартово дерево по неявному ключу''' (англ. ''Treap with implicit key''). |
===Ключ X=== | ===Ключ X=== | ||
− | Как известно, [[декартово дерево]] {{---}} это структура данных, объединяющая в себе бинарное дерево поиска и бинарную кучу. При реализации же декартова дерева по неявному ключу модифицируем эту структуру. А именно, оставим в нем только приоритет <tex>Y</tex>, а вместо ключа <tex>X</tex> будем использовать следующую величину: '''количество элементов в нашей структуре, находящихся левее нашего элемента'''. Иначе говоря, будем считать ключом порядковый номер нашего элемента в дереве, уменьшенный на единицу. | + | Как известно, [[декартово дерево]] {{---}} это структура данных, объединяющая в себе [[Дерево_поиска,_наивная_реализация|бинарное дерево поиска]] и [[Двоичная_куча|бинарную кучу]]. При реализации же декартова дерева по неявному ключу модифицируем эту структуру. А именно, оставим в нем только приоритет <tex>Y</tex>, а вместо ключа <tex>X</tex> будем использовать следующую величину: '''количество элементов в нашей структуре, находящихся левее нашего элемента'''. Иначе говоря, будем считать ключом порядковый номер нашего элемента в дереве, уменьшенный на единицу. |
Заметим, что при этом сохранится структура [[Дерево_поиска,_наивная_реализация|двоичного дерева поиска]] по этому ключу (то есть модифицированное декартово дерево так и останется декартовым деревом). Однако, с этим подходом появляется проблема: операции добавления и удаления элемента могут поменять нумерацию, и при наивной реализации на изменение всех ключей потребуется <tex>O(n)</tex> времени, где <tex>n</tex> {{---}} количество элементов в дереве. | Заметим, что при этом сохранится структура [[Дерево_поиска,_наивная_реализация|двоичного дерева поиска]] по этому ключу (то есть модифицированное декартово дерево так и останется декартовым деревом). Однако, с этим подходом появляется проблема: операции добавления и удаления элемента могут поменять нумерацию, и при наивной реализации на изменение всех ключей потребуется <tex>O(n)</tex> времени, где <tex>n</tex> {{---}} количество элементов в дереве. | ||
===Вспомогательная величина С=== | ===Вспомогательная величина С=== | ||
− | Решается эта проблема довольно просто. Основная идея заключается в том, что такой ключ <tex>X</tex> сам по себе нигде не хранится. Вместо него будем хранить вспомогательную величину <tex>C</tex>: '''количество вершин в поддереве нашей вершины''' (в поддерево включается и сама вершина). Обратим внимание, что все операции с обычным декартовым деревом делались сверху. Также заметим, что если по пути до некой вершины просуммировать все такие величины в левых поддеревьях, в которые мы не пошли, увеличенные на единицу, то придя в саму вершину и добавив к этой величине количество элементов в её левом поддереве, мы получим как раз ее ключ <tex>X</tex>. | + | Решается эта проблема довольно просто. Основная идея заключается в том, что такой ключ <tex>X</tex> сам по себе нигде не хранится. Вместо него будем хранить вспомогательную величину <tex>C</tex>: '''количество вершин в поддереве нашей вершины''' (в поддерево включается и сама вершина). Обратим внимание, что все операции с обычным декартовым деревом делались сверху. Также заметим, что если по пути от корня до некой вершины просуммировать все такие величины в левых поддеревьях, в которые мы не пошли, увеличенные на единицу, то придя в саму вершину и добавив к этой величине количество элементов в её левом поддереве, мы получим как раз ее ключ <tex>X</tex>. |
[[Файл:DDpoNK.png|Пример описанного дерева с демонстрацией определения ключа <tex>X</tex>]] | [[Файл:DDpoNK.png|Пример описанного дерева с демонстрацией определения ключа <tex>X</tex>]] | ||
Строка 16: | Строка 16: | ||
===Split=== | ===Split=== | ||
Пусть процедура <tex>\mathrm{split}</tex> запущена в корне дерева с требованием отрезать от дерева <tex>k</tex> вершин. Также известно, что в левом поддереве вершины находится <tex>l</tex> вершин, а в правом <tex>r</tex>. Рассмотрим все возможные случаи: | Пусть процедура <tex>\mathrm{split}</tex> запущена в корне дерева с требованием отрезать от дерева <tex>k</tex> вершин. Также известно, что в левом поддереве вершины находится <tex>l</tex> вершин, а в правом <tex>r</tex>. Рассмотрим все возможные случаи: | ||
− | * <tex>l | + | * <tex>l \geqslant k</tex>. В этом случае нужно рекурсивно запустить процедуру <tex>\mathrm{split}</tex> от левого сына с тем же параметром <tex>k</tex>. При этом новым левым сыном корня станет правая часть ответа рекурсивной процедуры, а правой частью ответа станет корень. |
* <tex>l < k</tex> Случай симметричен предыдущему. Рекурсивно запустим процедуру <tex>\mathrm{split}</tex> от правого сына с параметром <tex>k - l - 1</tex>. При этом новым правым сыном корня станет левая часть ответа рекурсивной процедуры, а левой частью ответа станет корень. | * <tex>l < k</tex> Случай симметричен предыдущему. Рекурсивно запустим процедуру <tex>\mathrm{split}</tex> от правого сына с параметром <tex>k - l - 1</tex>. При этом новым правым сыном корня станет левая часть ответа рекурсивной процедуры, а левой частью ответа станет корень. | ||
Псевдокод: | Псевдокод: | ||
− | < | + | |
− | split(Treap t, int k, | + | ''' <tex>\langle</tex>Treap, Treap<tex>\rangle</tex>''' split('''Treap''' t, '''int''' k) |
− | + | '''if''' t == <tex> \varnothing </tex> | |
− | + | '''return''' <tex>\langle</tex><tex> \varnothing </tex>, <tex> \varnothing </tex><tex>\rangle</tex> | |
− | + | '''int''' l = t.left.size | |
− | + | '''if''' l <tex>\small{\geqslant}</tex> k | |
− | + | <tex>\langle</tex>t1, t2<tex>\rangle</tex> = split(t.left, k) | |
− | + | t.left = t2 | |
− | + | update(t) | |
− | + | '''return''' <tex>\langle</tex>t1, t<tex>\rangle</tex> | |
− | + | '''else''' | |
− | </ | + | <tex>\langle</tex>t1, t2<tex>\rangle</tex> = split(t.right, k - l - 1) |
+ | t.right = t1 | ||
+ | update(t) | ||
+ | '''return''' <tex>\langle</tex>t, t2<tex>\rangle</tex> | ||
===Merge=== | ===Merge=== | ||
Строка 40: | Строка 43: | ||
Псевдокод: | Псевдокод: | ||
− | + | '''void''' update('''Treap''' t) | |
− | update(Treap t) | + | t.size = 1 + t.left.size + t.right.size |
− | |||
− | |||
==Применение описанного дерева== | ==Применение описанного дерева== | ||
Таким образом, описана структура, от которой можно отрезать слева часть произвольной длины и слить две любые части в одну в нужном порядке. Теперь мы имеем возможность: | Таким образом, описана структура, от которой можно отрезать слева часть произвольной длины и слить две любые части в одну в нужном порядке. Теперь мы имеем возможность: | ||
− | * вставить элемент в любое место (отрежем нужное количество элементов слева, сольем левое дерево с деревом из одного добавленного элемента и результат {{---}} с правым деревом) | + | * вставить элемент в любое место (отрежем нужное количество элементов слева, сольем левое дерево с деревом из одного добавленного элемента и результат {{---}} с правым деревом), |
− | * переставить любой кусок массива куда угодно (сделаем нужные разрезы и слияния в правильном порядке) | + | * переставить любой кусок массива куда угодно (сделаем нужные разрезы и слияния в правильном порядке), |
− | * совершать групповые операции с элементами. Вспомним реализацию таких операций в дереве отрезков и поймем, что ничего не помешает нам сделать то же самое с описанным деревом. В групповые операции включается, естественно, и взятие функции от отрезка | + | * совершать групповые операции с элементами. Вспомним реализацию таких операций в дереве отрезков и поймем, что ничего не помешает нам сделать то же самое с описанным деревом. В групповые операции включается, естественно, и взятие функции от отрезка, |
− | * сделав на одном исходном массиве два дерева из элементов разной четности, можно решить задачу про смену мест четных и нечетных на отрезке | + | * сделав на одном исходном массиве два дерева из элементов разной четности, можно решить задачу про смену мест четных и нечетных на отрезке, |
− | * | + | * используя идеи декартова дерева по неявному ключу, можно реализовать такую структуру данных как [[Rope|Rope]]. |
+ | |||
+ | == См. также == | ||
+ | * [[Splay-дерево]] | ||
− | == | + | ==Источники информации== |
− | * [http://habrahabr.ru/post/102364/ Habrahabr - Декартово дерево по неявному ключу] | + | * [http://habrahabr.ru/post/102364/ Habrahabr {{---}} Декартово дерево по неявному ключу] |
− | * [http://e-maxx.ru/algo/treap#7 | + | * [http://e-maxx.ru/algo/treap#7 MAXimal :: algo :: Неявные декартовы деревья] |
[[Категория: Деревья поиска]] | [[Категория: Деревья поиска]] | ||
[[Категория: Дискретная математика и алгоритмы]] | [[Категория: Дискретная математика и алгоритмы]] | ||
+ | [[Категория: Структуры данных]] |
Текущая версия на 19:25, 4 сентября 2022
Содержание
Основная идея
Возьмем структуру данных динамический массив. В её стандартной реализации мы умеем добавлять элемент в конец вектора, узнавать значение элемента, стоящего на определенной позиции, изменять элемент по номеру и удалять последний элемент. Предположим, что нам необходима структура данных с вышеуказанными свойствами, а также с операциями: добавить элемент в любое место (с соответствующим изменением нумерации элементов) и удалить любой элемент (также с соответствующим изменением нумерации). Такую структуру можно реализовать на базе декартового дерева, результат часто называют декартово дерево по неявному ключу (англ. Treap with implicit key).
Ключ X
Как известно, декартово дерево — это структура данных, объединяющая в себе бинарное дерево поиска и бинарную кучу. При реализации же декартова дерева по неявному ключу модифицируем эту структуру. А именно, оставим в нем только приоритет , а вместо ключа будем использовать следующую величину: количество элементов в нашей структуре, находящихся левее нашего элемента. Иначе говоря, будем считать ключом порядковый номер нашего элемента в дереве, уменьшенный на единицу.
Заметим, что при этом сохранится структура двоичного дерева поиска по этому ключу (то есть модифицированное декартово дерево так и останется декартовым деревом). Однако, с этим подходом появляется проблема: операции добавления и удаления элемента могут поменять нумерацию, и при наивной реализации на изменение всех ключей потребуется времени, где — количество элементов в дереве.
Вспомогательная величина С
Решается эта проблема довольно просто. Основная идея заключается в том, что такой ключ
сам по себе нигде не хранится. Вместо него будем хранить вспомогательную величину : количество вершин в поддереве нашей вершины (в поддерево включается и сама вершина). Обратим внимание, что все операции с обычным декартовым деревом делались сверху. Также заметим, что если по пути от корня до некой вершины просуммировать все такие величины в левых поддеревьях, в которые мы не пошли, увеличенные на единицу, то придя в саму вершину и добавив к этой величине количество элементов в её левом поддереве, мы получим как раз ее ключ .Операции, поддерживающие структуру декартова дерева
Структура обычного декартова дерева поддерживается с помощью двух операций:
— разбиение одного декартова дерева на два таких, что в одном ключ меньше, чем заданное значение, а в другом — больше, и — слияние двух деревьев, в одном из которых все ключи меньше, чем во втором. С учетом отличий декартова дерева по неявному ключу от обычного, операции теперь будут описываться так: — разбиение дерева на два так, что в левом окажется ровно вершин, и — слияние двух любых деревьев, соответственно.Split
Пусть процедура
запущена в корне дерева с требованием отрезать от дерева вершин. Также известно, что в левом поддереве вершины находится вершин, а в правом . Рассмотрим все возможные случаи:- . В этом случае нужно рекурсивно запустить процедуру от левого сына с тем же параметром . При этом новым левым сыном корня станет правая часть ответа рекурсивной процедуры, а правой частью ответа станет корень.
- Случай симметричен предыдущему. Рекурсивно запустим процедуру от правого сына с параметром . При этом новым правым сыном корня станет левая часть ответа рекурсивной процедуры, а левой частью ответа станет корень.
Псевдокод:
Treap, Treap split(Treap t, int k) if t == return , int l = t.left.size if l k t1, t2 = split(t.left, k) t.left = t2 update(t) return t1, t else t1, t2 = split(t.right, k - l - 1) t.right = t1 update(t) return t, t2
Merge
Посмотрим любую из реализаций процедуры . Заметим, что в ней программа ни разу не обращается к ключу . Поэтому реализация процедуры для декартова дерева по неявному ключу вообще не будет отличаться от реализации той же процедуры в обычном декартовом дереве.
Поддержание корректности значений C
Единственное действие, обеспечивающее корректность этих значений заключается в том, что после любого действия с детьми вершины нужно записать в ее поле
сумму этих значений в ее новых детях, увеличенную на единицу.Псевдокод:
void update(Treap t) t.size = 1 + t.left.size + t.right.size
Применение описанного дерева
Таким образом, описана структура, от которой можно отрезать слева часть произвольной длины и слить две любые части в одну в нужном порядке. Теперь мы имеем возможность:
- вставить элемент в любое место (отрежем нужное количество элементов слева, сольем левое дерево с деревом из одного добавленного элемента и результат — с правым деревом),
- переставить любой кусок массива куда угодно (сделаем нужные разрезы и слияния в правильном порядке),
- совершать групповые операции с элементами. Вспомним реализацию таких операций в дереве отрезков и поймем, что ничего не помешает нам сделать то же самое с описанным деревом. В групповые операции включается, естественно, и взятие функции от отрезка,
- сделав на одном исходном массиве два дерева из элементов разной четности, можно решить задачу про смену мест четных и нечетных на отрезке,
- используя идеи декартова дерева по неявному ключу, можно реализовать такую структуру данных как Rope.