Сохранение ведра чисел в эффективной структуре данных

У меня есть ведра чисел, например. - от 1 до 4, от 5 до 15, от 16 до 21, от 22 до 34,.... У меня примерно 600 000 таких ведер. Диапазон чисел, попадающих в каждый из ковша, меняется. Мне нужно сохранить эти ведра в подходящей структуре данных, чтобы поиск числа был как можно быстрее.

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

Заранее спасибо

Ответ 1

Если ведра смежны и не пересекаются, как в вашем примере, вам нужно сохранить в векторе только левую границу каждого ведра (т.е. 1, 5, 16, 22) плюс, как последний элемент, первое число который не попадает ни в какое ведро (35). (Я предполагаю, конечно, что вы говорите о целых числах.)

Сохраните вектор. Вы можете искать ведро в O (log n), используя двоичный поиск. Для поиска, к какому ведеру принадлежит число x, просто перейдите для единственного индекса i, так что вектор [i] <= x < вектор [г + 1]. Если x строго меньше, чем вектор [0], или если он больше или равен последнему элементу вектора, то в нем нет ведра.

ИЗМЕНИТЬ. Вот что я имею в виду:

#include <stdio.h>

// ~ Binary search. Should be O(log n)
int findBucket(int aNumber, int *leftBounds, int left, int right)
{
    int middle;

    if(aNumber < leftBounds[left] || leftBounds[right] <= aNumber) // cannot find
        return -1;
    if(left + 1 == right) // found
        return left;

    middle = left + (right - left)/2;

    if( leftBounds[left] <= aNumber && aNumber < leftBounds[middle] )
        return findBucket(aNumber, leftBounds, left, middle);
    else
        return findBucket(aNumber, leftBounds, middle, right);
}


#define NBUCKETS 12
int main(void)
{
    int leftBounds[NBUCKETS+1] = {1, 4, 7, 15, 32, 36, 44, 55, 67, 68, 79, 99, 101};
    // The buckets are 1-3, 4-6, 7-14, 15-31, ...

    int aNumber;
    for(aNumber = -3; aNumber < 103; aNumber++)
    {
        int index = findBucket(aNumber, leftBounds, 0, NBUCKETS);
        if(index < 0)
            printf("%d: Bucket not found\n", aNumber);
        else
            printf("%d belongs to the bucket %d-%d\n", aNumber, leftBounds[index], leftBounds[index+1]-1);
    }   
    return 0;
}

Ответ 2

Возможно, вам понадобится какое-то сортированное дерево, например дерево B-Tree, B + Tree или дерево двоичного поиска.

Ответ 3

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

Предполагая, что ни один из диапазонов ковша не перекрывается, я думаю, что вы можете реализовать это в двоичном дереве поиска. Это сделало бы поиск возможным в O (logn) (whenere n = количество ковшей).

Было бы просто сделать это, просто определите левую ветвь меньше нижнего конца ведра, а правая ветвь - больше, чем правая. Итак, в вашем примере мы получим дерево вроде как:

    16-21
    /    \
  5-15  22-34
  /
1-4

Для поиска, скажем, 7, вы просто проверяете корень. Менее 16? Да, идите налево. Менее 5? Нет. Больше 15? Нет, все готово.

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

Ответ 4

Простым способом хранения и сортировки в С++ является использование пары отсортированных массивов, которые представляют нижнюю и верхнюю границы каждого ведра. Затем вы можете использовать int bucket_index= std::distance(lower_bounds.begin(), std::lower_bound(lower_bounds, value)), чтобы найти ведро, с которым будет соответствовать значение, и if (upper_bounds[bucket_index]>=value), bucket_index - это необходимое вам ведро.

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

Ответ 5

