Структура данных для совпадающих наборов

Согласно вашему коду:

String[] name = {"tom", "dick", "harry"};
for(int i = 0; i<=name.length; i++) {
  System.out.print(name[i] +'\n');
}

Если вы проверите System.out.print (name.length),

, вы получите 3;

, что означает, что длина вашего имени равна 3

, ваш цикл работает от 0 до 3, который должен работать либо от «0 до 2», либо от «1 до 3»

Ответ

String[] name = {"tom", "dick", "harry"};
for(int i = 0; i<name.length; i++) {
  System.out.print(name[i] +'\n');
}
14
задан Daniel 3 August 2010 в 21:46
поделиться

13 ответов

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

Например, если бы у вас были наборы A = {2, 3}, B = {4} и C = {1, 3}, у вас было бы следующее дерево

                      _NOT_HAVE_[1]___HAVE____
                      |                      |            
                _____[2]_____          _____[2]_____
                |           |          |           |
             __[3]__     __[3]__    __[3]__     __[3]__
             |     |     |     |    |     |     |     |
            [4]   [4]   [4]   [4]  [4]   [4]   [4]   [4]
            / \   / \   / \   / \  / \   / \   / \   / \
           .   B .   B .   B .   B    B C   B A   A A   A
                                            C     B C   B
                                                        C

После создания дерева вы просто нужно сделать 50 сравнений --- или сколько предметов может быть в наборе.

Например, для {1, 4} вы переходите по дереву: вправо (в наборе 1), влево (нет 2), влево, вправо, и вы получаете [B], что означает только набор B включен в {1, 4}.

Это в основном называется «диаграммой двоичных решений». Если вас оскорбляет избыточность узлов (как и должно быть, потому что 2 ^ 50 - это много узлов ...), тогда вам следует рассмотреть сокращенную форму, которая называется «сокращенной, упорядоченной двоичной диаграммой решений» и - это обычно используемая структура данных.В этой версии узлы объединяются, когда они избыточны, и у вас больше не двоичное дерево, а ориентированный ациклический граф.

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

8
ответ дан 1 December 2019 в 14:21
поделиться

Вы можете использовать инвертированный индекс ваших элементов данных. Для вашего примера

1 {1, 2, 4, 7, 8, 12, 18, 23, 29}
2 {3, 4, 6, 7, 15, 23, 34, 38}
3 {4, 7, 12, 18}
4 {1, 4, 7, 12, 13, 14, 15, 16, 17, 18}
5 {2, 4, 6, 7, 13, 15}

инвертированный индекс будет

1: {1, 4}
2: {1, 5}
3: {2}
4: {1, 2, 3, 4, 5}
5: {}
6: {2, 5}
...

Итак, для любого конкретного набора {x_0, x_1, ..., x_i} вам нужно пересечь наборы для x_0, x_1 и других. Например, для набора {2,3,4} вам нужно пересечь {1,5} с {2} и с {1,2,3, 4,5} . Поскольку вы можете отсортировать все свои наборы в инвертированном индексе, вы можете пересекать наборы в минимальной длине наборов, которые должны быть пересечены.

Это может быть проблемой, если у вас есть очень «популярные» элементы (как 4 в нашем примере) с огромным набором индексов.

Несколько слов о пересечении. Вы можете использовать отсортированные списки в инвертированном индексе и пересекать множества попарно (в порядке увеличения длины). Или, поскольку у вас не более 50 КБ элементов, вы можете использовать сжатые наборы битов (около 6 КБ для каждого числа, меньше для разреженных наборов битов, около 50 чисел, не так жадно) и пересекать наборы битов поразрядно. Думаю, для редких наборов битов это будет эффективно.

1
ответ дан 1 December 2019 в 14:21
поделиться

Возможный способ разделить список битовых карт - создать массив (Compiled Nibble Indicators)

Допустим, одна из ваших 64-битовых карт имеет установленные биты с 0 по 8.
В шестнадцатеричном формате это выглядит как 0x00000000000000001F

