Структура данных для больших диапазонов последовательных целых чисел? - PullRequest
6 голосов
/ 04 октября 2011

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

Я почти уверен, что вторая операция решается тривиально при правильной реализации для первой операции.

Каждое целое число начинается в категории, поэтому я начал с набора сбалансированных BST. Перемещение поддерева из одного BST в другое (например, перемещение диапазона в другую категорию) имеет время выполнения, эквивалентное объединению двух BST, которое равно O (n1 * n2) [ 1 ].

Это слишком медленно (в python, и C не вариант), и я не мог найти способ воспользоваться преимуществами внутренней структуры моих данных для создания эффективной операции слияния BST.

Сейчас я смотрю на AVL, красно-черные и интервальные деревья, двоичные кучи и трэпы. Сравнение их свойств является подавляющим. Какую структуру я должен использовать?

Редактировать (разъяснение проблемы) : Я гибко отношусь к тому, как хранить эти значения и создавать свои структуры данных. Я несгибаем к тому, как я получаю свой вклад, который приходит из другого приложения и выглядит следующим образом: CATEGORY(cat3, I, J). Мое текущее решение создает дерево с узлом для каждого целого числа в диапазоне. Это слишком медленно для размера моего набора данных, поэтому я с радостью перепроектирую, если получу лучший метод.

Любой данный запрос может переместить любой возможный диапазон целых чисел в любую категорию. Другими словами, диапазоны перекрываются в смысле CATEGORY(cat1, 1, 10), за которым следует CATEGORY(cat3, 5, 15), но не перекрываются в том смысле, что каждое целое число будет находиться ровно в одной категории в любой момент времени.

Ответы [ 5 ]

2 голосов
/ 04 октября 2011

Насколько я понял вопрос у вас есть диапазон [A, B] и запросы вида -

  1. Назначить определенный диапазон категории
E.g.
R1 R2 C1
R3 R4 C2
  1. Запрос диапазона для общего количества элементов в определенных категориях. Например. найти количество категорий в R1 R4

Простая реализация, использующая словари, как указано выше, не будет работать, как я описываю этим примером -

предположим, что у нас есть диапазон [1000, 5000]

и мы назначаем следующее:

1 2 C1
2 3 C2
3 4 C3
......
4999 5000 C4999

Теперь мы выполняем следующее задание

1 5000 C5555

Это сделает обновление / изменение / удаление ранее назначенных диапазонов диапазонов O (N), где N - максимальный размер диапазона (B - A)

D ['category'] = set (of_all_the_ranges_you_have_in_category)

В этом случае необходимо удалить предыдущие диапазоны из соответствующих наборов в категориях C1, C2 ... C4999 для последнего присвоения (1 5000 C5555)

OR

1: {"stop": 5, "category": "C1"}, 6: {"стоп": 19, "категория": "C23"},

Здесь обновление категории для каждого начального значения (1,2,3,4 ..., 4999) потребуется для последнего назначения (1 5000 C5555)

