Многомерное дерево отрезков — различия между версиями

Материал из Викиконспекты
Перейти к: навигация, поиск
Строка 22: Строка 22:
  
 
Заметим, что в общем случае для хранения <tex>p</tex>-мерного дерева отрезков требуется <tex>4^p n</tex> памяти, где <tex>n</tex> {{---}} общее количество элементов.
 
Заметим, что в общем случае для хранения <tex>p</tex>-мерного дерева отрезков требуется <tex>4^p n</tex> памяти, где <tex>n</tex> {{---}} общее количество элементов.
 
==Построение==
 
  
 
==Запрос==
 
==Запрос==
Строка 33: Строка 31:
  
 
  operationCalc(area[], x1, x2, ..., xP, leftBorder, rightBorder, needLeft, needRight, vertex)
 
  operationCalc(area[], x1, x2, ..., xP, leftBorder, rightBorder, needLeft, needRight, vertex)
  if needLeft > needRight
+
  '''if''' needLeft > needRight
   return 0 // нейтральный элемент по операции <tex>\times</tex>
+
   '''return''' 0 // нейтральный элемент по операции <tex>\times</tex>
  if leftBorder == needLeft && rightBorder == needRight
+
  '''if''' leftBorder == needLeft && rightBorder == needRight
   if последняя координата
+
   '''if''' последняя координата
   return t[x1][x2]...[xP][vertex]
+
   '''return''' t[x1][x2]...[xP][vertex]
   else
+
   '''else'''
   return operationCalc(area[], x1, x2, ..., xP, vertex, 0, m - 1, area[P + 2].left, area[P + 2].right, 0)
+
   '''return''' operationCalc(area[], x1, x2, ..., xP, vertex, 0, m - 1, area[P + 2].left, area[P + 2].right, 0)
 
  med = (leftBorder + rightBorder) / 2
 
  med = (leftBorder + rightBorder) / 2
  return operationCalc(area[], x1, x2, ..., xP, leftBorder, med, needLeft, min(needRight, med), vertex * 2 + 1) <tex>\times</tex>
+
  '''return''' operationCalc(area[], x1, x2, ..., xP, leftBorder, med, needLeft, min(needRight, med), vertex * 2 + 1) <tex>\times</tex>
 
         operationCalc(area[], x1, x2, ..., xP, med + 1, rightBorder, max(needLeft, med + 1), needRight, vertex * 2 + 2)
 
         operationCalc(area[], x1, x2, ..., xP, med + 1, rightBorder, max(needLeft, med + 1), needRight, vertex * 2 + 2)
  
Строка 63: Строка 61:
 
     update(newElem, x1, x2, ..., xP, x1Left, x1Right, x2Left, x2Right, ..., xPLeft, xPRight, leftBorder, med, vertex * 2 + 1)
 
     update(newElem, x1, x2, ..., xP, x1Left, x1Right, x2Left, x2Right, ..., xPLeft, xPRight, leftBorder, med, vertex * 2 + 1)
 
   '''else'''
 
   '''else'''
     update(newElem, x1, x2, ..., xP, x1Left, x1Right, x2Left, x2Right, ..., xPLeft, xPRight, med + 1, vertex * 2 + 2)
+
     update(newElem, x1, x2, ..., xP, x1Left, x1Right, x2Left, x2Right, ..., xPLeft, xPRight, med + 1, rightBorder, vertex * 2 + 2)
 
   '''if''' последняя координата
 
   '''if''' последняя координата
 
   '''for''' I = 1..n
 
   '''for''' I = 1..n
Строка 74: Строка 72:
 
     update(newElem, x1, x2, ..., xP, vertex, x1Left, x1Rigth, x2Left, x2Right, ..., leftBorder, rightBorder, 0, m - 1, 0)
 
     update(newElem, x1, x2, ..., xP, vertex, x1Left, x1Rigth, x2Left, x2Right, ..., leftBorder, rightBorder, 0, m - 1, 0)
  
 +
