繁体   English   中英

寻找有效的数据结构以进行快速搜索

[英]looking for an efficient data structure to do a quick searches

我有大约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!*/
                    }
       } 
}

空间不是太重要!

根据要求修改

如果空间不太重要,最简单的方法是基于“ a”创建哈希,具体取决于您在“ a”上遇到的冲突数量,使哈希表中的每个节点都指向一棵二叉树可能很有意义。基于“ b”如果b有很多冲突,请对c执行相同的操作。

哈希的第一个索引(取决于有多少冲突)将为您节省大量时间,而只需很少的编码或数据结构即可工作。

首先,按增加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有效的下标上,这可能会大大减少您的搜索时间。

由于只有三个要匹配的属性,因此可以使用哈希表。 执行搜索时,您可以使用哈希表(索引a属性)查找匹配SomeConstant所有条目。 之后,检查b和c是否也与您的常数匹配。 这样您可以减少比较次数。 我认为这将大大加快搜索速度。

除此之外,您还可以构建三个二进制搜索树。 一个按每个属性排序。 搜索所有三个树后,您将对与每个树中的值相匹配的那些树执行操作。

根据你所说的话(在这两个问题和评论)只有一个非常小值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那些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
        }
}

这样,您不必每次都查看fooArray中的1000个项目,而是平均每次查看100个项目。 如果要提高速度,下一步将是根据b的值对索引中每个向量中的项目进行排序。 这将使您可以使用二进制搜索而不是线性搜索来找到value2的下限,从而将〜50的比较减少到〜10。 既然您已经按b对其进行了排序,那么从那时起,您就不value2b进行比较了-您可以确切地知道满足不等式的其余数字在哪里,因此只需要与cd比较即可。

您可能还会考虑基于数字的有限范围的另一种方法:0到1000可以用10位表示。 使用一些位旋转,您可以将三个字段组合为一个32位数字,这将使编译器可以一次比较所有三个字段,而不必进行三个单独的操作。 正确执行此操作有些棘手,但是一旦您做到了,它的速度大约可以再次提高三倍。

我认为使用kd-tree比较合适。 如果没有与许多冲突a则散列/索引a可能解决您的问题。

无论如何,如果不起作用,我建议使用kd-tree。

首先做一张包含多个kd树的表。 指数将它们与a

然后为每个a值实施一个kd树,在bcd方向上具有3维。

然后搜索时-第一个索引到适当的kd树有a ,然后从kd树与限制搜索。 基本上,您将进行范围搜索。

Kd树

您将得到O(L^(2/3)+m)的答案,其中L是适当的kd-tree中元素的数量, m是匹配点的数量。

我发现更好的是Range Tree 这可能是您要寻找的。 它很快。 它将以O(log^3(L)+m)回答您的查询。 (不幸的是,对范围树了解不多。)

好吧,我们去吧。

首先,==运算符要求使用信鸽方法。 由于我们在谈论[0,1000]范围内的int值,因此使用一个简单的表即可。

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

当然,这样做的想法是,您将在存储桶中为它a属性值定义的YourObject实例中找到……到目前为止,还没有什么魔术。

现在介绍新内容。

 && 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实例将在其YourObject所有实例中YourObject yourObject.c <= i <= yourObject.b所有实例

现在,使用与d相同的方法。

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

这个想法是索引ith上的std::vector<YourObject*>包含一个指向yourObject.d <= iYourObject所有实例的YourObject

放在一起!

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复杂度大约是Pred k应用,其中k是与参数匹配的项目数。

我正在寻找评论,不确定所有索引是否正确

看,这只是线性搜索。 如果您可以进行更好的扩展搜索,那就太好了,但是您复杂的匹配要求使我不清楚,是否有可能保持其排序并使用二进制搜索。

话虽如此,也许一种可能性就是生成一些索引。 主索引可能是在a属性上键入的字典,将其与对该属性具有相同值的元素列表相关联。 假设此属性的值分布均匀,它将立即消除绝大多数比较。

如果该属性的值数量有限,则可以考虑添加一个附加索引,该索引按b排序项目,甚至可能按c排序另一个索引(但顺序相反)。

如果您的应用程序已经在使用数据库,则只需将它们放在表中并使用查询来查找它。 我在一些应用程序中使用mysql,并推荐使用。

首先为每个做a不同的表...

做一个TABEL 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-s。 如果你不需要数组值那么你可以做一个populationCountg 并做内在的事情popCount(g)次。

你可以做

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

也许可以通过tg = tg >>> 1; n++;进行改进tg = tg >>> 1; n++; tg = tg >>> 1; n++; 跳过许多零,但我不知道是否有可能。 它应该比您当前的方法快得多,因为循环的所有变量都在寄存器中。

正如pmg所说,其想法是消除尽可能多的比较。 显然,您不会进行4000次比较。 这将要求所有1000个元素都通过第一个测试,这将是多余的。 显然, a仅有10个值,因此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%)。 因此,这里的数据分发很重要。 了解数据后,该算法将变得清晰。

您可以使用标准模板库(STL)中的hash_set,这将为您提供非常有效的实现。 搜索的复杂度为O(1)

这里是链接: http : //www.sgi.com/tech/stl/hash_set.html

- 编辑 -

声明新的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;
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM