Дерево Фенвика
Дерево Фенвика Бинарное индексированное дерево | |||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Тип | Биномиальное дерево | ||||||||||||||||||||
Изобретенный | 1989 | ||||||||||||||||||||
Изобретён | Boris Ryabko | ||||||||||||||||||||
|
этой статьи Начальный раздел может быть слишком коротким, чтобы адекватно суммировать ключевые моменты . ( июль 2023 г. ) |
Дерево Фенвика или двоично-индексированное дерево (BIT) — это структура данных, которая может эффективно обновлять значения и вычислять суммы префиксов в массиве значений.
Эту структуру предложил Борис Рябко в 1989 году. [1] с дальнейшей модификацией, опубликованной в 1992 году. [2] Впоследствии оно стало известно под названием «дерево Фенвика» в честь Питера Фенвика, который описал эту структуру в своей статье 1994 года. [3]
По сравнению с плоским массивом значений дерево Фенвика обеспечивает гораздо лучший баланс между двумя операциями: обновлением значения и вычислением суммы префикса. Плоский массив значения могут хранить либо значения, либо суммы префиксов. В первом случае вычисление сумм префиксов требует линейного времени; во втором случае обновление значений массива требует линейного времени (в обоих случаях другая операция может выполняться за постоянное время). Деревья Фенвика позволяют выполнять обе операции в время. Это достигается путем представления значений в виде дерева с узлы, где значение каждого узла в дереве представляет собой префиксную сумму массива от индекса родительского узла (включительно) до индекса узла (исключающего). Само дерево является неявным и может храниться как массив значения, при этом неявный корневой узел опущен из массива. Древовидная структура позволяет выполнять операции поиска значения, обновления значения, суммы префикса и суммы диапазона, используя только доступ к узлу.
Мотивация [ править ]
Учитывая массив значений, иногда желательно вычислить промежуточную сумму значений до каждого индекса в соответствии с некоторой ассоциативной двоичной операцией (сложение целых чисел является наиболее распространенным). Деревья Фенвика предоставляют метод запроса промежуточной суммы по любому индексу или сумме префикса, а также позволяют вносить изменения в базовый массив значений и позволяют всем дальнейшим запросам отражать эти изменения.
Деревья Фенвика специально разработаны для реализации алгоритма арифметического кодирования , который поддерживает подсчет каждого произведенного символа и должен преобразовать его в кумулятивную вероятность того, что символ меньше заданного символа. Развитие операций, которые он поддерживает, было в первую очередь мотивировано использованием в этом случае. [ нужна ссылка ]
Описание [ править ]
Дерево Фенвика — это неявное дерево , в котором узлы пронумерованы последовательно, а отношения «родитель-потомок» определяются арифметическими действиями по индексам узлов.
Важной функцией в этой арифметике индексов является младший бит набора , также называемый операцией поиска первого набора . Это наибольшая степень двойки, которая делит индекс . Это степень двойки (1, 2, 4, 8, ...), а не показатель степени (0, 1, 2, 3, ...). Его можно эффективно вычислить в с дополнением до двух как арифметике (где & обозначает побитовое И ).
Дерево Фенвика легче всего понять, используя массив, основанный на единице. с ценности. Используя синтаксис полуоткрытого интервала , пусть диапазон от (эксклюзивный) для (включительно). Соответствующий массив Фенвика сохраняет суммы диапазонов . То есть сумма значения, заканчивающиеся на и включая .
В некоторых описаниях используется фиктивный узел 0, но на самом деле к нему никогда не обращаются, и его не нужно явно сохранять. но значение никогда на самом деле не требуется. можно считать содержащим сумму пустого диапазона со значением 0.
«Дерево Фенвика» на самом деле представляет собой три неявных дерева в одном и том же массиве: дерево опросов, используемое для перевода индексов в суммы префиксов, дерево обновления , используемое для обновления элементов, и дерево поиска для перевода сумм префиксов в индексы (ранговые запросы). [4] Первые два обычно идут вверх, а третий обычно идет вниз.
Дерево допросов [ править ]
Дерево опросов определяется таким образом, что родительский элемент узла является . Например, родительским элементом 6 = 110 2 является 4 = 100 2 . Неявный узел 0 является корнем.
Каждый уровень дерева содержит узлы с индексами, соответствующими суммам различные степени двойки (с представляющая пустую сумму 0). Например, уровень содержит узлы и уровень содержит узлы
Узел имеет дети ( ), и все потомки. (Эти числа включают узлы, превышающие , которые опущены и к которым никогда не обращаются.)
На диаграмме ниже показана структура дерева запросов дерева Фенвика из 16 узлов, включая корень, поэтому оно соответствует массиву A из 15 элементов:
Чтобы найти сумму префикса , суммируем значения в , его родительский элемент, родительский элемент его родительского элемента и т. д. до корня (но не включая его). Чтобы вычислить сумму диапазона , вычтите суммы префиксов для и . Это можно оптимизировать, остановившись на их первом общем предке.
Дерево обновлений [ править ]
Дерево обновлений является зеркальным отражением дерева опросов. Родитель узла является (где | обозначает побитовое ИЛИ ). Например, родительским элементом 6 = 110 2 является 8 = 1000 2 .
Это концептуальное дерево бесконечно, но только часть с индексами до хранится или используется. Исключение фиктивных узлов с индексами больше это будет лес непересекающихся деревьев, по одному на каждый бит, заданный в двоичном представлении .
Здесь предками узла являются все узлы, суммы диапазонов которых включают его собственные. Например, содержит сумму , содержит сумму , и так далее.
Чтобы изменить одно из значений , добавьте изменение в , затем родительский элемент, затем его дедушка и бабушка и так далее, пока индекс не превысит .
Дерево поиска [ править ]
В отличие от двух других деревьев, дерево поиска представляет собой двоичное дерево , расположенное в порядке, который Кнут называет «боковой кучей». [5] Каждому узлу присваивается высота, равная количеству конечных нулей в двоичном представлении его индекса, при этом родительский и дочерний узлы являются ближайшими по числовому значению индексами соседней высоты. Узлы с нечетными индексами ( ) — листья. Узлы с четными индексами имеют два ближайших узла следующего наименьшего индекса в качестве дочерних. . Узел родителем в дереве поиска является .
Например, дочерними элементами 6 = 110 2 являются 5 = 101 2 и 7 = 111 2 , а родительским — 4 = 100 2 .
Хотя это дерево потенциально бесконечно, мы можем определить его корень как самый высокий из существующих узлов, индекс которого равен наибольшей степени 2, меньшей или равной .
Узел может иметь фиктивного родителя с индексом больше, чем но у меня все еще есть бабушка и дедушка. Если приведенный выше пример применим к дереву из 5 узлов, то узел 5 будет иметь фиктивного родителя 6, но уже существующего прародителя 4.
Дерево поиска можно рассматривать как комбинацию двух предыдущих деревьев. Левое поддерево узла содержит всех его потомков в дереве обновлений, а его правое поддерево содержит всех его потомков в дереве опросов. Родителем узла в дереве поиска является либо его родительский элемент запроса, либо родительский элемент обновления (в зависимости от того, является ли узел правым или левым дочерним элементом соответственно), а родительский элемент другого типа может быть найден несколькими шагами вверх в дереве поиска.
Однако обходы вверх по дереву поиска встречаются редко; его основное использование — выполнение ранговых запросов: учитывая сумму префикса, под каким индексом она появляется? Это делается путем вниз обхода дерева поиска . Во время обхода поддерживаются три переменные: индекс текущего узла, искомый ранг в поддереве, корнем которого является текущий узел, и «резервный индекс», который возвращается, если искомый ранг больше, чем можно найти в поддереве.
Первоначально текущий узел является корнем, искомый ранг — исходным запросом, а резервный индекс — специальным значением «переполнения», указывающим, что ранг отсутствует в дереве. (В зависимости от приложения, или можно использовать для этой цели.)
На каждом шаге, либо текущий узел является фиктивным узлом (индекс больше ), или мы должны решить, находится ли искомая позиция слева или справа от конца текущего узла. Если искомый ранг меньше значения массива Фенвика для текущего узла мы должны найти его левое поддерево. Если оно больше, ищите его правое поддерево. Если оно равно, выбранное направление зависит от того, как вы хотите обрабатывать поиск сумм, лежащих точно между двумя узлами.
Эти три возможности затем делятся в зависимости от того, является ли текущий узел листом или нет:
- Если текущий узел является листом и:
- цель находится в его (пустом) левом поддереве, верните текущий индекс.
- оно фиктивное или цель находится в правом поддереве, верните резервный индекс.
- Если текущий узел не является листом и:
- это фиктивно, искать тот же ранг в его левом поддереве с неизменным запасным индексом.
- цель находится в его левом поддереве, найдите тот же ранг в его левом поддереве с текущим индексом в качестве резервного индекса.
- цель находится в правом поддереве, найдите целевой ранг минус значение текущего узла в правом поддереве с неизмененным резервным индексом.
Псевдокод [ править ]
Простая реализация на псевдокоде двух основных операций над деревом Фенвика — запроса и обновления — выглядит следующим образом:
function query(tree, index) is sum := 0 while index > 0 do sum += tree[index] index -= lsb(index) return sum function update(tree, index, value) is while index < size(tree) do tree[index] += value index += lsb(index)
Функция вычисляет младший значащий 1-бит или последний установленный бит данного или, что то же самое, наибольшая степень двойки , которая также является делителем . Например, , как показано в его двоичном представлении: . Эту функцию можно просто реализовать в коде посредством побитовой операции И : lsb(n) = n & (-n)
, предполагая целочисленный тип данных со знаком . [3]
Строительство [ править ]
Один простой алгоритм построения дерева Фенвика состоит из инициализации дерева нулевыми значениями и индивидуального обновления каждого индекса. Это решение работает в время, но возможно строительство: [6]
function construct(values) is tree := values for every index, value in tree do parentIndex := index + lsb(index) if parentIndex < size(tree) then tree[parentIndex] += value return tree
См. также [ править ]
Ссылки [ править ]
- ^ Борис Рябко (1989). «Быстрый онлайн-код» (PDF) . Советская математика. Докл . 39 (3): 533–537. Архивировано (PDF) из оригинала 17 июля 2019 г. Проверено 17 июля 2019 г.
- ^ Борис Рябко (1992). «Быстрый адаптивный онлайн-код» (PDF) . Транзакции IEEE по теории информации . 28 (1): 1400–1404. Архивировано (PDF) из оригинала 14 июля 2019 г. Проверено 14 июля 2019 г.
- ↑ Перейти обратно: Перейти обратно: а б Питер М. Фенвик (1994). «Новая структура данных для сводных таблиц частот». Программное обеспечение: практика и опыт . 24 (3): 327–336. CiteSeerX 10.1.1.14.8917 . дои : 10.1002/спе.4380240306 . S2CID 7519761 .
- ^ Марчини, Стефано; Винья, Себастьяно (14 октября 2019 г.). «Компактные деревья Фенвика для динамического ранжирования и выбора». arXiv : 1904.12370 [ cs.DS ]. Подробное обсуждение деталей практической реализации.
- ^ Кнут, Дональд (2011). Комбинаторные алгоритмы, часть 1 . Искусство компьютерного программирования . Том. 4А. Река Аппер-Сэддл, Нью-Джерси: Addison-Wesley Professional. стр. 164–165.
- ^ Халим, Стивен; Халим, Феликс; Эффенди, Сухендри (3 декабря 2018 г.). Соревновательное программирование . Том. 1 (4-е изд.). Лулу Пресс, Инкорпорейтед. ISBN 9781716745522 .