ankerl::unordered_dense::set与std::set_difference配合异常问题咨询
你在实现图问题求解器的addNewRedNeighbors函数时,遇到了std::set_difference的计算结果异常:当mergedSourceNeighbors包含{3, 7}、mergedTwinNeighbors包含{11, 7}时,本该得到仅包含{11}的差集,但实际结果却是{11, 7}。
原因分析
这确实是ankerl::unordered_dense::set的特性导致的。std::set_difference的核心前置条件是输入的两个迭代器范围必须是已排序的(且遵循同一升序规则),但ankerl::unordered_dense::set是基于哈希的无序集合,它的迭代器遍历顺序完全由元素的哈希值决定,不会按照元素的数值大小排序。
以你的场景为例,mergedSourceNeighbors的遍历顺序可能是7 → 3(而非预期的3 →7),mergedTwinNeighbors的遍历顺序是11 →7。此时std::set_difference会按遍历顺序逐个对比元素:
- 第一个元素
11vs7:11大于7,被判定为twin独有的元素,加入结果 - 第二个元素
7vs7:相等,跳过 - 由于
mergedSourceNeighbors已经遍历完毕,后续没有更多对比,但因为初始遍历顺序无序,set_difference无法正确识别7其实是两个集合共有的元素,最终错误地保留了它。
解决办法
方案1:改用有序集合
将ankerl::unordered_dense::set替换为std::set(天然有序),这样std::set_difference可以直接正确计算差集:
void addNewRedNeighbors(int source, int twin) { // Merge red and black edges for both source and twin std::set<int> mergedSourceNeighbors(adjListBlack[source]); mergedSourceNeighbors.insert(adjListRed[source].begin(), adjListRed[source].end()); std::set<int> mergedTwinNeighbors(adjListBlack[twin]); mergedTwinNeighbors.insert(adjListRed[twin].begin(), adjListRed[twin].end()); // Find edges of twin that are not adjacent to source ankerl::unordered_dense::set<int> newRedEdges; std::set_difference( mergedTwinNeighbors.begin(), mergedTwinNeighbors.end(), mergedSourceNeighbors.begin(), mergedSourceNeighbors.end(), std::inserter(newRedEdges, newRedEdges.begin()) ); // Add these edges as red edges for source for (int v : newRedEdges) { addEdge(source, v, "red"); } }
方案2:先排序再计算差集
如果不想替换集合类型,可以先将无序集合的元素导出到vector并排序,再执行std::set_difference:
void addNewRedNeighbors(int source, int twin) { // Merge red and black edges for both source and twin ankerl::unordered_dense::set<int> mergedSourceNeighbors(adjListBlack[source]); mergedSourceNeighbors.insert(adjListRed[source].begin(), adjListRed[source].end()); ankerl::unordered_dense::set<int> mergedTwinNeighbors(adjListBlack[twin]); mergedTwinNeighbors.insert(adjListRed[twin].begin(), adjListRed[twin].end()); // 转换为有序vector std::vector<int> srcSorted(mergedSourceNeighbors.begin(), mergedSourceNeighbors.end()); std::vector<int> twinSorted(mergedTwinNeighbors.begin(), mergedTwinNeighbors.end()); std::sort(srcSorted.begin(), srcSorted.end()); std::sort(twinSorted.begin(), twinSorted.end()); // 计算差集 std::vector<int> newRedEdgesVec; std::set_difference( twinSorted.begin(), twinSorted.end(), srcSorted.begin(), srcSorted.end(), std::back_inserter(newRedEdgesVec) ); // Add these edges as red edges for source for (int v : newRedEdgesVec) { addEdge(source, v, "red"); } }
方案3:手动遍历检查(更适配无序集合)
直接遍历twin的合并邻居,用哈希集合的O(1)查找特性判断元素是否存在于source的合并邻居中,这种方式不需要依赖std::set_difference的有序要求:
void addNewRedNeighbors(int source, int twin) { // Merge red and black edges for both source and twin ankerl::unordered_dense::set<int> mergedSourceNeighbors(adjListBlack[source]); mergedSourceNeighbors.insert(adjListRed[source].begin(), adjListRed[source].end()); ankerl::unordered_dense::set<int> mergedTwinNeighbors(adjListBlack[twin]); mergedTwinNeighbors.insert(adjListRed[twin].begin(), adjListRed[twin].end()); // 手动筛选twin独有的元素 ankerl::unordered_dense::set<int> newRedEdges; for (int v : mergedTwinNeighbors) { if (!mergedSourceNeighbors.contains(v)) { newRedEdges.insert(v); } } // Add these edges as red edges for source for (int v : newRedEdges) { addEdge(source, v, "red"); } }
内容的提问来源于stack exchange,提问作者Denis Koshelev

