Дерево Фенвика — различия между версиями

Материал из Викиконспекты
Перейти к: навигация, поиск
Строка 7: Строка 7:
  
 
Пусть дан массив <tex> A = [a_0, a_1, ... , a_{n - 1}]</tex>.
 
Пусть дан массив <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</tex>, где <tex> i = 0 .. n - 1 </tex> и <tex> F(i) </tex> — некоторая функция, от выбора которой зависит время работы операций над деревом. Рассмотрим функцию, позволяющую делать операции вставки и изменения элемента за время <tex> O(\log n) </tex>. Она задается простой формулой: <tex> F(i) = i \And (i + 1) </tex>, где <tex> \And </tex> — это операция логического <tex>AND</tex>. При <tex>AND</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 \And (i + 1) </tex>, где <tex> \And </tex> — это операция побитового логического <tex>AND</tex>. При <tex>AND</tex> числа и его значения, увеличенного на единицу, мы получаем это число без последних подряд идущих единиц.
  
 
Эту функцию можно вычислять по другой формуле: <tex> F(i) = i - 2^{h(i)} + 1, </tex> где <tex> h(i) </tex> — количество подряд идущих единиц в конце бинарной записи числа <tex> i </tex>. Оба варианта равносильны, так как функция, заданная какой-либо из этих формул, заменяет все подряд идущие единицы в конце числа на нули.
 
Эту функцию можно вычислять по другой формуле: <tex> F(i) = i - 2^{h(i)} + 1, </tex> где <tex> h(i) </tex> — количество подряд идущих единиц в конце бинарной записи числа <tex> i </tex>. Оба варианта равносильны, так как функция, заданная какой-либо из этих формул, заменяет все подряд идущие единицы в конце числа на нули.
Строка 15: Строка 15:
 
