[英]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
对其进行了排序,那么从那时起,您就不value2
与b
进行比较了-您可以确切地知道满足不等式的其余数字在哪里,因此只需要与c
和d
比较即可。
您可能还会考虑基于数字的有限范围的另一种方法:0到1000可以用10位表示。 使用一些位旋转,您可以将三个字段组合为一个32位数字,这将使编译器可以一次比较所有三个字段,而不必进行三个单独的操作。 正确执行此操作有些棘手,但是一旦您做到了,它的速度大约可以再次提高三倍。
我认为使用kd-tree比较合适。 如果没有与许多冲突a
则散列/索引a
可能解决您的问题。
无论如何,如果不起作用,我建议使用kd-tree。
首先做一张包含多个kd树的表。 指数将它们与a
。
然后为每个a
值实施一个kd树,在b
, c
, d
方向上具有3维。
然后搜索时-第一个索引到适当的kd树有a
,然后从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 <= i
的YourObject
所有实例的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。 如果你不需要数组值那么你可以做一个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++;
进行改进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.