==Построение==
 +
Построение многомерного дерева отрезков практически ничем не отличается от его обновления. Единственное различие {{---}} если рассматриваемый отрезок состоит из более чем одного элемента, то необходимо рекурсивно вызываться из обоих частей.
 +
 +
Псевдокод:
 +
build(x1, x2, ..., xP, x1Left, x1Right, x2Left, x2Right, ..., xPLeft, xPRight, leftBorder, rightBorder, vertex)
 +
  '''if''' leftBorder != rightBorder
 +
  med = (leftBorder + rightBorder) / 2
 +
  update(newElem, x1, x2, ..., xP, x1Left, x1Right, x2Left, x2Right, ..., xPLeft, xPRight, leftBorder, med, vertex * 2 + 1)
 +
  update(newElem, x1, x2, ..., xP, x1Left, x1Right, x2Left, x2Right, ..., xPLeft, xPRight, med + 1, rightBorder, vertex * 2 + 2)
 +
  '''if''' последняя координата
 +
  '''for''' I = 1..n
 +
    '''if''' xILeft != xIRigth
 +
    t[x1][x2]...[xP][vertex] = t[x1][x2]...[xI * 2 + 1]...[vertex] <tex>\times</tex> t[x1][x2]...[xI * 2 + 2]...[vertex]
 +
    '''return'''
 +
  t[x1][x2]...[xP][vertex] = data[x1Left][x2Left]...[xPLeft][vertex]
 +
  '''else'''
 +
  '''if''' leftBorder != rightBorder
 +
    update(newElem, x1, x2, ..., xP, vertex, x1Left, x1Rigth, x2Left, x2Right, ..., leftBorder, rightBorder, 0, m - 1, 0)
 +
 +
Заметим, что построение дерева требует <tex>O(n)</tex> времени, где <tex>n</tex> {{---}} общее число элементов в массиве.
 
==Источники==
 
==Источники==
 
