ищет эффективную структуру данных для быстрого поиска - PullRequest
1 голос
/ 22 октября 2009

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

void func(double value1, double value2, double value3)
{

       fooArr[1000];

       for(int i=0;i<1000; ++i) 
       {
                   //they are all numeric! ranges are < 1000
                  if(fooArr[i].a== value1
                       && fooArr[i].b >= value2;
                       && fooArr[i].c <= value2; //yes again value2  
                       && fooArr[i].d <= value3; 
                   )
                   {
                            /* yay found now do something!*/
                    }
       } 
}

Пространство не так уж важно!

ИЗМЕНЕНО по ЗАПРОСУ

Ответы [ 11 ]

4 голосов
/ 22 октября 2009

Если пространство не так уж важно, проще всего создать хеш на основе «a». В зависимости от того, сколько конфликтов вы получаете на «a», может иметь смысл сделать так, чтобы каждый узел в хеш-таблице указывал двоичное дерево, основанное на «b» Если b имеет много конфликтов, то же самое проделайте для c.

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

3 голосов
/ 23 октября 2009

Сначала отсортируйте список по возрастанию a и уменьшению b. Затем создайте индекс для a (значения являются целыми числами от 0 до 999. Итак, мы получили

int a_index[1001];  // contains starting subscript for each value
a_index[1000] = 1000;

for (i = a_index[value1]; i < a_index[value1 + 1] && fooArr[i].b >= value2; ++i)
{
   if (fooArr[i].c <= value2 && fooArr[i].d <= value3) /* do stuff */
}

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

1 голос
/ 23 октября 2009

Ну, поехали.

Прежде всего, оператор == требует подхода "голубиная дыра". Поскольку мы говорим о значениях int в диапазоне [0,1000], простая таблица - это хорошо.

std::vector<Bucket1> myTable(1001, /*MAGIC_1*/); // suspense

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

Теперь о новых вещах.

 && fooArr[i].b >= value2
 && fooArr[i].c <= value2 //yes again value2
 && fooArr[i].d <= value3

Использование value2 сложно, но вы сказали, что вам не безразлично место;)?

 typedef std::vector<Bucket2> Bucket1;
 /*MAGIC_1*/ <-- Bucket1(1001, /*MAGIC_2*/) // suspense ?

Экземпляр BucketA будет иметь в своей i-й позиции все экземпляры YourObject, для которых yourObject.c <= i <= yourObject.b

А теперь такой же подход с d.

 typedef std::vector< std::vector<YourObject*> > Bucket2;
 /*MAGIC_2*/ <-- Bucket2(1001)

Идея состоит в том, что std::vector<YourObject*> в индексе ih содержит указатель на все экземпляры YourObject, для которых yourObject.d <= i

В общем и целом!

class Collection:
{
public:
  Collection(size_t aMaxValue, size_t bMaxValue, size_t dMaxValue);
    // prefer to use unsigned type for unsigned values

  void Add(const YourObject& i);

  // Pred is a unary operator taking a YourObject& and returning void
  template <class Pred>
  void Apply(int value1, int value2, int value3, Pred pred);

  // Pred is a unary operator taking a const YourObject& and returning void
  template <class Pred>
  void Apply(int value1, int value2, int value3, Pred pred) const;

private:
  // List behaves nicely with removal,
  // if you don't plan to remove, use a vector
  // and store the position within the vector
  // (NOT an iterator because of reallocations)
  typedef std::list<YourObject> value_list;

  typedef std::vector<value_list::iterator> iterator_vector;
  typedef std::vector<iterator_vector> bc_buckets;
  typedef std::vector<bc_buckets> a_buckets;
  typedef std::vector<a_buckets> buckets_t;

  value_list m_values;
  buckets_t m_buckets;
}; // class Collection

Collection::Collection(size_t aMaxValue, size_t bMaxValue, size_t dMaxValue) :
  m_values(),
  m_buckets(aMaxValue+1,
            a_buckets(bMaxValue+1, bc_buckets(dMaxValue+1))
           )
  )
{
}

void Collection::Add(const YourObject& object)
{
  value_list::iterator iter = m_values.insert(m_values.end(), object);

  a_buckets& a_bucket = m_buckets[object.a];
  for (int i = object.c; i <= object.b; ++i)
  {
    bc_buckets& bc_bucket = a_bucket[i];
    for (int j = 0; j <= object.d; ++j)
    {
      bc_bucket[j].push_back(index);
    }
  }
} // Collection::Add

