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

时间:2009-10-22 19:49:49

标签: c++ c algorithm

我有一个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 个答案:

答案 0 :(得分:4)

如果空间不是太重要,最简单的方法是根据“a”创建一个哈希值根据你在“a”上得到多少冲突,可能有必要让哈希表中的每个节点都指向基于“b”的二叉树如果b有很多冲突,请对c执行相同的操作。

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

答案 1 :(得分:3)

首先,对增加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有效的下标中,这可能会大大缩短搜索时间。

答案 2 :(得分:1)

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

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

答案 3 :(得分:1)

根据您所说的内容(在问题和评论中),a(类似于10)只有非常的值。

在这种情况下,我会在a的值上建立一个索引,其中每个值都指向fooArra的所有元素,其值为std::vector<std::vector<foo *> > index(num_a_values); for (int i=0; i<1000; i++) index[fooArr[i].a].push_back(&fooArr[i]); :< / p>

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 } }

的那些项目
b

这样,您每次平均看100次,而不是每次查看fooArray中的1000个项目。如果您想要更高的速度,下一步是根据b的值对索引中每个向量中的项进行排序。这将允许您使用二分搜索而不是线性搜索找到value2的下限,从而将~50比较减少到~10。由于您按value2对其进行了排序,从那时起您不必将bc进行比较 - 您确切知道满足不等式的其余数字的位置是,所以你只需要与d和{{1}}进行比较。

您可能还会考虑另一种基于有限数字范围的方法:0到1000可以用10位表示。使用一些bit-twiddling,您可以将三个字段组合成一个32位数字,这样可以让编译器一次比较所有三个字段,而不是三个单独的操作。做到这一点有点棘手,但是一旦你做到了,它的速度可能会大约增加三倍。

答案 4 :(得分:1)

我认为使用kd-tree是合适的。 如果与a冲突不多,则哈希/索引a可能会解决您的问题。

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

首先做一个多个kd树的表。用a索引它们。

然后为方向abc中的三维维度实现每个d值的kd树。

然后搜索时 - 首先使用a索引到适当的kd-tree,然后使用您的限制从kd-tree中搜索。基本上你会做范围搜索。

Kd-tree

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

我找到的更好的是Range Tree。这可能就是你要找的东西。 它很快。它会在O(log^3(L)+m)中回答您的问题。 (很遗憾不太了解Range Tree。)

答案 5 :(得分:1)

好吧,让我们一起去吧。

首先,==运算符需要一个鸽子洞的方法。由于我们讨论的是[0,1000]范围内的int值,因此一个简单的表是好的。

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*>

我们的想法是索引ith的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 {{1}}个{{1}}个{{1}}的应用程序,其中{{1}}是与参数匹配的项目数。

我正在寻找那里的评论,不确定我是否正确地获得了所有索引

答案 6 :(得分:0)

看,这只是一个线性搜索。如果您可以进行更好的扩展搜索会很好,但是您的复杂匹配要求使我不清楚是否可以对其进行排序并使用二进制搜索。

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

如果属性的值有限,那么您可以考虑添加一个额外的索引,按b排序项目,甚至可以按c排序另一个(但顺序相反)。

答案 7 :(得分:0)

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

答案 8 :(得分:0)

首先为每个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行的索引表值为:

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。如果你不这样做 需要数组值,然后您可以在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++;部分,但我不知道这是否可行。它应该比你当前的方法快得多,因为循环的所有变量都在寄存器中。

答案 9 :(得分:0)

正如pmg所说,我们的想法是消除尽可能多的比较。显然你不会有4000次比较。这将要求所有1000个元素通过第一次测试,这将是多余的。显然,a只有10个值,因此10%通过了该检查。那么,你会做1000 + 100 +? +?检查。我们假设+ 50 + 25,总共1175。

你需要知道如何分配a,b,c,d和value1,2和3来确定最快的是什么。我们只知道a可以有10个值,我们假设value1具有相同的域。在这种情况下,按a进行分箱可以将其减少到O(1)操作以获得正确的bin,再加上相同的175次检查。但是如果b,c和value2有效地形成50个桶,你可以在O(1)中再次找到合适的桶。然而,每个桶现在平均有20个元素,所以你只需要35个测试(减少80%)。因此,数据分发在这里很重要。一旦理解了数据,算法就会清晰。

答案 10 :(得分:-1)

您可以使用标准模板库(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;
}