找到组合对之间共享元素的最佳方式

Optimal way to find shared elements between combination pairs

本文关键字:元素 最佳 方式 共享 之间 组合      更新时间:2023-10-16

我有一个类型为a的有序项目列表,每个项目都包含项目B列表中的一个子集。对于a中的每一对项目,我想找到它们共享(相交)的项目B的数量。

例如,如果我有以下数据:

A1 : B1  
A2 : B1 B2 B3  
A3 : B1  

然后我会得到以下结果:

A1, A2 : 1  
A1, A3 : 1  
A2, A3 : 1  

我遇到的问题是使算法高效。我的数据集大小约为8.4K个类型A的项目。这意味着8.4K个项目选择2=35275800个组合。我使用的算法只是简单地遍历每个组合对并进行一个集合交集。

到目前为止,我所掌握的要点如下。我将计数作为键存储在地图中,值作为a对的向量。我使用图形数据结构来存储数据,但我使用的唯一"图形"操作是get_neighbors(),它从a返回项目的B子集。我碰巧知道图形中的元素是从索引0到8.4K排序的。

void get_overlap(Graph& g, map<int, vector<A_pair> >& overlap) {
map<int, vector<A_pair> >::iterator it;
EdgeList el_i, el_j;
set<int> intersect;
size_t i, j;
VertexList vl = g.vertices();
for (i = 0; i < vl.size()-1; i++) {
el_i = g.get_neighbors(i);
for (j = i+1; j < vl.size(); j++) {
el_j = g.get_neighbors(j);
set_intersection(el_i.begin(), el_i.end(), el_j.begin(), el_j.end(), inserter(intersect, intersect.begin()));
int num_overlap = intersect.size();
it = overlap.find(num_overlap);
if (it == overlap.end()) {
vector<A_pair> temp;
temp.push_back(A_pair(i, j));
overlap.insert(pair<int, vector<A_pair> >(num_overlap, temp));
}
else {
vector<A_pair> temp = it->second;
temp.push_back(A_pair(i, j));
overlap[num_overlap] = temp;
}
}
}

}

我已经运行这个程序将近24小时了,for循环中的第I个元素已经达到了250次迭代(我正在将每个I打印到一个日志文件中)。当然,这离8.4K还有很长的路要走(尽管我知道随着迭代的进行,比较的次数会缩短,因为j=I+1)。是否有更优化的方法?

编辑:需要明确的是,这里的目标最终是找到前k个重叠对。

编辑2:感谢@Beta和其他人指出的优化。特别是,直接更新映射(而不是复制其内容并重置映射值)大大提高了性能。现在只需几秒钟。

我认为您可以通过预先计算反向(边到顶点)映射来加快速度。这将允许您避免set_interaction调用,该调用执行大量代价高昂的集合插入。我遗漏了一些声明来制作功能齐全的代码,但希望您能理解。我假设EdgeList是某种int向量:

void get_overlap(Graph& g, map<int, vector<A_pair> >& overlap) {
map<int, vector<A_pair> >::iterator it;

EdgeList el_i, el_j;
set<int> intersect;
size_t i, j;
VertexList vl = g.vertices();
// compute reverse map
map<int, set<int>> reverseMap;
for (i = 0; i < vl.size()-1; i++) {
el_i = g.get_neighbors(i);
for (auto e : el_i) {
const auto findIt = reverseMap.find(e);
if (end(reverseMap) == findIt) {
reverseMap.emplace(e, set<int>({i})));
} else {
findIt->second.insert(i);
}
}
}
for (i = 0; i < vl.size()-1; i++) {
el_i = g.get_neighbors(i);
for (j = i+1; j < vl.size(); j++) {
el_j = g.get_neighbors(j);
int num_overlap = 0;
for (auto e: el_i) {
auto findIt = reverseMap.find(e);
if (end(reverseMap) != findIt) {
if (findIt->second.count(j) > 0) {
++num_overlap;
}
}
}
it = overlap.find(num_overlap);
if (it == overlap.end()) {
overlap.emplace(num_overlap, vector<A_pair>({ A_pair(i, j) }));
}
else {
it->second.push_back(A_pair(i,j));
}
}
}

我没有做精确的性能分析,但在双循环中,你用N*log(M)*log(E)比较代替了"最多4N个比较"+一些昂贵的集合插入(来自set_intersection),其中N是每个顶点的平均边数,M是每个边的平均顶点数,E是边数,所以根据你的数据集,这可能是有益的。此外,如果边索引是紧凑的,那么可以使用simplae向量而不是映射来表示反向映射,这消除了log(E)性能成本。

不过,还有一个问题。既然你在谈论顶点和边,难道你没有额外的约束,即边总是有两个顶点吗?这可以简化一些计算。