Дерево Фенвіка
Дерево Фенвіка – це структура даних на масиві довжини , яка дозволяє виконувати наступні операції:
Знайти значення деякої заданої функції на будь-якому відрізку за час ;
Змінювати значення будь-якого елементу масиву за ;
Вимагає пам’яті, а саме стільки, скільки необхідно для зберігання масиву з елементів.
Далі в якості будемо розглядати функцію сумування.
Нехай є масив чисел . Деревом Фенвіка є масив , в кожному елементі якого зберігається сума деяких елементів масиву :
де – деяка функція. Від її вибору буде залежати швидкість виконання основних операцій (знаходження суми на відрізку та модифікація елементу масиву). Нас цікавить такий вибір F, при якому вказані операції будуть виконуватися за логарифмічний час .
Визначимо , де через '&' позначено операцію побітового "і". Розглянемо двійковий запис числа та подивимося на його молодший біт. Якщо він дорівнює , то . Інакше число закінчується групою з однієї або декількох одиниць. Замінимо усі ці одиниці на нулі та присвоїмо отримане число значенню .
Приклад 1. Приклади обчислення функції
Бінарне представлення числа | Бінарне представлення числа | ||
---|---|---|---|
14 | 1110 | 1110 | 14 |
31 | 11111 | 00000 | 0 |
47 | 101111 | 100000 | 32 |
75 | 1001011 | 1001000 | 72 |
Розглянемо функцію , яка обчислює суму елементів масиву на відрізку . Замість того щоб рухатися по усім елементам масиву , будемо рухатися по масиву , здійснюючи стрибки через відрізки там, де це можливо. Оскільки
то
Вказана сума розписується до тих пір, поки значення не стане рівним нулю. Тобто складається із сум елементів масиву а на відрізках , і так далі.
Значення можна також записати у вигляді суми:
де .
Приклад 2. Розглянемо процес обчислення
стоп |
Із таблиці випливає, що . Або те ж саме, що сумуються інтервали: .
Наступний рисунок демонструє, що зберігається в масиві (дереві Фенвіка). Клітинка -го рядка та -го стовпчику чорна, якщо входить до часткової суми , тобто .
Аналогічно можна стверджувати, наприклад, що входить в якості доданку до , , .
Приклад 3. Оскільки , то .
Так як , то .
Для подальшого викладення матеріалу нам знадобиться функція , яка замінює останній у двійковому представленні числа на . Через '|' тут позначено операцію побітового "або".
Приклад 4. В наступній таблиці наведені приклади обчислення функції .
Бінарне представлення числа | Бінарне представлення числа | ||
---|---|---|---|
Розглянемо, як за числом швидко знаходити такі числа , що . Усі такі числа можна отримати з послідовними замінами самого правого (молодшого) нуля у двійковому представленні.
Приклад 5. Нехай . Знайдемо такі , для яких .
Послідовно замінюючи останній 0 у двійковому представленні на , отримаємо числа: , , , , і так далі. Отже буде міститися у і так далі.
Нехай . Аналогічно замінюючи послідовно по одному останньому нулю, отримуємо числа: і так далі. Отже міститься у і так далі.
Для збільшення значення на необхідно додати до усіх , в які входить в якості доданку. Наприклад, для збільшення на , необхідно додати до .
Таким чином, для виконання операції необхідно додати до
де , . Цикл додавання завершується як тільки стане більшим за .
Приклад 6. Нехай є масив чисел довжини (комірки масиву пронумеровані від до ). Необхідно додати одиницю до . Які елементи масиву необхідно збільшити на одиницю?
Діючи таким же чином, як і у прикладі 5, запишемо число у двійковому коді: , тобто , і першим значенням, яке слід збільшити на , буде . Інші кроки алгоритму подані у в таблиці:
Далі представлені функції, які працюють з деревом Фенвіка.
vector<int> t; int n; // ініціализація масиву t - дерева Фенвіка void init (int nn) { n = nn; t.assign (n, 0); } // a[0] + a[1] + ... + a[r] int sum (int r) { int result = 0; for (; r >= 0; r = (r & (r+1)) - 1) result += t\[r\]; return result; } // a[i] = a[i] + delta void inc (int i, int delta) { for (; i < n; i = (i | (i+1))) t[i] += delta; } // a[l] + a[l+1] + ... + a[r] int sum (int l, int r) { return sum (r) - sum (l-1); }