Дерево Фенвика — различия между версиями
Строка 1: | Строка 1: | ||
− | + | '''Дерево Фе́нвика''' (англ. ''Binary indexed tree'') — структура данных, требующая <tex> O(n) </tex> памяти и позволяющая эффективно (за <tex> O(\log n) </tex>): | |
− | + | * изменять значение любого элемента в массиве, | |
− | '''Дерево Фе́нвика''' (англ. ''Binary indexed tree'') — структура данных, требующая <tex> O(n) </tex> памяти и позволяющая эффективно (за <tex> O(\log n) </tex>) | + | * выполнять некоторую ассоциативную, коммутативную, обратимую операцию <tex> G </tex> на отрезке <tex> [i, j] </tex>. |
− | + | ||
− | |||
− | |||
[[Файл:Bit.jpg|thumb|300px|По горизонтали - содержимое массива <tex>T</tex> <br/> (<tex>T_i</tex> является суммой заштрихованных ячеек массива <tex>A</tex>),<br/> по вертикали - содержимое массива <tex>A</tex>]] | [[Файл:Bit.jpg|thumb|300px|По горизонтали - содержимое массива <tex>T</tex> <br/> (<tex>T_i</tex> является суммой заштрихованных ячеек массива <tex>A</tex>),<br/> по вертикали - содержимое массива <tex>A</tex>]] | ||
Впервые описано Питером Фенвиком в 1994 году. | Впервые описано Питером Фенвиком в 1994 году. | ||
− | Пусть дан массив <tex> A | + | Пусть дан массив <tex> A = [a_0, a_1, ... , a_{n - 1}]</tex>. |
− | Деревом Фенвика будем называть массив <tex> T </tex> из <tex> n </tex> элементов: <tex> T_i = \sum\limits_{k = F(i)}^{i} a_k, i = 0 .. n - 1 </tex> | + | Деревом Фенвика будем называть массив <tex> T </tex> из <tex> n </tex> элементов: <tex> T_i = \sum\limits_{k = F(i)}^{i} a_k</tex>, где <tex> i = 0 .. n - 1 </tex> и <tex> F(i) </tex> — некоторая функция. |
− | От выбора функции зависит время работы операций над деревом. Рассмотрим функцию, позволяющую делать | + | От выбора функции зависит время работы операций над деревом. Рассмотрим функцию, позволяющую делать операции вставки и изменения элемента за время <tex> O(\log n) </tex>. |
− | <tex> F(i) = i - 2^{h(i)} + 1, </tex> где <tex> h(i) </tex> | + | <tex> F(i) = i - 2^{h(i)} + 1, </tex> где <tex> h(i) </tex> — количество единиц в конце бинарной записи числа <tex> i </tex>. Эту функцию можно записать так: <tex> F(i) = i - t(i), </tex> где <tex> t(i) = 2^{h(i)} - 1, </tex> то есть число, состоящее из <tex> i </tex> единиц. Это значит, что функция заменяет все подряд идущие единицы в конце числа на нули. |
− | Эта функция задается простой формулой: <tex> F(i) = i \And (i + 1) </tex>, где <tex> \And </tex> — это операция | + | Эта функция задается простой формулой: <tex> F(i) = i \And (i + 1) </tex>, где <tex> \And </tex> — это операция логического <tex>AND</tex>. При <tex>AND</tex> числа и его значения, увеличенного на единицу, мы получаем это число без последних подряд идущих единиц. |
== Запрос изменения элемента == | == Запрос изменения элемента == | ||
Строка 26: | Строка 24: | ||
{{Лемма | {{Лемма | ||
− | |statement= Все такие <tex> i </tex> удовлетворяют равенству <tex>i_{next} = i_{prev} \mid (i_{prev} + 1) </tex>, где <tex> \mid </tex> — это операция | + | |statement= Все такие <tex> i </tex> удовлетворяют равенству <tex>i_{next} = i_{prev} \mid (i_{prev} + 1) </tex>, где <tex> \mid </tex> — это операция <tex> OR </tex>. |
|proof=Первый элемент последовательности само <tex> k </tex>. Для него выполняется равенство, так как <tex> F(i) \leqslant i </tex>. По формуле <tex>i_{next} = i_{prev} \mid (i_{prev} + 1) </tex> мы заменим первый ноль на единицу. Неравенство при этом сохранится, так как <tex>F(i)</tex> осталось прежним или уменьшилось, а <tex> i </tex> увеличилось. Можем заметить, что если количество единиц в конце не будет совпадать с <tex> k </tex>, то формула <tex>i_{next} = i_{prev} \mid (i_{prev} + 1) </tex> нарушит неравенство, потому что либо само <tex> i </tex> будет меньше, чем k, либо <tex> F(i) </tex> станет больше, чем <tex> k </tex>. Таким образом, перебраны будут только нужные элементы}} | |proof=Первый элемент последовательности само <tex> k </tex>. Для него выполняется равенство, так как <tex> F(i) \leqslant i </tex>. По формуле <tex>i_{next} = i_{prev} \mid (i_{prev} + 1) </tex> мы заменим первый ноль на единицу. Неравенство при этом сохранится, так как <tex>F(i)</tex> осталось прежним или уменьшилось, а <tex> i </tex> увеличилось. Можем заметить, что если количество единиц в конце не будет совпадать с <tex> k </tex>, то формула <tex>i_{next} = i_{prev} \mid (i_{prev} + 1) </tex> нарушит неравенство, потому что либо само <tex> i </tex> будет меньше, чем k, либо <tex> F(i) </tex> станет больше, чем <tex> k </tex>. Таким образом, перебраны будут только нужные элементы}} | ||
− | Все <tex>i</tex> мы можем получить следующим образом : <tex>i_{next} = i_{prev} \mid (i_{prev} + 1) </tex>. Следующим элементом в последовательности будет элемент, у которого первый с конца ноль превратится в единицу. Можно заметить, что если к исходному элементу прибавить единицу, то необходимый ноль обратится в единицу, но при этом все следующие единицы обнулятся. Чтобы обратно их превратить в единицы, применим операцию | + | Все <tex>i</tex> мы можем получить следующим образом : <tex>i_{next} = i_{prev} \mid (i_{prev} + 1) </tex>. Следующим элементом в последовательности будет элемент, у которого первый с конца ноль превратится в единицу. Можно заметить, что если к исходному элементу прибавить единицу, то необходимый ноль обратится в единицу, но при этом все следующие единицы обнулятся. Чтобы обратно их превратить в единицы, применим операцию <tex>OR</tex>. Таким образом все нули в конце превратятся в единицы и мы получим нужный элемент. Для того, чтобы понять, что эта последовательность верна, достаточно посмотреть на таблицу. |
{| style="background-color:#CCC;margin:0.5px" | {| style="background-color:#CCC;margin:0.5px" | ||
Строка 89: | Строка 87: | ||
* [http://citeseer.ist.psu.edu/viewdoc/download;jsessionid=F180153B9C0CD797594314B736E2CCC5?doi=10.1.1.14.8917&rep=rep1&type=pdf Peter M. Fenwick: A new data structure for cumulative frequency] | * [http://citeseer.ist.psu.edu/viewdoc/download;jsessionid=F180153B9C0CD797594314B736E2CCC5?doi=10.1.1.14.8917&rep=rep1&type=pdf Peter M. Fenwick: A new data structure for cumulative frequency] | ||
* [http://en.wikipedia.org/wiki/Fenwick_tree Wikipedia — Fenwick_tree Fenwick tree] | * [http://en.wikipedia.org/wiki/Fenwick_tree Wikipedia — Fenwick_tree Fenwick tree] | ||
− | * [http://e-maxx.ru/algo/fenwick_tree | + | * [http://e-maxx.ru/algo/fenwick_tree Maximal:: algo:: Дерево Фенвика] |
Версия 15:21, 24 мая 2015
Дерево Фе́нвика (англ. Binary indexed tree) — структура данных, требующая
памяти и позволяющая эффективно (за ):- изменять значение любого элемента в массиве,
- выполнять некоторую ассоциативную, коммутативную, обратимую операцию на отрезке .
Впервые описано Питером Фенвиком в 1994 году.
Пусть дан массив
. Деревом Фенвика будем называть массив из элементов: , где и — некоторая функция. От выбора функции зависит время работы операций над деревом. Рассмотрим функцию, позволяющую делать операции вставки и изменения элемента за время .где — количество единиц в конце бинарной записи числа . Эту функцию можно записать так: где то есть число, состоящее из единиц. Это значит, что функция заменяет все подряд идущие единицы в конце числа на нули.
Эта функция задается простой формулой:
, где — это операция логического . При числа и его значения, увеличенного на единицу, мы получаем это число без последних подряд идущих единиц.Содержание
Запрос изменения элемента
Нам надо научиться быстро изменять частичные суммы в зависимости от того, как изменяются элементы. Рассмотрим как изменять величину
.Лемма: |
Для изменения величины необходимо изменить элементы дерева , для которых верно неравенство . |
Доказательство: |
необходимо менять те , для которых попадает в необходимые удовлетворяют условию . |
Лемма: |
Все такие удовлетворяют равенству , где — это операция . |
Доказательство: |
Первый элемент последовательности само | . Для него выполняется равенство, так как . По формуле мы заменим первый ноль на единицу. Неравенство при этом сохранится, так как осталось прежним или уменьшилось, а увеличилось. Можем заметить, что если количество единиц в конце не будет совпадать с , то формула нарушит неравенство, потому что либо само будет меньше, чем k, либо станет больше, чем . Таким образом, перебраны будут только нужные элементы
Все
мы можем получить следующим образом : . Следующим элементом в последовательности будет элемент, у которого первый с конца ноль превратится в единицу. Можно заметить, что если к исходному элементу прибавить единицу, то необходимый ноль обратится в единицу, но при этом все следующие единицы обнулятся. Чтобы обратно их превратить в единицы, применим операцию . Таким образом все нули в конце превратятся в единицы и мы получим нужный элемент. Для того, чтобы понять, что эта последовательность верна, достаточно посмотреть на таблицу.
Несложно заметить, что данная последовательность строго возрастает и в худшем случае будет применена логарифм раз, так как добавляет каждый раз по одной единице в двоичном разложении числа .
Напишем функцию, которая будет изменять элемент на , и при этом меняет соответствующие частичные суммы.
function modify(i, d): while i < N t[i] += d i = i | (i + 1)
Запрос получения суммы на префиксе
В качестве бинарной операции
Обозначим . Тогда .
Лемма: |
входит в сумму для , если . |
Для доказательства леммы рассмотрим битовую запись следующих чисел:
Реализация
Приведем код функции
:int sum(i): result = 0; while i >= 0 result += t[i]; i = f(i) - 1; return result;
Преимущества и недостатки дерева Фенвика
Главными преимуществами данной конструкции являются простота реализации и быстрота ответов на запросы за
. Также дерево Фенвика позволяет быстро изменять значения в массиве и находить некоторые функции от элементов массива. Недостатком является то, что при изменении одного элемента исходного массива, приходится пересчитывать частичные суммы, а это затратно по времени.