假设我们有一个函数返回一百万个长度为30的整数向量,每个向量都带有小条目(比如说在-100和100之间)。进一步假设输出只有大约30000个唯一向量,其余的是重复的。什么是良好的数据结构和算法来检索唯一输出向量列表?优选地,当3%独特载体的比例大致恒定时,溶液应该很好地扩展。
这个问题主要是关于数据结构,但我打算使用STL在C ++中实现它,所以对实现的任何提示都是受欢迎的。
我没有计算机科学背景。我也很擅长指向文学,我可以学习如何处理这些问题。
答案 0 :(得分:3)
你提出的建议有时被称为后备表;一个
用于各种查找目的的辅助表。在你的情况下,
你有许多不同的组织方式
表。最明显的是不组织它,并使用线性
搜索以查看下一个元素是否已知。自从
表最终将包含大约30000个元素,即
可能不是一个好主意。从标准库(至少
在C ++ 11)中,有两种可能性:std::set
和
std::unordered_set
。 std::set
使用某种形式的平衡
树,因此每个最多进行lg n 比较
查找(大约15个,30000个元素); std::unordered_set
是一个
哈希表,并具有良好的哈希函数,将需要尽可能小
恒定的比较数:你应该能够得到它
平均降至2以下(但可能会花费更多
记忆 - 负载系数越低,概率越小
碰撞)。如你所说,你做有额外的费用
计算哈希函数,正如你所指出的那样
确实涉及访问向量中的每个元素;在二进制文件中
树,每次比较所需的一切就足够了
比较元素以确定顺序 - 在许多情况下,
这可能只是一两个。 (但如果你说有一个
很多重复...你不能检测到重复,直到你
访问了所有30个条目,因为任何一个可能会有所不同。)唯一的方法
知道哪个解决方案实际上更快就是测量
两者,使用典型数据;对于您描述的数据集
(很多重复),我怀疑哈希表会赢,但它是
远非确定。
最后,您可以使用某种非二叉树。如果你可以的话 真的将值限制在特定范围内(例如-100..100), 你可以使用一个普通的向量或数组与指针 子节点,直接用元素值索引,转置 有必要的。然后你走到树上直到你找到 一个空指针,或者你到达终点。最大深度 树将是30,事实上,每个元素将是30深,但 通常,您会发现元素在获取之前是唯一的 那么深。我怀疑(但同样,你需要衡量) 在你的情况下,有许多重复,这实际上是 比前两个建议慢得多。 (而且它 对你来说会有更多的工作,因为我不知道 任何现有的实现。)
至于散列,几乎任何形式的线性全等散列
应该足够了:例如FNV。大部分的
此类哈希的文档涉及字符串(数组)
char
),但它们往往与任何积分一样好用
类型。我通常使用类似的东西:
template <typename ForwardIterator>
size_t
hash( ForwardIterator begin, ForwardIterator end )
{
size_t results = 2166136261U
for ( ForwardIterator current = begin; current != end; ++ current ) {
results = 127 * results + static_cast<size_t>( *current );
}
return results;
}
我选择127
作为乘数主要是基于速度
较旧的系统:乘以127比大多数系统快得多
其他值可以产生良好的效果。 (我不知道
这是否仍然是真的。但乘法仍然是一个
在许多机器和编译器上运行相对较慢
会将127 * x
转换为类似x << 7 - x
的内容
更快。)使用上述算法的分布是关于
和FNV一样好,至少我的数据集
测试
答案 1 :(得分:1)
基数映射是理想的,但您需要实现它,因为std库中没有实现。
答案 2 :(得分:1)
计算第一个向量中值的CRC表示。您现在有一个代表30个值的数字。对于其他载体而言,这个数字可能是唯一的,但它并不是保证。
将CRC值作为键,并指向实际向量并将其插入多图{CRC,VectorPointer}。
现在,对于每个剩余的向量计算CRC,并在多图中查找。
如果找不到,请插入{CRC,VectorPointer}。如果找到它,则迭代匹配并比较数据元素以确定它是否相同。如果丢弃新的矢量。如果不是,则插入{CRC,VectorPointer}。
冲洗并重复,直到所有30,000个载体都已处理完毕。
您在multimap中拥有可以迭代的唯一设置。
答案 3 :(得分:1)
假设您有N个长度为K的向量,并且只有M个唯一的向量。
你可以在O(K)时间内计算每个向量的哈希值,检查你的hashmap中是否已经有这样的向量,并在O(1)时间内插入新向量。对于散列函数,您可以简单地使用没有模数的poliomial散列,只存储64位类型的散列并忽略溢出。实现非常简单,它将在需要O(M * K)存储器的O(N * K)时间内工作。如果您需要先对元素进行排序,则时间将为O(N * K * log(K))
我认为你不应该在这里使用基数树,因为你仍然需要查看每个向量的每个元素。这是因为如果你在树中没有这样的矢量,你需要插入它的所有元素,如果你有这样的矢量,你需要去树的叶子看到你以前真的插入过这样的矢量。所以渐近线保持不变,但你需要自己实现树,这不是一个好主意:))
看起来很容易证明你至少需要阅读载体的所有元素。这是因为在每个时刻你都有两种可能性 - 你之前已经找到了当前的向量,你需要将所有元素读到最后才能识别它,或者你之前没有找到当前向量,你需要阅读它的所有元素排序和保存它们。然而,如果向量已经排序,则需要仅在第一个不匹配时读取元素。但是让我们想象一下,前30000个向量是唯一的,然后你需要读取所有其他向量到最后确定它们不是唯一的,无论你使用什么算法或数据结构。最后我们得到你需要阅读几乎所有的向量到最后:)
如果你的值确实在范围内(-100,100)并且向量中只有30个值,你可以注意到这样的向量可以保存在四个64位整数中,因为你只有8*30 = 240
位其中的数据。但这只是另一个想法,我认为使用它的任何实现都不会比散列+ hashmap更快。
答案 4 :(得分:0)
Hashing:...我看到的一个缺点是每个向量的每个组件至少被触摸一次。这似乎已经太多了。
在最坏的情况下,你怎么能比较两个向量而不至少看一次?不,真的,如果你有1,1,1和2,2,2比较/匹配立即结束。但如果你有1,2,3和1,2,3?
无论如何,这是解决问题的一种方法。实施可以明确改善。
#include <iostream>
#include <map>
#include <vector>
#include <list>
#include <cstdint>
#include <cstdlib>
#include <ctime>
using namespace std;
const int TotalVectorCount = 1000000;
const int UniqueVectorCount = 30000;
const int VectorLength = 30;
typedef vector<int> Vector;
typedef unsigned long long uint64;
void GenerateRandomVector(Vector& v)
{
v.reserve(VectorLength);
// generate 30 values from -100 to +100
for (int i = 0; i < VectorLength; i++)
v.push_back(rand() % 201 - 100);
}
bool IdenticalVectors(const Vector& v1, const Vector& v2)
{
for (int i = 0; i < VectorLength; i++)
if (v1[i] != v2[i])
return false;
return true;
}
// this lets us do "cout << Vector"
ostream& operator<<(ostream& os, const Vector& v)
{
for (int i = 0; i < VectorLength; i++)
os << v[i] << ' ';
return os;
}
uint64 HashVector(const Vector& v)
{
// this is probably a bad hash function,
// but it seems to work nonetheless
uint64 h = 0x7FFFFFFFFFFFFFE7;
for (int i = 0; i < VectorLength; i++)
h = h * 0xFFFFFFFFFFFFFFC5 + v[i];
return h & 0xFFFFFFFFFFFFFFFF;
}
Vector UniqueTestVectors[UniqueVectorCount];
void GenerateUniqueTestVectors()
{
map<uint64,char> m;
for (int i = 0; i < UniqueVectorCount; i++)
{
for (;;)
{
GenerateRandomVector(UniqueTestVectors[i]);
uint64 h = HashVector(UniqueTestVectors[i]);
map<uint64,char>::iterator it = m.find(h);
if (it == m.end())
{
m[h] = 0;
break;
}
}
}
}
bool GetNextVector(Vector& v)
{
static int count = 0;
v = UniqueTestVectors[count % UniqueVectorCount];
return ++count <= TotalVectorCount;
}
int main()
{
srand(time(0));
cout << "Generating " << UniqueVectorCount << " unique random vectors..."
<< endl;
GenerateUniqueTestVectors();
#if 0
for (int i = 0; i < UniqueVectorCount; i++)
cout << UniqueTestVectors[i] << endl;
#endif
cout << "Generating " << TotalVectorCount << " random vectors with only "
<< UniqueVectorCount << " unique..." << endl;
map<uint64,list<Vector>> TheBigHashTable;
int uniqCnt = 0;
int totCnt = 0;
Vector v;
while (GetNextVector(v))
{
totCnt++;
uint64 h = HashVector(v);
map<uint64,list<Vector>>::iterator it = TheBigHashTable.find(h);
if (it == TheBigHashTable.end())
{
// seeing vector with this hash (h) for the first time,
// insert it into the hash table
list<Vector> l;
l.push_back(v);
TheBigHashTable[h] = l;
uniqCnt++;
}
else
{
// we've seen vectors with this hash (h) before,
// let's see if we've already hashed this vector
list<Vector>::iterator it;
bool exists = false;
for (it = TheBigHashTable[h].begin();
it != TheBigHashTable[h].end();
it++)
{
if (IdenticalVectors(*it, v))
{
// we've hashed this vector before
exists = true;
break;
}
}
if (!exists)
{
// we haven't hashed this vector before,
// let's do it now
TheBigHashTable[h].push_back(v);
uniqCnt++;
}
}
}
#if 0
cout << "Unique vectors found:" << endl;
map<uint64,list<Vector>>::iterator it;
for (it = TheBigHashTable.begin();
it != TheBigHashTable.end();
it++)
{
list<Vector>::iterator it2;
for (it2 = it->second.begin();
it2 != it->second.end();
it2++)
cout << *it2 << endl;
}
#endif
cout << "Hashed " << uniqCnt << " unique vectors out of " << totCnt << " total" << endl;
return 0;
}
输出(ideone)在1.12秒内使用12848 kB RAM:
Generating 30000 unique random vectors...
Generating 1000000 random vectors with only 30000 unique...
Hashed 30000 unique vectors out of 1000000 total
现在,使用更少和更短的唯一向量相同,因此可以在控制台中打印它们:
输出(ideone),0.14秒,使用3040 kB RAM:
Generating 10 unique random vectors...
-45 75 1 -71 -83 97 10 -18 89 -10
-11 60 18 -54 -90 77 19 -90 -7 -31
-15 -65 -47 88 25 -56 4 39 -20 39
-64 -14 -37 -13 15 -70 -66 -75 12 73
-35 -99 32 83 98 -8 59 16 2 -98
86 37 -63 -62 24 62 -68 78 -50 -38
17 -64 48 80 -26 -87 61 8 -62 -28
-70 -47 -27 62 86 -29 -97 44 37 -45
-4 -28 92 -17 -40 -35 -56 -58 -57 -55
5 10 -19 -48 -61 5 -35 100 -88 -47
Generating 1000000 random vectors with only 10 unique...
Unique vectors found:
86 37 -63 -62 24 62 -68 78 -50 -38
17 -64 48 80 -26 -87 61 8 -62 -28
5 10 -19 -48 -61 5 -35 100 -88 -47
-4 -28 92 -17 -40 -35 -56 -58 -57 -55
-11 60 18 -54 -90 77 19 -90 -7 -31
-15 -65 -47 88 25 -56 4 39 -20 39
-35 -99 32 83 98 -8 59 16 2 -98
-45 75 1 -71 -83 97 10 -18 89 -10
-64 -14 -37 -13 15 -70 -66 -75 12 73
-70 -47 -27 62 86 -29 -97 44 37 -45
Hashed 10 unique vectors out of 1000000 total