template <class Pred>
void Collection::Apply(int value1, int value2, int value3, Pred pred)
{
  index_vector const& indexes = m_buckets[value1][value2][value3];
  BOOST_FOREACH(value_list::iterator it, indexes)
  {
    pred(*it);
  }
} // Collection::Apply<Pred>

template <class Pred>
void Collection::Apply(int value1, int value2, int value3, Pred pred) const
{
  index_vector const& indexes = m_buckets[value1][value2][value3];

  // Promotion from value_list::iterator to value_list::const_iterator is ok
  // The reverse is not, which is why we keep iterators
  BOOST_FOREACH(value_list::const_iterator it, indexes)
  {
    pred(*it);
  }
} // Collection::Apply<Pred>

Таким образом, добавление и удаление элементов в этих коллекциях будет стоить.

Кроме того, у вас есть (aMaxValue + 1) * (bMaxValue + 1) * (dMaxValue + 1) std::vector<value_list::iterator>, что очень много.

Однако сложность Collection::Apply примерно равна k приложениям Pred, где k - количество элементов, соответствующих параметрам.

Я ищу там обзор, но не уверен, что все индексы были в порядке oO

1 голос
/ 23 октября 2009

Я думаю, что было бы целесообразно использовать kd-tree. Если с a не так много конфликтов, то хеширование / индексирование a может решить вашу проблему.

В любом случае, если это не сработает, я предлагаю использовать kd-tree.

Сначала создайте таблицу из нескольких kd-деревьев. Индексируйте их с a.

Затем реализовать дерево kd для каждого значения a с 3-мя измерениями в направлениях b, c, d.

Затем при поиске - сначала индексировать соответствующее kd-дерево с помощью a, а затем выполнять поиск из kd-дерева с вашими пределами. В основном вы будете выполнять поиск по диапазону.

Kd-дерево

Вы получите ответ в O(L^(2/3)+m), где L - количество элементов в соответствующем kd-дереве, а m - количество совпадающих точек.

Что-то лучшее, что я нашел, это Range Tree . Это может быть то, что вы ищете. Это быстро. Он ответит на ваш запрос в O(log^3(L)+m). (К сожалению, не очень много знаю о Range Tree.)

1 голос
/ 23 октября 2009

Исходя из того, что вы сказали (и в вопросе, и в комментариях), есть только очень несколько значений для a (что-то вроде 10).

В таком случае я бы построил индекс на значениях a, где каждый из них указывает прямо на все элементы в fooArr с этим значением a:

std::vector<std::vector<foo *> > index(num_a_values);

for (int i=0; i<1000; i++)
    index[fooArr[i].a].push_back(&fooArr[i]);

Затем, когда вы получаете значение для поиска элемента, вы сразу переходите к тем, для которых fooArr[i].a==value1:

std::vector<foo *> const &values = index[value1];
for (int i=0; i<values.size(); i++) {
    if (value2 <= values[i]->b
        && value2 >= values[i]->c
        && value3 >= values[i]->d) {
            // yay, found something
        }
}

Таким образом, вместо того, чтобы каждый раз просматривать 1000 элементов в fooArray, вы каждый раз просматриваете в среднем 100 элементов. Если вам нужна еще большая скорость, следующим шагом будет сортировка элементов в каждом векторе индекса на основе значения b. Это позволит вам найти нижнюю границу для value2, используя бинарный поиск вместо линейного поиска, сократив ~ 50 сравнений до ~ 10. Поскольку вы отсортировали его по b, с этого момента вам не нужно сравнивать value2 с b - вы точно знаете, где находятся остальные числа, удовлетворяющие неравенству, поэтому у вас есть только сравнить с c и d.

Вы могли бы также рассмотреть другой подход, основанный на ограниченном диапазоне чисел: от 0 до 1000 могут быть представлены в 10 битах. С помощью некоторого бит-тиддлинга вы можете объединить три поля в одно 32-битное число, что позволит компилятору сравнивать все три сразу, а не в трех отдельных операциях. Понять это правильно немного сложно, но если вы сделаете это, это может примерно утроить скорость.

1 голос
/ 23 октября 2009

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

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

0 голосов
/ 23 октября 2009

Как сказал pmg, идея состоит в том, чтобы исключить как можно больше сравнений. Очевидно, у вас не будет 4000 сравнений. Это потребовало бы, чтобы все 1000 элементов прошли первый тест, который затем был бы избыточным. Очевидно, есть только 10 значений a, следовательно, 10% проходит эту проверку. Итак, вы бы сделали 1000 + 100 +? +? чеки. Давайте предположим, что + 50 + 25, в сумме 1175.

Вам нужно знать, как распределяются a, b, c, d и value1, 2 и 3, чтобы точно решить, что будет быстрее. Мы только знаем, что a может иметь 10 значений, и мы предполагаем, что value1 имеет тот же домен. В этом случае биннинг на a может уменьшить его до операции O (1), чтобы получить правильный бин, плюс те же 175 проверок в дальнейшем. Но если b, c и value2 фактически образуют 50 сегментов, вы можете снова найти нужный сегмент в O (1). Тем не менее, каждое ведро теперь будет содержать в среднем 20 элементов, поэтому вам потребуется всего 35 тестов (снижение на 80%). Таким образом, распределение данных имеет значение здесь. Как только вы поймете свои данные, алгоритм станет понятен.

0 голосов
/ 23 октября 2009

Сначала для каждого a сделать разные таблицы ...

сделать табель num для чисел с одинаковыми a.

сделать 2 табличные таблицы по 1000 строк в каждой.

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

Например, допустим, у вас есть значения в массиве (игнорируя a, потому что у нас есть таблица для каждого значения a)

b = 96  46  47  27  40  82   9  67   1  15
c = 76  23  91  18  24  20  15  43  17  10
d = 44  30  61  33  21  52  36  70  98  16

тогда значения таблицы индекса для строки 50, 20:

idx[a].bc[50] = 0000010100
idx[a].d[50]  = 1101101001
idx[a].bc[20] = 0001010000
idx[a].d[20]  = 0000000001

так скажем, вы делаете func (a, 20, 50). Затем, чтобы узнать, какие номера участвуют, вы делаете:

g = idx[a].bc[20] & idx[a].d[50];

Тогда g имеет 1-е для каждого номера, с которым вам приходится иметь дело. Если вы этого не сделаете нужны значения массива, тогда вы можете просто сделать populationCount на g. А также делай внутреннее дело popCount(g) раз.

Вы можете сделать

tg = g
n = 0
while (tg > 0){
  if(tg & 1){
    // do your stuff
  }
  tg = tg >>> 1;
  n++;
}

возможно, его можно улучшить в части tg = tg >>> 1; n++;, пропустив множество нулей, но я понятия не имею, возможно ли это. Это должно быть значительно быстрее, чем ваш текущий подход, потому что все переменные для цикла находятся в регистрах.

0 голосов
/ 23 октября 2009

Вы можете использовать hash_set из стандартной библиотеки шаблонов (STL), это даст вам очень эффективную реализацию. сложность вашего поиска будет O (1)

вот ссылка: http://www.sgi.com/tech/stl/hash_set.html

- EDIT -

объявляет новый Struct, который будет содержать ваши переменные, операторы сравнения перегрузки и создает hash_set этой новой структуры. каждый раз, когда вы хотите выполнить поиск, создайте новый объект с вашими переменными и передайте его методу hash_set "find".

Кажется, что hash_set не является обязательным для STL, поэтому вы можете использовать set, и это даст вам O (LogN) сложность для поиска. вот пример:

#include <cstdlib>
#include <iostream>
#include <set>

using namespace std;

struct Obj{

public:
       Obj(double a, double b, double c, double d){
                this->a = a;
                this->b = b;
                this->c = c;
                this->d = d;
       }

       double a;
       double b;
       double c;
       double d;
       friend bool operator < ( const Obj &l, const Obj &r )  {
              if(l.a != r.a)  return l.a < r.a;
              if(l.b != r.b) return l.a < r.b;
              if(l.c != r.c) return l.c < r.c;
              if(l.d != r.d) return l.d < r.d;
              return false;

       }
  };


 int main(int argc, char *argv[])
{
set<Obj> A;

A.insert( Obj(1,2,3,4));
A.insert( Obj(16,23,36,47));
A.insert(Obj(15,25,35,43));

Obj c(1,2,3,4);

A.find(c);
cout <<    A.count(c);



system("PAUSE");
return EXIT_SUCCESS;
}
0 голосов
/ 23 октября 2009

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

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