Теперь давайте преобразуем это в более простое и меньшее представление. Каждый 4-битный полубайт либо имеет хотя бы один установленный бит, либо нет. Если да, то мы представляем его как 1, если нет, то как 0.

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

Скомпилируйте каждую из ваших битовых карт в компактный CNI. Добавьте ее в нужный список, пока не будут составлены все списки.

Затем возьмите иглу. Скомпилируйте ее в форму CNI. Используйте это значение для подписи к главе списка. Все битовые изображения в этом списке имеют вероятность совпадения. Все растровые изображения в других списках не могут совпадать.

Это способ разделить их.

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

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

Другой техникой может быть просто компиляция части 64-битной битовой карты, так что у вас будет меньше голов. Анализ ваших шаблонов должен дать вам представление о том, какие ниблы наиболее эффективны при разбиении.

Удачи вам, и, пожалуйста, дайте нам знать, что вы в итоге сделаете.

Зло.

1
ответ дан 1 December 2019 в 14:21
поделиться

Поскольку числа меньше 50, вы могли бы создать хэш один-к-одному, используя 64-битное целое число, а затем использовать побитовые операции для проверки наборов за время O(1). Создание хэша также будет O(1). Я думаю, что подойдет либо XOR с последующей проверкой на нуль, либо AND с последующей проверкой на равенство. (Если я правильно понял задачу)

.
0
ответ дан 1 December 2019 в 14:21
поделиться

Сколько элементов данных у вас есть? Неужели все они уникальны? Не могли бы вы кэшировать популярные элементы данных или использовать сегментную / основную сортировку перед запуском, чтобы сгруппировать повторяющиеся элементы вместе?

Вот подход к индексации:

1) Разделите 50-битное поле, например, на 10 5-битных подполей. Если у вас действительно есть 50K наборов, то 3 17-битных фрагмента могут быть ближе к цели.

2) Для каждого набора выберите одно подполе. Хороший выбор - это подполе, где в этом наборе установлено наибольшее количество битов, причем связи разорваны почти произвольно - например, используйте крайнее левое такое подполе.

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

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

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

Дополнение: Сколько таблиц потребуется, чтобы гарантировать, что любые 2 бита всегда попадут в одну и ту же таблицу? Если вы посмотрите на комбинаторное определение в http://en.wikipedia.org/wiki/Projective_plane , вы увидите, что существует способ извлекать наборы из 7 бит из 57 (= 1 + 7 + 49) битами 57 различными способами, так что для любых двух битов по крайней мере один набор содержит их оба. Наверное, не очень полезно, но все же ответ.

0
ответ дан 1 December 2019 в 14:21
поделиться

Поместите свои наборы в массив (не связанный список) и СОРТИРУЙТЕ ИХ. Критерии сортировки могут быть либо 1) количеством элементов в наборе (количество 1-битов в представлении набора), либо 2) наименьшим элементом в наборе. Например, пусть A = {7, 10, 16} и B = {11, 17} . Тогда B по критерию 1) и A по критерию 2). Сортировка выполняется за O (n log n), но я предполагаю, что вы можете позволить себе некоторое время предварительной обработки, т.е. что структура поиска статична.

Когда поступает новый элемент данных, вы можете использовать двоичный поиск (логарифмическое время), чтобы найти начальный кандидат, установленный в массиве. Затем вы производите линейный поиск в массиве и проверяете элемент данных на соответствие набору в массиве, пока элемент данных не станет «больше», чем набор.

Вы должны выбрать критерий сортировки на основе разброса ваших наборов. Если все наборы имеют нулевой нижний элемент, вам не следует выбирать критерий 2). И наоборот, если распределение множеств множеств неравномерно, не следует выбирать критерий 1).

Еще одним, более надежным критерием сортировки могло бы быть вычисление диапазона элементов в каждом наборе и их сортировка в соответствии с этим. Например, самый низкий элемент в наборе A равен 7, а самый высокий - 16, поэтому вы должны кодировать его диапазон как 0x1007 ; аналогично диапазон B будет 0x110B . Отсортируйте наборы в соответствии с «кодом диапазона» и снова используйте двоичный поиск, чтобы найти все наборы с тем же «кодом диапазона», что и ваш элемент данных.

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

