确保集合新颖性的有效方法
Posted
技术标签:
【中文标题】确保集合新颖性的有效方法【英文标题】:Efficient way of ensuring newness of a set 【发布时间】:2018-09-30 04:33:55 【问题描述】:给定集合N = 1,...,n
,考虑P
的不同预先存在的N
子集。子集S_p
的特征在于 0-1 n
向量 x_p
其中 i
th 元素是 0 或 1,具体取决于 i
th(n
)项是否属于子集与否。让我们称之为x_p
s 指标向量。
例如,如果N=1,2,3,4,5
,则子集1,2,5
由向量(1,0,0,1,1)
表示。
现在,给定 P
预先存在的子集及其关联向量 x_p
s。
计算由向量y
表示的候选子集。
检查y
是否已经是P
预先存在的子集的一部分或者y
是否确实是一个新的子集而不是P
子集的一部分的最有效方法是什么?
以下是我能想到的方法:
(方法1)基本上,我们必须对所有预先存在的集合进行逐个元素的检查。伪代码如下:
for(int p = 0; p < P; p++)
//(check if x_p == y by doing an element by element comparison)
int i;
for(i = 0; i < n; i++)
if(x_pi != y_i)
i = 999999;
if(i < 999999)
return that y is pre-existing
return that y is new
(方法 2)想到的另一个想法是存储指标向量 x_p
s 的十进制等效值(其中指标向量被视为二进制表示)并将其与 y
的十进制等效值进行比较.也就是说,如果一组P
预先存在的集合是: (0,1,0,0,1), (1,0,1,1,0)
,则该集合的存储小数将为9, 22
。如果y
是(0,1,1,0,0)
,我们计算12
并对照集合9, 22
进行检查。这种方法的好处是,对于每个新的y
,我们不必检查每个预先存在的集合的n
元素。我们可以只比较十进制数。
问题1。在我看来(方法2)应该比(方法1)更有效。对于(方法2),是否有一种有效的方法(C/C++ 中的内置库函数)将x_p
s 和y
从二进制转换为十进制?这些指标变量的数据类型应该是什么?例如,bool y[5];
或 char y[5];
?
问题2.有没有比(方法2)更有效的方法?
【问题讨论】:
n
的最大值是多少?
看看Bloom filter 是否有帮助。
@user3386109 高达 1000
如果 n
是 1000,那么十进制等价物是 1000 位数字。所以是一个由 16 个 64 位数字或 32 个 32 位数字组成的数组。
哦,还不错。在 C++ 中,您可以将 x_p
向量转换为 string
s 并将字符串存储在 std::set
中。
【参考方案1】:
您已经注意到,指标向量和 N 位整数之间存在微不足道的同构。这意味着您的问题 2 的答案是“否”:可用于维护集合和测试其中成员资格的工具与整数相同(哈希表带来了正常的方法)。评论中提到了 Bloom 填充器,它可以有效地测试成员资格,但存在一些误报的风险,但 Bloom 过滤器通常适用于比您看到的更大的数据量。
至于你的问题1:方法2是合理的,比你想象的还要容易。虽然vector<bool>
没有给您提供将其转换为整数块的简单方法,但在实现上我知道它已经以这种方式实现(C++ 标准允许对该特定向量类型进行特殊处理,这在当今普遍被考虑这是一个糟糕的决定,但偶尔会产生一些好处)。这些向量是可散列的。因此,只需保持unordered_set<vector<bool>>
左右,您将获得相当接近最佳性能的性能。 (如果你在编译时知道N
,你可能更喜欢bitset
而不是vector<bool>
。)
【讨论】:
所以,如果我理解正确的话,我可以有std::vector<std::vector<bool> > preexistingsets;
和std::vector<bool> candidateset;
然后,要检查candidateset
是否预先存在,我应该只做一个for 循环,遍历每个预先存在的逐个元素设置和检查?或者,有没有我可以使用的库函数?我在编译时确实知道 N 。所以,我也会看看bitset
。
我想我理解unordered_set
。所以,我可以拥有unordered_set<bitset <N>> preexistingsets;
。让我做实验。
@Tryer 您可以直接将两个vector<bool>
s 与==
进行比较。操作员将负责循环遍历向量的内容,希望优化以比较字节大小或更大的块,而不是逐位比较。 bitset
也一样。您无需编写自己的每个元素的比较。
@Sneftel 我有以下内容:std::unordered_set<std::bitset<1000>> S;
假设我想将指标向量10011000...0
添加到S
中。我应该先做一个 std::bitset<1000> bset;
然后是适当的 bset.set(index);
然后最后是 S.insert(bset);
吗?那是最有效的吗?或者是否有类似 emplace_back
的东西或类似的东西可以帮助将 bitset
直接构建到 unordered_set
中,而不是先在外部创建它然后将其复制到容器中?
只需构建位集,然后将其插入。这不会成为您的性能瓶颈。【参考方案2】:
方法 2 可以通过计算给定子集的十进制等效值并使用模数 1e9+7 对其进行散列来优化。这会导致每次 N
#define M 1000000007 //big prime number
unordered_set<long long> subset; //containing decimal representation of all the
//previous found subsets
/*fast computation of power of 2*/
long long Pow(long long num,long long pow)
long long result=1;
while(pow)
if(pow&1)
result*=num;
result%=M;
num*=num;
num%=M;
pow>>=1;
return result;
/*checks if subset pre exists*/
bool check(vector<bool> booleanVector)
long long result=0;
for(int i=0;i<booleanVector.size();i++)
if(booleanVector[i])
result+=Pow(2,i);
return (subset.find(result)==subset.end());
【讨论】:
2^1000 > 1e9+7
(相差很大)。根据鸽巢原理,您不能使用模数(或其他散列方法)来减少范围而不引起冲突。
最后,您在循环中使用Pow
来计算与指标向量对应的整数的方法效率低下:最好通过移位和通过平方添加而不是使用求幂(O(n)
与 O(n log n)
)。
我已经尝试并测试了使用 M = 1e9+7 的代码,并且在 5e6 之前没有发生冲突,正如代码中所做的那样。 code。此外,您的第二条评论仅删除了 log(N) 因子,这与 N
碰撞不太可能。但是您不能仅根据一些测试就声称它们是不可能的。如果你稍微研究一下,你会发现一对具有相同哈希的位向量。
确实,根据您的评论,您确实找到了一个。那么,鉴于存在碰撞,是什么阻止了前两个位向量成为那对?以上是关于确保集合新颖性的有效方法的主要内容,如果未能解决你的问题,请参考以下文章