Лучшим вариантом для обновления диапазонов в O (lg n) будут деревья сегментов. (http://en.wikipedia.org/wiki/Segment_tree)

В приведенном выше примере дерево сегментов будет выглядеть примерно так

                   1000:5000
                      |
             ---------------------
             |                   |
           1000:3000         3001:5000
            |                    |
    ----------------      --------------------
   |               |      |                  |
 1000:2000     2001:3000   3001:4000       4001:5000

............................................... .................. .................................................. ............. и т. д.

Конечные узлы будут иметь диапазоны (1: 2, 2: 3, ...)

Вы можете присвоить значение «категория» каждому узлу и задавать интервал, пересекающий дерево, делящий интервал соответствующим образом (например, для 2500–4500, делите на 2500: 3000 и 3001: 4500 и продолжайте в обоих направлениях до узлов с совпадающими диапазонами найдены).

Теперь интересно обновлять дочерние узлы, когда они вам нужны. Например, не продолжайте обновлять детей сразу же при выполнении заданий, таких как 1 5000 C5555. Эта вещь называется ленивым распространением, и вы можете узнать больше об этом здесь (http://www.spoj.pl/forum/viewtopic.php?f=27&t=8296).

Теперь для части запроса. Если количество категорий очень мало, вы можете вести таблицу частот на каждом узле и обновлять диапазоны, когда это необходимо, и лениво распространяться, когда это необходимо, в противном случае вам придется обходить все дерево от листа к узлу, счет и сложность станут равны O (п). * * 1 056

Я думаю, что лучшее решение для запросов может существовать. Это не приходит мне в голову.

UPDATE Давайте возьмем небольшой пример.

Диапазон [1,8]

Категории разрешены {C1, C2}

        1:8
     1:4         5:8
     1:2  3:4      5:6    7:8
 1:1 2:2 3:3 4:4  5:5 6:6 7:7 8:8

Каждый узел будет иметь 3 поля [category, category_counts [], children_update_required = false]

1 5 С1

Запрос будет разделен, и для категории узлов 1: 4 будет установлено значение C1, а для children_update_required будет установлено значение true, его дочерние элементы теперь не будут обновляться (запоминайте обновление только при необходимости или при отложенном распространении). Также категория узла 5: 5 будет установлена ​​в C1

3 4 C2

Запрос будет распространяться по дереву в направлении 3: 4 (и в процессе достижения категорий 3: 4, 1: 2 и 3: 4 будет обновляться до C1, 1: 4 для children_update_required будет установлено значение false, 1: 2 и 3: 4 для children_update_required будет установлено в значение true) и теперь будет обновлять категорию с 3: 4 до C2 в соответствии с текущими требованиями. Затем он установит значение children_update, равное 3: 4, для истины для будущего обновления своих дочерних элементов (что уже установлено в этом случае .. без вреда).

0 голосов
/ 04 октября 2011

Мы можем представить текущие состояния как что-то вроде:

0:cat1 200:cat2 500: cat0 700:cat6 800:cat1

Это означает, что 0-200 - это cat1, 200-500 - это cat2, и т. Д. Мы сохраняем значения в двоичном дереве поиска, используя начальный номер. Каждый узел также будет содержать словарь, отображающий категории для подсчета всех дочерних элементов этого узла.

Эти словари должны облегчать получение значений за O (log) время. Мы просто должны добавить правильные числа на путях к началу и концу рассматриваемой последовательности.

Что мы делаем, когда нам нужно установить последовательность X-Y в категорию Z?

  1. Определить текущую категорию Y O (logn)
  2. Удалите все значения между X -Y O (k), но поскольку затраты на вставку этих узлов более дороги, мы можем назвать это O (1) амортизированным.
  3. Вставить новый узел в X O (log n)
  4. Обновить словари подсчета категорий. Нам нужно только обновить O (log n) родителей затронутых разделов, так что это O (log n)

В целом это время O (log n).

0 голосов
/ 04 октября 2011

Допущения:

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

Представляет диапазоны с помощью (start, end, category) кортежей.Диапазоны не пересекаются, поэтому вы можете построить из них дерево.Это намного более экономично, чем дерево отдельных целых чисел.Чтобы упорядочить диапазоны (то есть узлы), вы можете просто использовать начальное значение, поскольку оно уникально и не может принадлежать другому диапазону.

Если вам нужно переместить диапазон [a, b] в другую категорию, вы должны:

Сканирование вашего дерева и обновление каждого узла / диапазона, который полностью включен в диапазон [a, b].Это простой обход в глубину.Во время обхода

  • Если current_node.start < a or current_node.start > b, остановить поиск.
  • Если current_node.start >= a and current_node.end > b, вам нужно разделить current_node на две части;[current_node.start, b] будет принадлежать новой категории, остальные будут исходной категории.
  • Если current_node.start < a and current_node.end <= b, вы делите наоборот.

Поиск по дереву является логарифмическим, а разбиения узлов - O (1).

Если ваше деревокогда он становится слишком фрагментированным, вы можете рассмотреть возможность объединения узлов, имеющих соседние диапазоны.Это всегда будет родитель и потомок, являющиеся результатом вставки или разделения;проверки и объединения, кажется, всегда O (1).

0 голосов
/ 04 октября 2011

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

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

            (all)
     0-3      4-7
   0-1 2-3  4-5   6-7
 0 1  2  3  4  5  6   7

Учитывая число и уровень, я могу узнать смещение в массиве для этого уровня, просто сдвинув число вправо на величину в зависимости отуровень.

В каждом элементе я могу поставить маркер, который либо говорит «смешанный», либо дает категорию для каждого элемента в или под этим узлом дерева.Я могу определить, в какой категории находится узел, следуя по пути вниз от корня дерева к листу, останавливаясь, как только я вижу узел, который не говорит «смешанный».Я могу изменить категорию для интервала чисел во времени lg n, потому что у меня есть максимум два узла на каждом уровне для представления категории: если бы у меня было три, у двух из них был бы один и тот же родитель, и я мог бы объединить их.Возможно, вам придется немного поиграть с краями, чтобы получить правильные соседние диапазоны, но я думаю, что это сработает в lg n раз.

0 голосов
/ 04 октября 2011

Вы можете иметь простой словарь Python следующей формы

1 : { "stop" : 5, "category" : "C1"},
6 : { "stop" : 19, "category" : "C23"},
etc

Ключи здесь - начало вашего диапазона, а значения содержат конец диапазона и категорию, в которую входит этот диапазон.

Поскольку словари имеют постоянное время для доступа к элементам, вы можете написать некоторый код, который легко и эффективно перемещает диапазон в другую категорию: в худшем случае вам придется как-то реструктурировать свой словарь, если ваш диапазон разбиваетпредыдущие диапазоны в два или более.Например, если вы хотите присвоить диапазон (4, 8) другой категории, вы получите:

1 : { "stop" : 3, "category" : "C1"},
4 : { "stop" : 8, "category" : "new category"},
9 : { "stop" : 19, "category" : "C23"},
etc

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

РЕДАКТИРОВАТЬ: Чтобы успешно найти самый низкий (самый высокий) ключ, чтобы начать выполнять вычисления / изменения, вам также нужен простой список Python со всеми отсортированными ключами,и модуль деления пополам.Это поможет найти индекс в списке, чтобы «поместить» начало диапазона в O (logn) время, тогда все можно сделать за постоянное время, кроме O (n) времени, необходимого для вставки нового ключа всписок с bisect.insort_left.

...