Устройство и построение
Разбиение массива на отрезки, почему массива размера 4n всегда хватает и как построить дерево за O(n).
2 мин
Разобьём массив пополам, каждую половину — пополам, и так до отдельных элементов. Получится дерево, в каждом узле которого хранится ответ для его отрезка.
[1..8]
[1..4] [5..8]
[1..2] [3..4] [5..6] [7..8]
1 2 3 4 5 6 7 8
Всего узлов меньше : листьев , внутренних — на один меньше. Высота дерева — .
Нумерация
Корень — вершина 1, дети вершины — это и . Такая нумерация избавляет от указателей: всё дерево живёт в одном массиве.
std::vector<long long> tree(4 * n);
void build(int v, int tl, int tr) {
if (tl == tr) { tree[v] = a[tl]; return; }
int tm = (tl + tr) / 2;
build(2 * v, tl, tm);
build(2 * v + 1, tm + 1, tr);
tree[v] = tree[2 * v] + tree[2 * v + 1];
}
Построение обходит каждый узел один раз: .
Почему 4n
Узлов меньше , но при такой нумерации номера не идут подряд: если не степень двойки, дерево получается неровным, и максимальный номер может превысить .
Оценка сверху такая. Достроим до ближайшей степени двойки ; тогда дерево полное, узлов ровно , и все номера меньше . Поскольку , все номера меньше .
Отсюда правило: выделяйте и не думайте. Попытка сэкономить и выделить приводит к выходу за границы массива: уже при максимальный использованный номер равен 13, а .
Оценка не запас «на всякий случай», а почти точная граница. Перебор по всем до 20 000 даёт наибольшее отношение — при максимальный номер равен 65 281. Урезать до тоже нельзя: впервые не хватает уже при , где номер доходит до 113.
Альтернатива: дополнить до степени двойки
Второй способ — сразу дополнить массив нейтральными элементами до длины . Тогда дерево полное, хватает ровно ячеек, а листья лежат в и адресуются напрямую.
Это основа итеративной реализации, которая короче и быстрее рекурсивной.
Плата — до вдвое большая память в худшем случае: при дополнение почти удваивает длину.