0
ответ дан 1 December 2019 в 14:21
поделиться

Это не настоящий ответ, скорее наблюдение: эта проблема выглядит так, как будто ее можно эффективно распараллелить или даже распределить, что, по крайней мере, сократит время работы до O (n / количество ядер)

0
ответ дан 1 December 2019 в 14:21
поделиться

Вы можете построить обратный индекс списков "стога сена", содержащих каждый элемент:

std::set<int> needle;  // {4, 7, 12, 18}
std::vector<std::set<int>> haystacks;
// A list of your each of your data sets:
// 1 {1, 2, 4, 7, 8, 12, 18, 23, 29}
// 2 {3, 4, 6, 7, 15, 23, 34, 38}
// 3 {4, 7, 12, 18}
// 4 {1, 4, 7, 12, 13, 14, 15, 16, 17, 18}
// 5 {2, 4, 6, 7, 13, 

std::hash_map[int, set<int>>  element_haystacks;
// element_haystacks maps each integer to the sets that contain it
// (the key is the integers from the haystacks sets, and 
// the set values are the index into the 'haystacks' vector):
// 1 -> {1, 4}  Element 1 is in sets 1 and 4.
// 2 -> {1, 5}  Element 2 is in sets 2 and 4.
// 3 -> {2}  Element 3 is in set 3.
// 4 -> {1, 2, 3, 4, 5}  Element 4 is in sets 1 through 5.  
std::set<int> answer_sets;  // The list of haystack sets that contain your set.
for (set<int>::const_iterator it = needle.begin(); it != needle.end(); ++it) {
  const std::set<int> &new_answer = element_haystacks[i];
  std::set<int> existing_answer;
  std::swap(existing_answer, answer_sets);
  // Remove all answers that don't occur in the new element list.
  std::set_intersection(existing_answer.begin(), existing_answer.end(),
                        new_answer.begin(), new_answer.end(),
                        inserter(answer_sets, answer_sets.begin()));
  if (answer_sets.empty()) break;  // No matches :(
}

// answer_sets now lists the haystack_ids that include all your needle elements.
for (int i = 0; i < answer_sets.size(); ++i) {
  cout << "set: " << element_haystacks[answer_sets[i]];
}

Если я не ошибаюсь, это будет иметь максимальное время выполнения O (k * m) , где - среднее количество наборов, которым принадлежит целое число, а m - средний размер набора игл (<50). К сожалению, это будет иметь значительные накладные расходы на память из-за построения обратного отображения ( element_haystacks ).

Я уверен, что вы могли бы немного улучшить это, если бы сохранили отсортированные векторов вместо наборов и element_haystacks могли бы быть вектором из 50 элементов вместо hash_map .

0
ответ дан 1 December 2019 в 14:21
поделиться

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

Может быть, вы можете разделить элементы данных так, чтобы у вас были все те, где 1 - наименьший элемент в одной группе, и все те, где 2 - наименьший элемент в другой группе, и так далее.

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

Или, возможно, сгруппируйте их в 50 групп, указав «этот элемент данных содержит N» для N = 1..50.

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

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

1
ответ дан 1 December 2019 в 14:21
поделиться

Индексы наборов, соответствующих критерию поиска, напоминают сами наборы. Вместо того, чтобы иметь уникальные индексы меньше 50, у нас есть уникальные индексы меньше 50000.Поскольку вы не возражаете против использования небольшого количества памяти, вы можете предварительно вычислить соответствующие наборы в массиве из 50 элементов из 50000-битных целых чисел. Затем вы индексируете предварительно вычисленные совпадения и в основном просто выполняете свой ((set & data) == set), но на 50000-битных числах, которые представляют совпадающие наборы. Вот что я имею в виду.

#include <iostream>

enum
{
    max_sets = 50000, // should be >= 64
    num_boxes = max_sets / 64 + 1,
    max_entry = 50
};

uint64_t sets_containing[max_entry][num_boxes];

#define _(x) (uint64_t(1) << x)

uint64_t sets[] =
{
    _(1) | _(2) | _(4) | _(7) | _(8) | _(12) | _(18) | _(23) | _(29),
    _(3) | _(4) | _(6) | _(7) | _(15) | _(23) | _(34) | _(38),
    _(4) | _(7) | _(12) | _(18),
    _(1) | _(4) | _(7) | _(12) | _(13) | _(14) | _(15) | _(16) | _(17) | _(18),
    _(2) | _(4) | _(6) | _(7) | _(13) | _(15),
    0,
};

void big_and_equals(uint64_t lhs[num_boxes], uint64_t rhs[num_boxes])
{
    static int comparison_counter = 0;
    for (int i = 0; i < num_boxes; ++i, ++comparison_counter)
    {
        lhs[i] &= rhs[i];
    }
    std::cout
        << "performed "
        << comparison_counter
        << " comparisons"
        << std::endl;
}

int main()
{
    // Precompute matches
    memset(sets_containing, 0, sizeof(uint64_t) * max_entry * num_boxes);

    int set_number = 0;
    for (uint64_t* p = &sets[0]; *p; ++p, ++set_number)
    {
        int entry = 0;
        for (uint64_t set = *p; set; set >>= 1, ++entry)
        {
            if (set & 1)
            {
                std::cout
                    << "sets_containing["
                    << entry
                    << "]["
                    << (set_number / 64)
                    << "] gets bit "
                    << set_number % 64
                    << std::endl;

                uint64_t& flag_location =
                    sets_containing[entry][set_number / 64];

                flag_location |= _(set_number % 64);
            }
        }
    }

    // Perform search for a key
    int key[] = {4, 7, 12, 18};
    uint64_t answer[num_boxes];
    memset(answer, 0xff, sizeof(uint64_t) * num_boxes);

    for (int i = 0; i < sizeof(key) / sizeof(key[0]); ++i)
    {
        big_and_equals(answer, sets_containing[key[i]]);
    }

    // Display the matches
    for (int set_number = 0; set_number < max_sets; ++set_number)
    {
        if (answer[set_number / 64] & _(set_number % 64))
        {
            std::cout
                << "set "
                << set_number
                << " matches"
                << std::endl;
        }
    }

    return 0;
}

Выполнение этой программы дает:

sets_containing[1][0] gets bit 0
sets_containing[2][0] gets bit 0
sets_containing[4][0] gets bit 0
sets_containing[7][0] gets bit 0
sets_containing[8][0] gets bit 0
sets_containing[12][0] gets bit 0
sets_containing[18][0] gets bit 0
sets_containing[23][0] gets bit 0
sets_containing[29][0] gets bit 0
sets_containing[3][0] gets bit 1
sets_containing[4][0] gets bit 1
sets_containing[6][0] gets bit 1
sets_containing[7][0] gets bit 1
sets_containing[15][0] gets bit 1
sets_containing[23][0] gets bit 1
sets_containing[34][0] gets bit 1
sets_containing[38][0] gets bit 1
sets_containing[4][0] gets bit 2
sets_containing[7][0] gets bit 2
sets_containing[12][0] gets bit 2
sets_containing[18][0] gets bit 2
sets_containing[1][0] gets bit 3
sets_containing[4][0] gets bit 3
sets_containing[7][0] gets bit 3
sets_containing[12][0] gets bit 3
sets_containing[13][0] gets bit 3
sets_containing[14][0] gets bit 3
sets_containing[15][0] gets bit 3
sets_containing[16][0] gets bit 3
sets_containing[17][0] gets bit 3
sets_containing[18][0] gets bit 3
sets_containing[2][0] gets bit 4
sets_containing[4][0] gets bit 4
sets_containing[6][0] gets bit 4
sets_containing[7][0] gets bit 4
sets_containing[13][0] gets bit 4
sets_containing[15][0] gets bit 4
performed 782 comparisons
performed 1564 comparisons
performed 2346 comparisons
performed 3128 comparisons
set 0 matches
set 2 matches
set 3 matches

3128 сравнений uint64_t лучше 50000 сравнений, так что вы выигрываете. Даже в худшем случае, когда это будет ключ, содержащий все 50 элементов, вам нужно будет выполнить только сравнение num_boxes * max_entry, которое в данном случае равно 39100. Все же лучше, чем 50000.

1
ответ дан 1 December 2019 в 14:21
поделиться

Я не могу этого доказать, но вполне уверен, что не существует решения, которое могло бы легко превзойти границу O (n). Ваша проблема «слишком общая»: каждый набор имеет m = 50 свойств (а именно, свойство k состоит в том, что оно содержит число k), и дело в том, что все эти свойства независимы друг от друга . Нет никаких умных комбинаций свойств, которые могут предсказать наличие других свойств. Сортировка не работает, потому что проблема очень симметрична, любая перестановка ваших 50 чисел приведет к той же проблеме, но испортит любой порядок. Если ваш ввод не имеет скрытой структуры , вам не повезло.

Однако есть место для компромисса между скоростью и памятью. А именно, вы можете предварительно вычислить ответы на небольшие запросы . Пусть Q будет набором запросов, а надмножества (Q) будет набором наборов, которые содержат Q , то есть решение вашей проблемы. Тогда ваша проблема имеет следующее ключевое свойство

Q ⊆ P  =>  supersets(Q) ⊇ supersets(P)

Другими словами, результаты для P = {1,3,4} являются частью результатов для Q = {1,3 } .

Теперь предварительно вычислите все ответы на небольшие запросы. Для демонстрации возьмем все запросы размера <= 3. Вы получите таблицу

supersets({1})
supersets({2})
...
supersets({50})
supersets({1,2})
supersets({2,3})
...
supersets({1,2,3})
supersets({1,2,4})
...

supersets({48,49,50})

с O (m ^ 3) записями. Чтобы вычислить, скажем, надмножеств ({1,2,3,4}) , вы ищите надмножество ({1,2,3}) и запускаете свой линейный алгоритм на этом коллекция.Дело в том, что в среднем надмножество ({1,2,3}) не будет содержать полных n = 50 000 элементов, а будет содержать только часть n / 2 ^ 3 = 6250 из них, что дает 8-кратное увеличение скорости.

(Это обобщение метода «обратного индекса», предложенного в других ответах.)

Однако в зависимости от вашего набора данных использование памяти будет довольно ужасным. Но вы могли бы опустить некоторые строки или ускорить алгоритм, заметив, что такой запрос, как {1,2,3,4} , может быть вычислен из нескольких различных предварительно вычисленных ответов, таких как надмножества ( {1,2,3}) и надмножества ({1,2,4}) , и вы будете использовать наименьшее из них.

2
ответ дан 1 December 2019 в 14:21
поделиться

Другая идея состоит в том, чтобы полностью предварительно охотиться на ваших слонов.

Настройка

Создание 64-битного массива X 50 000 битов элементов.

Проанализируйте свой поисковый набор и установите соответствующие биты в каждой строке.

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

Поиск

Загрузить элемент битового массива в память.

Создайте массив битовых карт, 1 X 50000. Установите все значения в 1. Это битовый массив поиска.

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

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

Reconstruct

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

0
ответ дан 1 December 2019 в 14:21
поделиться

Я удивлен, что никто не упомянул, что STL содержит алгоритм для обработки таких вещей за вас. Следовательно, вы должны использовать includes. По описанию он выполняет максимум 2*(N+M)-1 сравнений для наихудшего случая производительности O(M+N).

Следовательно:

bool isContained = includes( myVector.begin(), myVector.end(), another.begin(), another.end() );

если вам нужно время O( log N), то я вынужден уступить другим респондентам.

0
ответ дан 1 December 2019 в 14:21
поделиться
Другие вопросы по тегам:

Похожие вопросы: