两组的有效交集

时间:2018-05-09 11:54:01

标签: c++ algorithm data-structures

我有两套(或地图),需要有效地处理他们的交集。 我知道有两种方法可以做到这一点:

  • 迭代两个地图,如std :: set_intersection:O(n1 + n2)
  • 迭代一个地图并在另一个地图中找到元素:O(n1 * log(n2))

根据尺寸的不同,这两种解决方案中的任何一种都明显更好(定时),因此我需要根据尺寸(这有点凌乱)在这些算法之间切换 - 或者找到一个优于两者的解决方案,例如使用map.find()的一些变体将前一个迭代器作为提示(类似于map.emplace_hint(...)) - 但我找不到这样的函数。

问题:是否可以直接使用STL或某些兼容的库来结合两种解决方案的性能特征?

请注意,性能要求使其与早期的问题不同,例如 Efficient intersection of sets?

4 个答案:

答案 0 :(得分:4)

几乎在每种情况下std::set_intersection都是最佳选择。 只有当集合包含非常少量的元素时,其他解决方案才可能更好。 由于原木的性质与基地二。 其缩放为:

  

n = 2,log(n)= 1
  n = 4,log(n)= 2
  n = 8,log(n)= 3
  .....
  n = 1024 log(n)= 10

O(如果集合的长度超过5-10个元素,则n1 * log(n2)比O(n1 + n2)复杂得多。

这样的功能被添加到STL是有原因的,并且它是这样实现的。它还将使代码更具可读性。

对于长度小于20但很少使用的集合,选择排序比合并或快速排序要快。

答案 1 :(得分:2)

对于作为二叉树实现的集合,实际上一种算法,它结合了您提到的两个过程的好处。本质上,你像std :: set_intersection那样进行合并,但是在一棵树中迭代时,你跳过任何小于另一棵树当前值的分支。

得到的交点需要 O(min(n1 log n2,n2 log n1,n1 + n2),这正是你想要的。

不幸的是,我非常确定std :: set不提供可以支持此操作的接口。

过去我曾经做过几次,当时我正在加入倒排索引和类似的东西。通常我使用skipTo(x)操作生成迭代器,该操作将前进到下一个元素> = x。为了满足我承诺的复杂性,它必须能够在log(N)摊销时间内跳过N个元素。然后一个十字路口看起来像这样:

void get_intersection(vector<T> *dest, const set<T> set1, const set<T> set2)
{
    auto end1 = set1.end();
    auto end2 = set2.end();
    auto it1 = set1.begin();
    if (it1 == end1)
        return;
    auto it2 = set2.begin();
    if (it2 == end2)
        return;
    for (;;)
    {
        it1.skipTo(*it2);
        if (it1 == end1)
            break;
        if (*it1 == *it2)
        {
            dest->push_back(*it1);
            ++it1;
        }
        it2.skipTo(*it1);
        if (it2 == end2)
            break;
        if (*it2 == *it1)
        {
            dest->push_back(*it2);
            ++it2;
        }
    }
}

使用迭代器向量可以很容易地扩展到任意数量的集合,并且几乎任何有序集合都可以扩展以提供所需的迭代器 - 排序数组,二叉树,b树,跳过列表等。 / p>

答案 2 :(得分:0)

关于性能要求,O(n1 + n2)在大多数情况下是非常好的复杂性,所以只有在紧密循环中进行此计算时才值得考虑。

如果你确实需要它,那么组合方法也不算太糟糕,或许类似于什么?

伪代码:

x' = set_with_min_length([x, y])
y' = set_with_max_length([x, y])
if (x'.length * log(y'.length)) <= (x'.length + y'.length):
     return iterate_over_map_find_elements_in_other(y', x')

return std::set_intersection(x, y)

我认为你不会找到能够击败其中任何一种复杂性的算法,但很高兴被证明是错误的。

答案 3 :(得分:0)

我不知道如何使用标准库执行此操作,但如果您编写了自己的平衡二叉搜索树,则以下是如何使用提示&#34;实现有限的&#34;查找。 (根据您的其他要求,BST重新实现也可能遗漏父指针,这可能是对STL的性能胜利。)

假设提示值小于要找到的值,并且我们知道提示节点属于其左子树的提示节点的祖先堆栈。首先通常在提示节点的右子树中搜索,按照保证将节点推送到堆栈(以准备下次提示)。如果这不起作用,那么当堆栈的顶级节点的值小于查询值时,弹出堆栈。从弹出的最后一个节点(如果有的话)中搜索,按照保证推送。

我声称,当使用这种机制连续搜索值按升序排列时,(1)每个树边最多遍历一次,(2)每个查找遍历最多两个下行路径的边。给定具有n2个节点的二叉树中的2 * n1个下行路径,边缘的成本为O(n1 log n2)。它也是O(n2),因为每个边都被遍历了一次。