* [http://e-maxx.ru/algo/segment_tree Дерево отрезков {{---}} e-maxx.ru]
 
* [http://e-maxx.ru/algo/segment_tree Дерево отрезков {{---}} e-maxx.ru]

Версия 20:24, 2 июня 2012

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

Принцип работы

Пример некоторой стадии работы алгоритма (поиск элементов, подходящих некоторой области)

[math]n[/math]-мерное дерево отрезков — обычное дерево отрезков, элементами которого являются деревья отрезков размерности на 1 меньше. Основная идея заключается в рекурсивном переходе к деревьям меньшей размерности. Рассмотрим работу этого принципа на следующем примере. Пусть задано [math]p[/math]-мерное пространство с координатными осями [math]x_1, x_2, x_3...x_p[/math]. Необходимо найти значение некоторой ассоциативной функции на гиперпрямоугольнике.

Функция, вычисляющая ответ, должна работать следующим образом. На вход она принимает [math]i[/math]-мерное дерево отрезков, которое соответствует рассматриваемой области (где [math]i[/math] — количество координатных осей, которые не были рассмотрены), а также [math]i[/math]-мерную область, для которой следует вычислить функцию. Вначале она находит [math]i-1[/math]-мерные деревья отрезков, которые соответствуют отрезку по [math]p-i+1[/math] координате, и рекурсивно запускается от них (если текущее дерево одномерное, то функция просто возвращает ответ из соответствующего листа). После этого считает итоговый результат, используя полученные после рекурсивных вызовов значения.

Для того, чтобы определить, от каких именно деревьев отрезков следует запускаться рекурсивно, действовать необходимо так же, как и в одномерном случае. Т. е. если текущий отрезок не пересекается с необходимым, то возвращаем нейтральный элемент, если он полностью лежит в необходимом отрезке, то рекурсивно переходим к следующей координате, иначе разобьем текущий отрезок пополам, и рассмотри отдельно каждую из частей.

На рисунке справа показан пример обработки очередной координаты (поиск соответствующих отрезку элементов — деревьев на 1 меньшей мерности).

Таким образом, алгоритм совершит [math]p[/math] вхождений в рекурсию, каждая итерация которой работает за [math]O(\log n)[/math] и получим необходимую асимптотику.

Хранение

Пример двумерного дерева отрезков для 16 элементов

Пусть необходимо хранить дерево отрезков для [math]p[/math]-мерной области, размеры которой [math]n_1 \times n_2 \times ... \times n_p[/math]. Удобнее всего это делать с помощью [math]p[/math]-мерного массива. Однако его размеры по каждой координате, так же как и в одномерном случае, должны превышать размеры соответствующего отрезка в 4 раза. Т. е. потребуется массив размером [math]4 n_1 \times 4 n_2 \times ... \times 4 n_p[/math]. Так двумерное дерево отрезков удобно хранить в виде массива, размером [math]4N \times 4M[/math]. Каждая строчка такого массива соответствует некоторому отрезку по первой координате. Сама же строчка является деревом отрезков по второй координате.

На рисунке справа показан пример дерева отрезков для суммы на массиве 4 на 4, заполненного числами от 1 от 16. Например, в элементе [math]a[2][0] = 100[/math] хранится сумма элементов, соответствующих отрезку [math][2..3][/math] по первой координате и [math][0..3][/math] по второй в исходном массиве. А в ячейке [math]a[0][0] = 136[/math] хранится сумма всех элементов.

Интересно, что если построить дерево вначале по второй координате, а потом по первой, то получившийся массив будет таким же. Т. е. данный двумерный массив можно рассматривать как массив деревьев отрезков, где каждое дерево соответствует некоторому отрезку по второй координате, а в нем хранятся суммы по первой.

Заметим, что в общем случае для хранения [math]p[/math]-мерного дерева отрезков требуется [math]4^p n[/math] памяти, где [math]n[/math] — общее количество элементов.

Запрос

Рассмотрим отличия реализации многомерного и одномерного случаев. На самом деле, отличаются реализации только в двух местах. Во-первых, если рассматриваемый отрезок совпадает с необходимым, то в одномерном случае функция просто возвращает число, которое находится в текущем элементе массива. В многомерном случае, если рассматриваемая координата не последняя, следует вместо этого узнать значение, рекурсивно перейдя к следующей координате, и вернуть его.

Еще один момент, в которых отличается реализация — передаваемые в функцию параметры. В многомерном случае кроме всего прочего следует также передать рассматриваемое [math]p-i+1[/math]-мерное дерево (или картеж из чисел, указывающих на соответствующие элементы массива), а также область, которую следует рассматривать (или [math]p-i+1[/math] пар чисел, обозначающих отрезки на соответствующих координатных осях). Все остальные детали реализации остаются такими же как и в одномерном дерево отрезков.

Псевдокод:

operationCalc(area[], x1, x2, ..., xP, leftBorder, rightBorder, needLeft, needRight, vertex)
if needLeft > needRight
 return 0 // нейтральный элемент по операции [math]\times[/math]
if leftBorder == needLeft && rightBorder == needRight
 if последняя координата
  return t[x1][x2]...[xP][vertex]
 else
  return operationCalc(area[], x1, x2, ..., xP, vertex, 0, m - 1, area[P + 2].left, area[P + 2].right, 0)
med = (leftBorder + rightBorder) / 2
return operationCalc(area[], x1, x2, ..., xP, leftBorder, med, needLeft, min(needRight, med), vertex * 2 + 1) [math]\times[/math]
       operationCalc(area[], x1, x2, ..., xP, med + 1, rightBorder, max(needLeft, med + 1), needRight, vertex * 2 + 2)

Обновление

Как и в одномерном случае, обновить в массиве необходимо не один элемент, а все, которые отвечают за области, в которых он присутствует. Таким образом, при обработке отрезка по некоторой координате (если она не последняя) следует выполнить следующие действия:

  • Если рассматриваемый отрезок содержит больше одного элемента, разобьем его на две части и рекурсивно перейдем в ту, где находится необходимый элемент
  • Перейдем к следующей координате

Заметим, что "переходов к следующей координаты" при рассмотрении некоторой координатной оси будет совершено [math]\log n[/math], а итоговая сложность составит [math]O(\log^{p} n)[/math].

Отдельно следует рассмотреть, что происходит, когда текущее дерево является одномерным (мы рассмотрели все координаты, кроме текущей):

  • Если рассматриваемый отрезок содержит больше одного элемента, разобьем его на две части и рекурсивно перейдем в ту, где находится необходимый элемент
  • Найдем первую координату, в которой рассматривается больше одного элемента. Обновим значение элемента массива с помощью уже вычисленных значений для разбитого надвое отрезка по этой координате.
  • Если мы рассматриваем область, состоящую из одного элемента, обновим значение массива.

Псевдокод:

update(newElem, x1, x2, ..., xP, x1Left, x1Right, x2Left, x2Right, ..., xPLeft, xPRight, leftBorder, rightBorder, vertex)
 if leftBorder != rightBorder
  med = (leftBorder + rightBorder) / 2
  if med >= newElem.x(P+1)
   update(newElem, x1, x2, ..., xP, x1Left, x1Right, x2Left, x2Right, ..., xPLeft, xPRight, leftBorder, med, vertex * 2 + 1)
  else
   update(newElem, x1, x2, ..., xP, x1Left, x1Right, x2Left, x2Right, ..., xPLeft, xPRight, med + 1, rightBorder, vertex * 2 + 2)
 if последняя координата
  for I = 1..n
   if xILeft != xIRigth
    t[x1][x2]...[xP][vertex] = t[x1][x2]...[xI * 2 + 1]...[vertex] [math]\times[/math] t[x1][x2]...[xI * 2 + 2]...[vertex]
    return
  t[x1][x2]...[xP][vertex] = newElem.value
 else
  if leftBorder != rightBorder
   update(newElem, x1, x2, ..., xP, vertex, x1Left, x1Rigth, x2Left, x2Right, ..., leftBorder, rightBorder, 0, m - 1, 0)

Построение

Построение многомерного дерева отрезков практически ничем не отличается от его обновления. Единственное различие — если рассматриваемый отрезок состоит из более чем одного элемента, то необходимо рекурсивно вызываться из обоих частей.

Псевдокод:

build(x1, x2, ..., xP, x1Left, x1Right, x2Left, x2Right, ..., xPLeft, xPRight, leftBorder, rightBorder, vertex)
 if leftBorder != rightBorder
  med = (leftBorder + rightBorder) / 2
  update(newElem, x1, x2, ..., xP, x1Left, x1Right, x2Left, x2Right, ..., xPLeft, xPRight, leftBorder, med, vertex * 2 + 1)
  update(newElem, x1, x2, ..., xP, x1Left, x1Right, x2Left, x2Right, ..., xPLeft, xPRight, med + 1, rightBorder, vertex * 2 + 2)
 if последняя координата
  for I = 1..n
   if xILeft != xIRigth
    t[x1][x2]...[xP][vertex] = t[x1][x2]...[xI * 2 + 1]...[vertex] [math]\times[/math] t[x1][x2]...[xI * 2 + 2]...[vertex]
    return
  t[x1][x2]...[xP][vertex] = data[x1Left][x2Left]...[xPLeft][vertex]
 else
  if leftBorder != rightBorder
   update(newElem, x1, x2, ..., xP, vertex, x1Left, x1Rigth, x2Left, x2Right, ..., leftBorder, rightBorder, 0, m - 1, 0)

Заметим, что построение дерева требует [math]O(n)[/math] времени, где [math]n[/math] — общее число элементов в массиве.

Источники

См. также