+1 к форме идеи бинарного поиска. Он прост и обеспечивает хорошую производительность для 600000 ковшей. При этом, если он недостаточно хорош, вы можете создать массив с MAX BUCKET VALUE - MIN BUCKET VALUE = RANGE, и каждый элемент в этом массиве ссылается на соответствующее ведро. Затем вы получаете доступ к гарантированному постоянному времени [O (1)] за счет использования огромного объема памяти.

Если A) вероятность доступа к ведрам неравномерна, и B) вы знали/могли выяснить, насколько вероятен доступ к определенному набору ковшей, вы могли бы объединить эти два подхода для создания своего рода кеша. Например, скажем, ведро {0, 3} было доступно все время, как было {7, 13}, тогда вы можете создать массив CACHE.,.

int cache_low_value = 0;

int cache_hi_value = 13;

CACHE [0] = BUCKET_1

CACHE [1] = BUCKET_1

...

CACHE [6] = BUCKET_2

CACHE [7] = BUCKET_3

CACHE [8] = BUCKET_3

...

CACHE [13] = BUCKET_3

., который позволит вам найти ведро в O (1) время, предполагая, что значение, которое вы пытаетесь связать с ведром, находится между cache_low_value и cache_hi_value (если Y <= cache_hi_value & Y >= cache_low_value; BUCKET = CACHE [Y]). С другой стороны, этот подход не будет использовать всю память на вашем компьютере; с другой стороны, это добавит эквивалент дополнительной операции или двух в ваш bsearch в случае, если вы не можете найти свою партию номер/ковш в кеше (так как вам нужно было проверить кеш в первую очередь).

Ответ 6

Позвольте мне посмотреть, смогу ли я подтвердить ваши требования. Это похоже на то, что, например, в день года, и хотите узнать, в каком месяце наступает этот день? Итак, учитывая год с 600 000 дней (интересная планета), вы хотите вернуть строку, которая является либо "Jan", "Feb", "Mar"... "Dec"?

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

Создайте структуру данных...

typedef struct {
  int DayOfYear    :20; // an bit-int donating some bits for other uses
  int MonthSS      :4;  // subscript to select months
  int Unused       :8;  // can be used to make MonthSS 12 bits
} BUCKET_LIST;

  char MonthStr[12] = "Jan","Feb","Mar"... "Dec";
.

Для инициализации используйте цикл for {} для установки BUCKET_LIST.MonthSS на один из 12 месяцев в MonthStr.

При извлечении выполните двоичный поиск по вектору BUCKET_LIST.DayOfYear(вам нужно будет написать тривиальную функцию сравнения для BUCKET_LIST.DayOfYear). Ваш результат может быть получен с помощью возврата из bsearch() в качестве индекса в MonthStr...

pBucket = (BUCKET_LIST *)bsearch( v_bucket_list);
MonthString = MonthStr[pBucket->MonthSS];  

Общий подход здесь состоит в том, чтобы иметь коллекции "указателей" к строкам, прикрепленным к 600 000 записей. Все указатели в ковше указывают на одну строку. Я использовал бит int в качестве индекса здесь, а не 600k 4 байтовых указателя, потому что он занимает меньше памяти (4 бита против 4 байта), а BUCKET_LIST сортирует и ищет как вид int.

Используя эту схему, вы будете использовать больше памяти или хранилища, чем хранение простого ключа int, получите ту же производительность, что и простой ключ int, и покончите со всеми проверками диапазона при поиске. IE: нет, если {} тестирование. Сохраните их, если {} s для инициализации структуры данных BUCKET_LIST, а затем забудьте о них при поиске.

Я рассматриваю этот метод как псевдонижение подстрочного индекса, так как он решает отношения "один-к-одному", преобразовывая индексы многих в индекс одного из них - очень эффективно, что я мог бы добавить.

Мое приложение должно было использовать массив многих UCHAR для индексации гораздо меньшего массива двойных поплавков. Уменьшения размера было достаточно, чтобы сохранить все данные с горячей точки в кеше L1 на процессоре. Производительность 3X только от этого небольшого изменения.