{{Лемма
 
{{Лемма
 
|statement=
 
|statement=
Для изменения величины <tex>a_{k}</tex> необходимо изменить элементы дерева <tex>T_{i}</tex>, для которых верно неравенство <tex>F(i) \leqslant k \leqslant i</tex> .
+
Для изменения величины <tex>a_{k}</tex> необходимо изменить элементы дерева <tex>T_{i}</tex>, для индексов <tex>i</tex> которых верно неравенство <tex>F(i) \leqslant k \leqslant i</tex> .
 
|proof=
 
|proof=
 
<tex> T_i =\sum\limits_{k = F(i)}^{i} a_k , i = 0 .. n - 1 \Rightarrow</tex> необходимо менять те <tex>T_i</tex>, для которых <tex>a_{k}</tex> попадает в <tex>T_i \Rightarrow</tex> необходимые <tex> i </tex> удовлетворяют условию <tex>F(i) \leqslant k \leqslant i</tex>.
 
<tex> T_i =\sum\limits_{k = F(i)}^{i} a_k , i = 0 .. n - 1 \Rightarrow</tex> необходимо менять те <tex>T_i</tex>, для которых <tex>a_{k}</tex> попадает в <tex>T_i \Rightarrow</tex> необходимые <tex> i </tex> удовлетворяют условию <tex>F(i) \leqslant k \leqslant i</tex>.
Строка 21: Строка 21:
  
 
{{Лемма
 
{{Лемма
|statement= Все такие <tex> i </tex> удовлетворяют равенству <tex>i_{next} = i_{prev}  \mid  (i_{prev} + 1) </tex>, где <tex> \mid </tex> — это операция побитового логического <tex> OR </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> будет меньше, чем <tex>k</tex>, либо <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> будет меньше, чем <tex>k</tex>, либо <tex> F(i) </tex> станет больше, чем <tex> k </tex>. Таким образом, перебраны будут только нужные элементы}}
   
+
 
 +
Заметим, что <tex>F(i)</tex> возрастает немонотонно.
 +
 
 +
{| style="background-color:#CCC;margin:0.5px"
 +
|style="background-color:#EEE;padding:2px 30px"| <tex>i</tex>, десятичная запись
 +
|style="background-color:#FFF;padding:2px 30px"| <tex>0</tex>
 +
|style="background-color:#FFF;padding:2px 30px"| <tex>1</tex>
 +
|style="background-color:#FFF;padding:2px 30px"| <tex>2</tex>
 +
|style="background-color:#FFF;padding:2px 30px"| <tex>3</tex>
 +
|style="background-color:#FFF;padding:2px 30px"| <tex>4</tex>
 +
|style="background-color:#FFF;padding:2px 30px"| <tex>5</tex>
 +
|style="background-color:#FFF;padding:2px 30px"| <tex>6</tex>
 +
|style="background-color:#FFF;padding:2px 30px"| <tex>7</tex>
 +
|style="background-color:#FFF;padding:2px 30px"| <tex>8</tex>
 +
|style="background-color:#FFF;padding:2px 30px"| <tex>9</tex>
 +
|style="background-color:#FFF;padding:2px 30px"| <tex>10</tex>
 +
 
 +
|-
 +
|style="background-color:#EEE;padding:2px 30px"| <tex>i</tex>, двоичная запись
 +
|style="background-color:#FFF;padding:2px 30px"| <tex>0000</tex>
 +
|style="background-color:#FFF;padding:2px 30px"| <tex>0001</tex>
 +
|style="background-color:#FFF;padding:2px 30px"| <tex>0010</tex>
 +
|style="background-color:#FFF;padding:2px 30px"| <tex>0011</tex>
 +
|style="background-color:#FFF;padding:2px 30px"| <tex>0100</tex>
 +
|style="background-color:#FFF;padding:2px 30px"| <tex>0101</tex>
 +
|style="background-color:#FFF;padding:2px 30px"| <tex>0110</tex>
 +
|style="background-color:#FFF;padding:2px 30px"| <tex>0111</tex>
 +
|style="background-color:#FFF;padding:2px 30px"| <tex>1000</tex>
 +
|style="background-color:#FFF;padding:2px 30px"| <tex>1001</tex>
 +
|style="background-color:#FFF;padding:2px 30px"| <tex>1010</tex>
 +
|-
 +
|style="background-color:#EEE;padding:2px 30px"| <tex>F(i)</tex>, двоичная запись
 +
|style="background-color:#FFF;padding:2px 30px"| <tex>0000</tex>
 +
|style="background-color:#FFF;padding:2px 30px"| <tex>0000</tex>
 +
|style="background-color:#FFF;padding:2px 30px"| <tex>0010</tex>
 +
|style="background-color:#FFF;padding:2px 30px"| <tex>0000</tex>
 +
|style="background-color:#FFF;padding:2px 30px"| <tex>0100</tex>
 +
|style="background-color:#FFF;padding:2px 30px"| <tex>0100</tex>
 +
|style="background-color:#FFF;padding:2px 30px"| <tex>0110</tex>
 +
|style="background-color:#FFF;padding:2px 30px"| <tex>0000</tex>
 +
|style="background-color:#FFF;padding:2px 30px"| <tex>1000</tex>
 +
|style="background-color:#FFF;padding:2px 30px"| <tex>1000</tex>
 +
|style="background-color:#FFF;padding:2px 30px"| <tex>1010</tex>
 +
|-
 +
|style="background-color:#EEE;padding:2px 30px"| <tex>F(i)</tex>, десятичная запись
 +
|style="background-color:#FFF;padding:2px 30px"| <tex>0</tex>
 +
|style="background-color:#FFF;padding:2px 30px"| <tex>0</tex>
 +
|style="background-color:#FFF;padding:2px 30px"| <tex>2</tex>
 +
|style="background-color:#FFF;padding:2px 30px"| <tex>0</tex>
 +
|style="background-color:#FFF;padding:2px 30px"| <tex>4</tex>
 +
|style="background-color:#FFF;padding:2px 30px"| <tex>4</tex>
 +
|style="background-color:#FFF;padding:2px 30px"| <tex>6</tex>
 +
|style="background-color:#FFF;padding:2px 30px"| <tex>0</tex>
 +
|style="background-color:#FFF;padding:2px 30px"| <tex>8</tex>
 +
|style="background-color:#FFF;padding:2px 30px"| <tex>8</tex>
 +
|style="background-color:#FFF;padding:2px 30px"| <tex>10</tex>
 +
|}
 +
 
 +
 
 
Все <tex>i</tex> мы можем получить следующим образом : <tex>i_{next} = i_{prev}  \mid  (i_{prev} + 1) </tex>. Следующим элементом в последовательности будет элемент, у которого первый с конца ноль превратится в единицу. Можно заметить, что если к исходному элементу прибавить единицу, то необходимый ноль обратится в единицу, но при этом все следующие единицы обнулятся. Чтобы обратно их превратить в единицы, применим операцию <tex>OR</tex>. Таким образом все нули в конце превратятся в единицы и мы получим нужный элемент. Для того, чтобы понять, что эта последовательность верна, достаточно посмотреть на таблицу.
 
Все <tex>i</tex> мы можем получить следующим образом : <tex>i_{next} = i_{prev}  \mid  (i_{prev} + 1) </tex>. Следующим элементом в последовательности будет элемент, у которого первый с конца ноль превратится в единицу. Можно заметить, что если к исходному элементу прибавить единицу, то необходимый ноль обратится в единицу, но при этом все следующие единицы обнулятся. Чтобы обратно их превратить в единицы, применим операцию <tex>OR</tex>. Таким образом все нули в конце превратятся в единицы и мы получим нужный элемент. Для того, чтобы понять, что эта последовательность верна, достаточно посмотреть на таблицу.
  

Версия 15:18, 27 мая 2015

Дерево Фе́нвика (англ. Binary indexed tree) — структура данных, требующая [math] O(n) [/math] памяти и позволяющая эффективно (за [math] O(\log n) [/math]) выполнять следующие операции:

По горизонтали - индексы массива [math]T[/math]
([math]T_i[/math] является суммой элементов массива [math]A[/math], индексы которых заштрихованы),
по вертикали - индексы массива [math]A[/math]

Впервые описано Питером Фенвиком в 1994 году.

Пусть дан массив [math] A = [a_0, a_1, ... , a_{n - 1}][/math]. Деревом Фенвика будем называть массив [math] T [/math] из [math] n [/math] элементов: [math] T_i = \sum\limits_{k = F(i)}^{i} a_k[/math], где [math] i = 0 .. n - 1 [/math] и [math] F(i) [/math] — некоторая функция, от выбора которой зависит время работы операций над деревом. Рассмотрим функцию, позволяющую делать операции вставки и изменения элемента за время [math] O(\log n) [/math]. Она задается простой формулой: [math] F(i) = i \And (i + 1) [/math], где [math] \And [/math] — это операция побитового логического [math]AND[/math]. При [math]AND[/math] числа и его значения, увеличенного на единицу, мы получаем это число без последних подряд идущих единиц.

Эту функцию можно вычислять по другой формуле: [math] F(i) = i - 2^{h(i)} + 1, [/math] где [math] h(i) [/math] — количество подряд идущих единиц в конце бинарной записи числа [math] i [/math]. Оба варианта равносильны, так как функция, заданная какой-либо из этих формул, заменяет все подряд идущие единицы в конце числа на нули.

Запрос изменения элемента

Нам надо научиться быстро изменять частичные суммы в зависимости от того, как изменяются элементы. Рассмотрим как изменяется массив [math]T[/math] при изменении элемента [math]a_k[/math].

Лемма:
Для изменения величины [math]a_{k}[/math] необходимо изменить элементы дерева [math]T_{i}[/math], для индексов [math]i[/math] которых верно неравенство [math]F(i) \leqslant k \leqslant i[/math] .
Доказательство:
[math]\triangleright[/math]
[math] T_i =\sum\limits_{k = F(i)}^{i} a_k , i = 0 .. n - 1 \Rightarrow[/math] необходимо менять те [math]T_i[/math], для которых [math]a_{k}[/math] попадает в [math]T_i \Rightarrow[/math] необходимые [math] i [/math] удовлетворяют условию [math]F(i) \leqslant k \leqslant i[/math].
[math]\triangleleft[/math]
Лемма:
Все такие [math] i [/math] можно найти по формуле [math]i_{next} = i_{prev} \mid (i_{prev} + 1) [/math], где [math] \mid [/math] — это операция побитового логического [math] OR [/math].
Доказательство:
[math]\triangleright[/math]
Из доказанной выше леммы следует, что первый элемент последовательности само [math] k [/math]. Для него выполняется равенство, так как [math] F(i) \leqslant i [/math]. По формуле [math]i_{next} = i_{prev} \mid (i_{prev} + 1) [/math] мы заменим первый ноль на единицу. Неравенство при этом сохранится, так как [math]F(i)[/math] осталось прежним или уменьшилось, а [math] i [/math] увеличилось. Можем заметить, что если количество единиц в конце не будет совпадать с [math] k [/math], то формула [math]i_{next} = i_{prev} \mid (i_{prev} + 1) [/math] нарушит неравенство, потому что либо само [math] i [/math] будет меньше, чем [math]k[/math], либо [math] F(i) [/math] станет больше, чем [math] k [/math]. Таким образом, перебраны будут только нужные элементы
[math]\triangleleft[/math]

Заметим, что [math]F(i)[/math] возрастает немонотонно.

[math]i[/math], десятичная запись [math]0[/math] [math]1[/math] [math]2[/math] [math]3[/math] [math]4[/math] [math]5[/math] [math]6[/math] [math]7[/math] [math]8[/math] [math]9[/math] [math]10[/math]
[math]i[/math], двоичная запись [math]0000[/math] [math]0001[/math] [math]0010[/math] [math]0011[/math] [math]0100[/math] [math]0101[/math] [math]0110[/math] [math]0111[/math] [math]1000[/math] [math]1001[/math] [math]1010[/math]
[math]F(i)[/math], двоичная запись [math]0000[/math] [math]0000[/math] [math]0010[/math] [math]0000[/math] [math]0100[/math] [math]0100[/math] [math]0110[/math] [math]0000[/math] [math]1000[/math] [math]1000[/math] [math]1010[/math]
[math]F(i)[/math], десятичная запись [math]0[/math] [math]0[/math] [math]2[/math] [math]0[/math] [math]4[/math] [math]4[/math] [math]6[/math] [math]0[/math] [math]8[/math] [math]8[/math] [math]10[/math]


Все [math]i[/math] мы можем получить следующим образом : [math]i_{next} = i_{prev} \mid (i_{prev} + 1) [/math]. Следующим элементом в последовательности будет элемент, у которого первый с конца ноль превратится в единицу. Можно заметить, что если к исходному элементу прибавить единицу, то необходимый ноль обратится в единицу, но при этом все следующие единицы обнулятся. Чтобы обратно их превратить в единицы, применим операцию [math]OR[/math]. Таким образом все нули в конце превратятся в единицы и мы получим нужный элемент. Для того, чтобы понять, что эта последовательность верна, достаточно посмотреть на таблицу.

[math]\i_{prev}[/math] [math]\ldots 011 \ldots 1[/math]
[math]i_{prev} + 1[/math] [math]\ldots 100 \ldots 0[/math]
[math]i_{next}[/math] [math]\ldots 111 \ldots 1[/math]


Несложно заметить, что данная последовательность строго возрастает и в худшем случае будет применена логарифм раз, так как добавляет каждый раз по одной единице в двоичном разложении числа [math]i[/math]. Напишем функцию, которая будет изменять элемент [math]a_i[/math] на [math]d[/math], и при этом меняет соответствующие частичные суммы.

function modify(i, d):
   while i < N
       t[i] += d
       i = i | (i + 1)

Запрос получения значения функции на префиксе

Пусть существует некоторая бинарная операция [math]\circ[/math]. Чтобы получить значение на отрезке [math][i, j][/math], нужно провести операцию, обратную к [math]\circ[/math], над значениями на отрезках [math][0, j][/math] и [math][0, i - 1][/math].

В качестве бинарной операции [math] \circ [/math] рассмотрим операцию сложения.
Обозначим [math] \circ_i = \mathrm sum(i) = \sum\limits_{k = 0}^{i} a_k [/math]. Тогда [math] \mathrm sum(i, j) = \sum\limits_{k = i}^{j} a_k = \circ_j - \circ_{i - 1} [/math].

Лемма:
[math] a_i [/math] входит в сумму для [math] t_k [/math], если [math] \exists j: k = i \mid (2^j - 1) [/math].

Для доказательства леммы рассмотрим битовую запись следующих чисел: [math] k - 2^{h(k)} + 1 \leqslant i \leqslant k [/math]

[math]k - 2^{h(k)} + 1[/math] [math]\ldots (0 \ldots 0)[/math]
[math]i[/math] [math]\ldots (\ldots \ldots)[/math]
[math]k[/math] [math]\ldots (1 \ldots 1)[/math]

Реализация

Приведем код функции [math] \mathrm sum(i) [/math]:

int sum(i):
   result = 0;
   while i >= 0
       result += t[i]
       i = f(i) - 1
   return result

Преимущества и недостатки дерева Фенвика

Главными преимуществами данной конструкции являются простота реализации и быстрота ответов на запросы за [math] O(\log{n}) [/math]. Также дерево Фенвика позволяет быстро изменять значения в массиве и находить некоторые функции от элементов массива. Недостатком является то, что при изменении одного элемента исходного массива, приходится пересчитывать частичные суммы, а это затратно по времени.

См. также

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