Давайте рассмотрим следующую проблему, чтобы понять деревья сегментов.
У нас есть массив arr [0. , , н-1]. Мы должны быть в состоянии 1 Найдите сумму элементов из индекса l в r, где 0 <= l <= r <= n-1
2 Измените значение указанного элемента массива на новое значение x. Нам нужно сделать arr [i] = x, где 0 <= i <= n-1.
Простое решение состоит в том, чтобы запустить цикл от l до r и вычислить сумму элементов в данном диапазоне. Чтобы обновить значение, просто сделайте arr [i] = x. Первая операция занимает время O (n), а вторая операция занимает время O (1).
Другое решение состоит в том, чтобы создать другой массив и сохранить сумму от начала до i в i-м индексе в этом массиве. Сумма заданного диапазона теперь может быть рассчитана за время O (1), но операция обновления теперь занимает время O (n). Это хорошо работает, если количество операций с запросами велико и очень мало обновлений.
Что делать, если количество запросов и обновлений равны? Можем ли мы выполнить обе операции за O (log n) раз, учитывая массив? Мы можем использовать дерево сегментов для выполнения обеих операций за время O (Logn).
Представление Сегментных деревьев 1. Конечные узлы являются элементами входного массива. 2. Каждый внутренний узел представляет собой некоторое слияние листовых узлов. Слияние может быть разным для разных задач. Для этой проблемы объединение — это сумма листьев под узлом.
Представление массива дерева используется для представления деревьев сегментов. Для каждого узла по индексу i левый потомок имеет индекс 2 * i + 1, правый потомок — 2 * i + 2, а родительский — ,
Как выглядит дерево сегментов в памяти? Как и куча, дерево сегментов также представляется в виде массива. Разница здесь в том, что это не полное двоичное дерево. Это скорее полное двоичное дерево (каждый узел имеет 0 или 2 дочерних элемента), и все уровни заполнены, кроме, возможно, последнего уровня. В отличие от кучи, последний уровень может иметь промежутки между узлами. Ниже приведены значения в массиве дерева сегментов для приведенной выше диаграммы.
Below is memory representation of segment tree for input array {1, 3, 5, 7, 9, 11} st[] = {36, 9, 27, 4, 5, 16, 11, 1, 3, DUMMY, DUMMY, 7, 9, DUMMY, DUMMY}
Фиктивные значения никогда не доступны и не имеют смысла. Это некоторая потеря пространства из-за простого представления массива. Мы можем оптимизировать эту потерю, используя некоторые умные реализации, но код для суммирования и обновления становится более сложным.
Построение дерева сегментов из заданного массива Начнем с сегмента arr [0. , , н-1]. и каждый раз, когда мы делим текущий сегмент на две половины (если он еще не стал сегментом длины 1), а затем вызываем одну и ту же процедуру для обеих половин, и для каждого такого сегмента мы сохраняем сумму в соответствующем узле. Все уровни построенного дерева сегментов будут полностью заполнены, кроме последнего уровня. Кроме того, дерево будет полным двоичным деревом, потому что мы всегда делим сегменты на две половины на каждом уровне. Поскольку построенное дерево всегда является полным двоичным деревом с n листьями, будет иметься n-1 внутренних узлов. Таким образом, общее количество узлов будет 2 * n — 1. Обратите внимание, что это не включает фиктивные узлы.
Каков общий размер массива, представляющего дерево сегментов? Если n — степень 2, то фиктивные узлы отсутствуют. Таким образом, размер дерева сегментов составляет 2n-1 (n листовых узлов и n-1) внутренних узлов. Если n не является степенью 2, то размер дерева будет 2 * x — 1, где x — наименьшая степень 2, превышающая n. Например, когда n = 10, тогда размер массива, представляющего дерево сегментов, составляет 2 * 16-1 = 31. Альтернативное объяснение размера основано на хейгенте. Высота сегмента дерева будет , Поскольку дерево представлено с использованием массива, и необходимо поддерживать связь между родительскими и дочерними индексами, объем памяти, выделенной для дерева сегментов, будет ,
Запрос суммы заданного диапазона Как только дерево построено, как получить сумму, используя построенное дерево сегментов. Ниже приведен алгоритм получения суммы элементов.
int getSum(node, l, r)
{
if the range of the node is within l and r
return value in the node
else if the range of the node is completely outside l and r
return 0
else
return getSum(node's left child, l, r) +
getSum(node's right child, l, r)
}
Обновить значение Подобно построению дерева и операциям запроса, обновление также может быть выполнено рекурсивно. Нам дан индекс, который необходимо обновить. Пусть diff будет значением, которое будет добавлено. Мы начинаем с корня дерева сегментов и добавляем diff ко всем узлам, которые имеют индекс в своем диапазоне. Если узел не имеет заданного индекса в своем диапазоне, мы не вносим никаких изменений в этот узел.
Реализация: Ниже приводится реализация дерева сегментов. Программа реализует построение дерева сегментов для любого заданного массива. Он также реализует операции запроса и обновления.
C ++
// C ++ программа для отображения операций с сегментным деревом, таких как конструкция, запрос // и обновляем #include <bits/stdc++.h>
usingnamespacestd;
// Полезная функция для получения среднего индекса из угловых индексов.
intgetMid(ints, inte) { returns + (e -s)/2; }
/ * Рекурсивная функция для получения суммы значений в заданном диапазоне
массива. Ниже приведены параметры для этой функции.
st -> Указатель на дерево сегментов
si -> Индекс текущего узла в дереве сегментов. Первоначально
0 передается как root всегда с индексом 0
ss & se -> Начальный и конечный индексы представленного сегмента
текущим узлом, т. е. st [si]
qs & qe -> начальные и конечные индексы диапазона запросов * /
// Базовый случай: если входной индекс лежит вне диапазона
// этот сегмент
if(i < ss || i > se)
return;
// Если входной индекс находится в диапазоне этого узла, тогда обновляем
// значение узла и его потомков
st[si] = st[si] + diff;
if(se != ss)
{
intmid = getMid(ss, se);
updateValueUtil(st, ss, mid, i, diff, 2*si + 1);
updateValueUtil(st, mid+1, se, i, diff, 2*si + 2);
}
}
// Функция для обновления значения во входном массиве и сегментном дереве. // Он использует updateValueUtil () для обновления значения в дереве сегментов
/ * Функция для построения дерева сегментов из заданного массива. Эта функция выделяет память для дерева сегментов и вызывает constructSTUtil () для заполнить выделенную память * /
int*constructST(intarr[], intn)
{
// Выделим память для дерева сегментов
// Высота сегмента дерева
intx = (int)(ceil(log2(n)));
// Максимальный размер дерева сегментов
intmax_size = 2*(int)pow(2, x) - 1;
// Распределить память
int*st = newint[max_size];
// Заполняем выделенную память st
constructSTUtil(arr, 0, n-1, st, 0);
// Возвращаем построенное дерево сегментов
returnst;
}
// Программа драйвера для проверки вышеуказанных функций
intmain()
{
intarr[] = {1, 3, 5, 7, 9, 11};
intn = sizeof(arr)/sizeof(arr[0]);
// Построить дерево сегментов из заданного массива
int*st = constructST(arr, n);
// Вывести сумму значений в массиве из индекса 1 в 3
cout<<"Sum of values in given range = "<<getSum(st, n, 1, 3)<<endl;
// Обновление: установить arr [1] = 10 и обновить соответствующее
// сегментируем узлы дерева
updateValue(arr, st, n, 1, 10);
// Находим сумму после обновления значения
cout<<"Updated sum of values in given range = "
<<getSum(st, n, 1, 3)<<endl;
return0;
} // Этот код предоставлен rathbhupendra
С
// C-программа для отображения операций с сегментным деревом, таких как конструкция, запрос // и обновляем #include <stdio.h> #include <math.h>
// Полезная функция для получения среднего индекса из угловых индексов.
intgetMid(ints, inte) { returns + (e -s)/2; }
/ * Рекурсивная функция для получения суммы значений в заданном диапазоне
массива. Ниже приведены параметры для этой функции.
st -> Указатель на дерево сегментов
si -> Индекс текущего узла в дереве сегментов. Первоначально
0 передается как root всегда с индексом 0
ss & se -> Начальный и конечный индексы представленного сегмента
текущим узлом, т. е. st [si]
qs & qe -> начальные и конечные индексы диапазона запросов * /
// Базовый случай: если входной индекс лежит вне диапазона
// этот сегмент
if(i < ss || i > se)
return;
// Если входной индекс находится в диапазоне этого узла, тогда обновляем
// значение узла и его потомков
st[si] = st[si] + diff;
if(se != ss)
{
intmid = getMid(ss, se);
updateValueUtil(st, ss, mid, i, diff, 2*si + 1);
updateValueUtil(st, mid+1, se, i, diff, 2*si + 2);
}
}
// Функция для обновления значения во входном массиве и сегментном дереве. // Он использует updateValueUtil () для обновления значения в дереве сегментов
// Базовый случай: если входной индекс лежит вне диапазона
// этот сегмент
if(i < ss || i > se)
return;
// Если входной индекс находится в диапазоне этого узла, то обновляем
// значение узла и его потомков
st[si] = st[si] + diff;
if(se != ss) {
intmid = getMid(ss, se);
updateValueUtil(ss, mid, i, diff, 2* si + 1);
updateValueUtil(mid + 1, se, i, diff, 2* si + 2);
}
}
// Функция для обновления значения во входном массиве и сегментном дереве.
// Он использует updateValueUtil () для обновления значения в дереве сегментов
voidupdateValue(intarr[], intn, inti, intnew_val)
{
// Проверяем ошибочный индекс ввода
if(i < 0|| i > n - 1) {
System.out.println("Invalid Input");
return;
}
// Получить разницу между новым значением и старым значением
intdiff = new_val - arr[i];
// Обновляем значение в массиве
arr[i] = new_val;
// Обновляем значения узлов в сегментном дереве
updateValueUtil(0, n - 1, i, diff, 0);
}
// Возвращаем сумму элементов в диапазоне от индекса qs (quey start) до
// qe (конец запроса). Он в основном использует getSumUtil ()
intgetSum(intn, intqs, intqe)
{
// Проверяем ошибочные входные значения
if(qs < 0|| qe > n - 1|| qs > qe) {
System.out.println("Invalid Input");
return-1;
}
returngetSumUtil(0, n - 1, qs, qe, 0);
}
// Рекурсивная функция, которая создает дерево сегментов для массива [ss..se].
// si - индекс текущего узла в сегментном дереве st
intconstructSTUtil(intarr[], intss, intse, intsi)
{
// Если в массиве есть один элемент, сохраняем его в текущем узле
// сегментируем дерево и возвращаем
if(ss == se) {
st[si] = arr[ss];
returnarr[ss];
}
// Если есть более одного элемента, то повторить для левого и
// правильные поддеревья и храним сумму значений в этом узле
intmid = getMid(ss, se);
st[si] = constructSTUtil(arr, ss, mid, si * 2+ 1) +
constructSTUtil(arr, mid + 1, se, si * 2+ 2);
returnst[si];
}
// Программа драйвера для проверки вышеуказанных функций
publicstaticvoidmain(String args[])
{
intarr[] = {1, 3, 5, 7, 9, 11};
intn = arr.length;
SegmentTree tree = newSegmentTree(arr, n);
// Построить дерево сегментов из заданного массива
// Вывести сумму значений в массиве из индекса 1 в 3
System.out.println("Sum of values in given range = "+
tree.getSum(n, 1, 3));
// Обновить: установить arr [1] = 10 и обновить соответствующий сегмент
// узлы дерева
tree.updateValue(arr, n, 1, 10);
// Находим сумму после обновления значения
System.out.println("Updated sum of values in given range = "+
tree.getSum(n, 1, 3));
}
} // Этот код предоставлен Ankur Narain Verma
python3
# Программа Python3 для отображения операций с сегментным деревом, например # конструкция, запрос и обновление
frommath importceil, log2;
# Полезная функция для получения # средний индекс из угловых индексов.
defgetMid(s, e) :
returns +(e -s) //2;
"" "Рекурсивная функция для получения суммы значений
в заданном диапазоне массива. Следующее
параметры для этой функции
st -> Указатель на дерево сегментов
si -> Индекс текущего узла в дереве сегментов.
Первоначально 0 передается как корень всегда с индексом 0
ss & se -> Начальный и конечный индексы сегмента
представлен текущим узлом, т. е. st [si]
qs & qe -> Начальные и конечные индексы диапазона запросов "" "
defgetSumUtil(st, ss, se, qs, qe, si) :
# Если сегмент этого узла является частью данного диапазона,
# затем вернуть сумму сегмента
if(qs <=ss andqe >=se) :
returnst[si];
# Если сегмент этого узла
# вне заданного диапазона
if(se < qs orss > qe) :
return0;
# Если часть этого сегмента перекрывается
# с заданным диапазоном
mid =getMid(ss, se);
returngetSumUtil(st, ss, mid, qs, qe, 2*si +1) +\
getSumUtil(st, mid +1, se, qs, qe, 2*si +2);
"" "Рекурсивная функция для обновления узлов которые имеют данный индекс в своем диапазоне. Ниже приведены параметры st, si, ss и se. такие же, как getSumUtil () я -> индекс элемента, который будет обновлен.
Этот индекс находится во входном массиве.
diff -> Значение, которое будет добавлено ко всем узлам у которого я в диапазоне "" "
defupdateValueUtil(st, ss, se, i, diff, si) :
# Базовый случай: если входной индекс лежит
# вне диапазона этого сегмента
if(i < ss ori > se) :
return;
# Если входной индекс находится в диапазоне этого узла,
# затем обновить значение узла и его потомков
st[si] =st[si] +diff;
if(se !=ss) :
mid =getMid(ss, se);
updateValueUtil(st, ss, mid, i,
diff, 2*si +1);
updateValueUtil(st, mid +1, se, i,
diff, 2*si +2);
# Функция для обновления значения во входном массиве # и дерево сегментов. Он использует updateValueUtil () # обновить значение в дереве сегментов
defupdateValue(arr, st, n, i, new_val) :
# Проверка на ошибочный индекс ввода
if(i < 0ori > n -1) :
print("Invalid Input", end ="");
return;
# Получите разницу между
# новое значение и старое значение
diff =new_val -arr[i];
# Обновить значение в массиве
arr[i] =new_val;
# Обновлять значения узлов в сегментном дереве
updateValueUtil(st, 0, n -1, i, diff, 0);
# Возвращаем сумму элементов в диапазоне от # index qs (начало quey) до qe (конец запроса). # В основном использует getSumUtil ()
defgetSum(st, n, qs, qe) :
# Проверьте ошибочные входные значения
if(qs < 0orqe > n -1orqs > qe) :
print("Invalid Input", end ="");
return-1;
returngetSumUtil(st, 0, n -1, qs, qe, 0);
# Рекурсивная функция, которая создает # Сегментное дерево для массива [ss..se]. # si - индекс текущего узла в дереве сегментов st
defconstructSTUtil(arr, ss, se, st, si) :
# Если в массиве есть один элемент,
# сохранить его в текущем узле
# сегмент дерева и возврат
if(ss ==se) :
st[si] =arr[ss];
returnarr[ss];
# Если есть более одного элемента,
# затем повторить для левого и правого поддеревья
# и сохранить сумму значений в этом узле
mid =getMid(ss, se);
st[si] =constructSTUtil(arr, ss, mid, st, si *2+1) +\
constructSTUtil(arr, mid +1, se, st, si *2+2);
returnst[si];
"" "Функция для построения дерева сегментов из данного массива. Эта функция выделяет память для дерева сегментов и вызывает constructSTUtil () для заполнить выделенную память "" "
defconstructST(arr, n) :
# Распределить память для дерева сегментов
# Высота сегмента дерева
x =(int)(ceil(log2(n)));
# Максимальный размер дерева сегментов
max_size =2*(int)(2**x) -1;
# Распределить память
st =[0] *max_size;
# Заполнить выделенную память st
constructSTUtil(arr, 0, n -1, st, 0);
# Вернуть построенное дерево сегментов
returnst;
Код водителя
if__name__ =="__main__":
arr =[1, 3, 5, 7, 9, 11];
n =len(arr);
# Построить дерево сегментов из заданного массива
st =constructST(arr, n);
# Вывести сумму значений в массиве из индекса 1 в 3
print("Sum of values in given range = ",
getSum(st, n, 1, 3));
# Обновление: установите arr [1] = 10 и обновите
# соответствующие узлы дерева сегментов
updateValue(arr, st, n, 1, 10);
# Найти сумму после обновления значения
print("Updated sum of values in given range = ",
getSum(st, n, 1, 3), end ="");
# Этот код предоставлен AnkitRai01
C #
// C # Программа для отображения дерева сегментов // операции типа строительства, // запрос и обновление
usingSystem;
classSegmentTree
{
int[]st; // Массив, в котором хранятся узлы дерева сегментов
/ * Конструктор для построения сегмента
дерево из данного массива. Этот конструктор
выделяет память для дерева сегментов и вызовов
constructSTUtil () для заполнения выделенной памяти * /
// Построить дерево сегментов из заданного массива
// Вывести сумму значений в массиве из индекса 1 в 3
Console.WriteLine("Sum of values in given range = "+
tree.getSum(n, 1, 3));
// Обновление: установить arr [1] = 10 и обновить
// соответствующие узлы дерева сегментов
tree.updateValue(arr, n, 1, 10);
// Находим сумму после обновления значения
Console.WriteLine("Updated sum of values in given range = "+
tree.getSum(n, 1, 3));
}
}
/ * Этот код предоставлен PrinciRaj1992 * /
Выход:
Sum of values in given range = 15
Updated sum of values in given range = 22
Сложность времени: Сложность времени для построения дерева O (n). Всего существует 2n-1 узлов, и значение каждого узла вычисляется только один раз при построении дерева.
Временная сложность запроса составляет O (Logn). Чтобы запросить сумму, мы обрабатываем не более четырех узлов на каждом уровне, и количество уровней равно O (Logn).
Временная сложность обновления также O (Logn). Чтобы обновить значение листа, мы обрабатываем один узел на каждом уровне, и количество уровней равно O (Logn).