Структура данных для обрезки больших значений в массиве? - PullRequest
4 голосов
/ 23 января 2012

Мне нужна структура данных для следующих целей. Допустим, у меня есть массив a. Изначально все элементы установлены на ноль. Всякий раз, когда мне нужно обновить один из элементов с положительным значением new_value в позиции p, если исходное значение в позиции p old_value не равно нулю и больше, чем new_value, тогда мне нужно обновить все ненулевые элементы, начиная с позиции p вплоть до конца массива. Здесь обновление означает сброс значений с меньшим между старым значением в этой позиции и new_value.

Например, массив: [2, 0, 3, 0, 2, 1, 5, 0, 4, 0, 7]

Учитывая новое значение 4 для позиции 2 (начиная с 0), которое имеет старое значение 3, мне нужно обновить массив так:

[2, 0, 3, 0, 2, 1, 4, 0, 4, 0, 4]

Если новое значение в позиции 2 равно 1, то результирующий массив будет:

[2, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1]

Есть ли известная структура данных, которая может сделать это эффективно? Мне нужно много таких операций обновления.

Спасибо за ваши предложения.

Ответы [ 3 ]

1 голос
/ 24 января 2012

Я полагаю, что вы можете получить эту работу за амортизированное время O (log n) на доступ к элементу или изменение значения, используя модификацию Splay Tree. Идея этого подхода двояка. Во-первых, вместо того, чтобы хранить массив в виде массива, мы сохраняем его в виде пары массивов, содержащих исходные значения, и дерева сплайнов, где ключ каждого узла является индексом в массиве. Например, для массива из семи элементов установка может выглядеть следующим образом:

Array: 3 1 4 2 5 9 3
Tree:             3
                /   \
               1.    5
              / \.  / \
             0.  2 4.  6

Обратите внимание, что дерево содержит индексы в массиве, а не сами элементы массива. Если мы хотим найти значение по определенному индексу, мы просто выполняем поиск индекса по индексу Splay Tree, а затем возвращаем элемент массива в заданной позиции, что занимает время амортизации O (log n).

Операцию, которую вы хотите поддержать, изменяя все будущие значения, я назову операцией «потолок», поскольку она устанавливает потолок для всех значений после текущего. Один из способов думать об этом состоит в том, что с каждым элементом в массиве связан верхний предел (который изначально равен бесконечности), и тогда истинное значение элемента является минимумом его истинного значения и верхнего предела. Хитрость заключается в том, что с помощью Splay Tree мы можем отрегулировать потолок всех значений в определенной точке или за ее пределами за время амортизации O (log n). Для этого мы расширяем дерево отображения, сохраняя в каждом узле значение c, которое представляет верхний предел, налагаемый этим элементом вперед, а затем вносим соответствующие изменения в c по мере необходимости.

Например, предположим, что мы хотим наложить потолок от некоторого элемента вперед. Давайте предположим, что этот элемент уже находится в корне дерева. В этом случае мы просто устанавливаем его значение c в качестве нового потолка за время O (1). С этого момента, всякий раз, когда мы выполняем поиск некоторого значения, которое приходит к этому элементу или после него, мы можем записать потолок, когда спускаемся по дереву от корня к рассматриваемому элементу. В более общем смысле, когда мы выполняем поиск, каждый раз, когда мы следуем по правильной дочерней ссылке, мы отмечаем значение c этого узла. Как только мы попадаем в рассматриваемый элемент, мы знаем потолок этого элемента, потому что мы можем просто взять минимальный потолок узлов на пути от корня, правые дочерние элементы которого мы следовали. Таким образом, чтобы найти элемент в структуре, мы выполняем стандартный поиск в Splay Tree, отслеживая значение c, в котором мы находимся, затем выводим минимум значения массива origin и значения c.

Но для того, чтобы сделать эту работу, наш подход должен учитывать тот факт, что Splay Tree делает вращения. Другими словами, мы должны показать, как распространять значения c во время вращения. Предположим, что мы хотим сделать вращение, подобное этому:

    A.         B
   /.      ->.  \
  B.             A

В этом случае мы не изменяем никакие значения c, поскольку любое значение, найденное после A, все равно будет проходить через узел A. Тем не менее, если мы сделаем противоположное вращение и потянем A выше B, то мы установим значение c в A как минимум значения c в B и значения c в A, так как, если мы спустимся в левое поддерево A после выполнения вращения, нам нужно Потолок B во внимание. Это означает, что мы выполняем O (1) работу за оборот, а поскольку амортизированное число вращений, выполненных за одно расширение, составляет O (log n), мы выполняем амортизированную O (log n) работу за поиск.

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

