Дерево Фенвика
Описание
Дерево Фенвика (Fenwick tree) или двоичное индексированное дерево (ДИД, binary indexed tree, BIT) - это структура данных, которая позволяет эффективно обновлять элементы и вычислять их суммы.
По сравнению с обычным массивом, ДИД позволяет достичь лучшего баланса между двумя операциями: обновлением элементов и вычислением суммы элементов. В массиве n
чисел можно хранить либо сами числа, либо их суммы. В первом случае вычисление суммы чисел занимает линейное время. Во втором случае обновление элемента занимает линейное время. Противоположные операции выполняются за константное время. ДИД позволяет выполнять обе операции за время O(log n)
. Это достигается за счет представления чисел в виде дерева, где значением каждого узла является сумма чисел поддерева. Структура дерева позволяет выполнять операции за O(log n)
доступов к узлам.
Особенности реализации
ДИД представлено в виде массива. Каждый узел дерева хранит сумму узлов некоторого поддерева. Размер ДИД равен n
, где n
- размер исходного массива. В нашей реализации будет использоваться размер n+1
для простоты. Индексация начинается с 1.

Пример получения суммы элементов с помощью ДИД
- каждый узел имеет индекс (синий) и значение по индексу (зеленый)
- при запросе суммы
i
, возвращается суммаBITree[i]
и всех предковi
- индекс предка
i
может быть получен с помощью следующей формулы:parent(i) = i - i & (-i)
. Данная операция удаляет последний установленный битi
. Например, еслиi=12
, тоparent(i)
вернет8
Анимированный пример создания ДИД для массива [1, 2, 3, 4, 5]
путем добавления элементов одного за другим:

Интерактивную визуализацию ДИД можно посмотреть здесь.
Сложность
Временная
Запрос суммы | Обновление |
---|---|
O(log(n)) | O(log(n)) |
Пространственная
O(n)
Реализация
Приступаем к реализации дерева Фенвика:
// data-structures/tree/fenwick-tree.js
export default class FenwickTree {
// Конструктор создает дерево Фенвика размера `size`,
// однако, размер дерева `n+1`, потому что индексация начинается с `1`
constructor(size) {
this.size = size
// Заполняем массив нулями
this.tree = new Array(size + 1).fill(0)
}
}
Метод добавления значения к существующему на определенной позиции:
// Прибавляет значение к существующему на указанной позиции
increase(position, value) {
if (position < 1 || position > this.size) {
throw new Error('Позиция находится за пределами разрешенного диапазона')
}
// magic! :D
for (let i = position; i <= this.size; i += i & -i) {
this.tree[i] += value
}
return this
}
Метод получения суммы от индекса 1 до определенной позиции:
// Возвращает сумму от индекса 1 до указанной позиции
query(position) {
if (position < 1 || position > this.size) {
throw new Error('Позиция находится за пределами разрешенного диапазона')
}
let sum = 0
// magic! :D
for (let i = position; i > 0; i -= i & -i) {
sum += this.tree[i]
}
return sum
}
Метод получения суммы между двумя индексами:
// Возвращает сумму от `leftIndex` до `rightIndex`
queryRange(leftIndex, rightIndex) {
if (leftIndex > rightIndex) {
throw new Error('Левый индекс не может превышать правый')
}
if (leftIndex === 1) {
return this.query(rightIndex)
}
return this.query(rightIndex) - this.query(leftIndex - 1)
}
Полный код дерева Фенвика
Тесты
Запускаем тесты:
npm run test ./data-structures/tree/__tests__/fenwick-tree