Короче говоря, у нас есть O (log n), время поиска O (log n) (амортизировано).

Эта идея основана на обсуждении деревьев ссылок / срезов из оригинальной статьи Слеатора и Тарьяна "Саморегулирующиеся деревья бинарного поиска", в которой также представлено дерево сплайнов.

Надеюсь, это поможет!

1 голос
/ 25 января 2012

Моя первоначальная идея была чем-то похожа на ответ templatetypedef (кстати, +1), но вместо простого дерева сплайнов использовалось простое статическое двоичное дерево. Как и в этом ответе, «логический» массив L представлен фактическим массивом A, содержащим исходные значения и двоичное дерево T наложенных предельных значений. (В этом случае форма дерева значений потолка является статической, и, таким образом, нам не нужно отслеживать индексы элементов в дереве: индекс, соответствующий узлу, является просто его индекс обхода порядка.) Дерево может иметь любую форму, если оно имеет минимальную высоту; то есть, если L должен содержать n элементов и 2^(k-1) <= n < 2^k, тогда дерево должно иметь высоту k. Я бы предложил макет, в котором мы поместим элемент 2^(k-1) - 1 в корень дерева, сделаем его левое поддерево деревом perfect , содержащим L[0 .. 2^(k-1) - 2], и рекурсивно определим его правое поддерево для L[2^(k-1) .. n - 1] (то есть может быть пустым). Например, дерево из 12 элементов будет иметь следующие записи:

              7
       /-----/ \-----\
      3               11 
   /-/ \-\         /-/
  1       5       9   
 / \     / \     / \
0   2   4   6   8   10

(Обратите внимание, что эти числа не являются записями дерева - они просто указывают, какой позиции в массиве соответствуют записи дерева.) Это описание также дает алгоритм поиска записи в дереве, которая соответствует записи i из n: если i < 2^(k-1) - 1, найдите i -ю запись в идеальном левом поддереве, если i = 2^(k-1) - 1 - это корень, и в противном случае найдите i - (2^(k-1) - 1) -ю запись из n - (2^(k-1) - 1) в правом поддереве рекурсивно.

Мы инициализируем все записи дерева в бесконечность. Когда мы хотим наложить потолок c для записи i и позже, мы находим i -ую запись в дереве следующим образом:

  • Если рассматриваемый нами узел x является i -ым узлом или нам нужно спуститься в его поддерево left , мы обновим значение, хранящееся в узле x, до минимума c и текущее значение x.
  • Если нам нужно спуститься в правильное поддерево x и текущее значение, хранящееся в x, составляет самое большее c, нам не нужно обновлять дерево - оно уже принудительно, чтобы мы могли остановиться.
  • Если x является i узлом, мы можем остановиться.

Это все для наложения потолка. Обратите внимание, что A само по себе никогда не обновляется.

Если мы хотим найти i -ое значение L, то мы инициализируем локальную переменную c в бесконечность и находим i -ую запись в дереве в соответствии с этими правилами:

  • Если рассматриваемый нами узел x является i -ым узлом или нам нужно спуститься в его поддерево вправо , тогда установите c на минимум его текущего значения и значение хранится в x.
  • Если x является i -ым узлом, мы можем остановиться.

Теперь мы возвращаем минимум A[i] и c.

Обе эти операции O(log n) (фактическое время для каждой операции, не амортизируется). Для реализации обратите внимание, что вы можете использовать массив для хранения двоичного дерева.

0 голосов
/ 24 января 2012

Я думаю, декартово дерево может помочь вам в вашей проблеме.Единственное отличие от описанного в википедии состоит в том, что я советую вам строить максимальные кучи вместо минимальных кучи в каждом узле (т.е. изменить свойство так, чтобы значение каждого узла было не меньше, чем у обоих его дочерних элементов).Вам нужно перейти к правому потомку текущего узла и спуститься вниз по дереву, изменив все элементы со значениями, превышающими новое значение.Вы можете доказать, что таким образом вы будете проверять не более 3 * K элементов, где K - фактическое количество элементов, которые необходимо изменить.Еще одна полезная вещь: при выполнении операции, описанной вами, свойство кучи в каждой вершине будет сохраняться при изменении всех значений, превышающих новое значение.

Надеюсь, этот ответ поможет.

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