找出两组是否重叠的最快方法?

时间:2015-04-02 18:27:05

标签: c++ algorithm

显然做std :: set_intersection()是浪费时间。 算法标题中是否有一个函数来完成这个? 据我所知,std :: find_first_of()正在进行线性搜索。

4 个答案:

答案 0 :(得分:6)

这是仅适用于std::set(或多个)的解决方案。地图解决方案只需要更多的工作。

我尝试了3种方式。

首先,如果一个远大于另一个,我只需要查找另一个中的所有元素。反之亦然。

理论上常数100是错误的。对于某些k,它应该是k n lg m > m,而不是100 n > m以获得最佳的大O性能:但是常数因子很大,并且100> lg m,所以实际上应该进行实验。

如果不是这样的话,我们会遍历每个集合,寻找与set_intersection非常相似的碰撞。我们使用++来尝试更快地跳过每个列表,而不仅仅是.lower_bound

请注意,如果您的列表包含交错元素(例如{1,3,7}{0,2,4,6,8}),那么这将比对数因子更慢。

如果两套"交叉"相对较少,这可以跳过大量的每组内容。

如果您想比较这两种行为,请将lower_bound部分替换为++

template<class Lhs, class Rhs>
bool sorted_has_overlap( Lhs const& lhs, Rhs const& rhs ) {
  if (lhs.empty() || rhs.empty()) return false;
  if (lhs.size() * 100 < rhs.size()) {
    for (auto&& x:lhs)
      if (rhs.find(x)!=rhs.end())
        return true;
    return false;
  }
  if (rhs.size() * 100 < lhs.size()) {
    for(auto&& x:rhs)
      if (lhs.find(x)!=lhs.end())
        return true;
    return false;
  }

  using std::begin; using std::end;

  auto lit = begin(lhs);
  auto lend = end(lhs);

  auto rit = begin(rhs);
  auto rend = end(rhs);

  while( lit != lend && rit != rend ) {
    if (*lit < *rit) {
      lit = lhs.lower_bound(*rit);
      continue;
    }
    if (*rit < *lit) {
      rit = rhs.lower_bound(*lit);
      continue;
    }
    return true;
  }
  return false;
}

一个有序数组可以做第三种算法选择并使用std::lower_bound来快速推进&#34;其他&#34;容器。这具有使用部分搜索的优势(您无法在set中快速执行)。在交错的情况下它也会表现不佳&#34;元素(通过log n因子)与天真++进行比较。

前两个也可以使用排序数组快速完成,用std中的算法调用替换方法调用。这种转变基本上是机械的。

排序数组上的渐近最优版本将使用二进制搜索,偏向于在列表的开头找到下限 - 搜索1,2,4,8等,而不是搜索一半,四分之一等。注意它具有相同的lg(n)最坏情况,但如果搜索的元素是 first 而不是O(lg(n)),则为O(1)。在这种情况下(搜索进展较少)意味着全局进展较少,优化该案例的子算法可以为您提供更好的全局最坏情况速度。

为了解决原因,快速交替&#34;它不会比++更糟糕 - 下一个元素是符号交换的情况需要进行O(1)操作,如果差距,则用O(lg k)替换O(k)更大。

然而,到目前为止,我们远远落后于优化漏洞:配置文件,并在继续这种方式之前确定它是否值得。


排序数组的另一种方法是假设std::lower_bound被最佳地写入(在随机访问迭代器上)。使用输出迭代器,如果写入则抛出异常。如果您捕获该异常,则返回true,否则返回false。

(上面的优化 - 选择一个和bin搜索另一个,并且指数提前搜索 - 对于std::set_intersection可能是合法的。)


我认为使用3种算法非常重要。设置交叉点测试,其中一边小得多,另一边可能是常见的:一边是一个元素的极端情况,另一边是许多另一边是众所周知的(作为搜索)。

天真的双线性&#39;在这种常见情况下,搜索可以提供线性性能。通过检测两侧之间的不对称性,您可以切换到小型线性,登录大型&#39;在适当的时候,并在这些情况下有更好的表现。 O(n + m)vs O(m lg n) - 如果m <1。 O(n / lg n)第二个击败第一个。如果m是常数,那么我们得到O(n)vs O(lg n) - 其中包括&#39;使用函数的边缘情况来查找单个元素是否在某个大集合中。

答案 1 :(得分:5)

如果输入已排序,您可以使用以下模板功能:

template<class InputIt1, class InputIt2>
bool intersect(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2)
{
    while (first1 != last1 && first2 != last2) {
        if (*first1 < *first2) {
            ++first1;
            continue;
        } 
        if (*first2 < *first1) {
            ++first2;
            continue;
        } 
        return true;
    }
    return false;
}

您可以这样使用:

#include <iostream>
int main() {
    int a[] = {1, 2, 3};
    int b[] = {3, 4};
    int c[] = {4};
    std::cout << intersect(a, a + 3, b, b + 2) << std::endl;
    std::cout << intersect(b, b + 2, c, c + 1) << std::endl;
    std::cout << intersect(a, a + 3, c, c + 1) << std::endl;
}

结果:

1
1
0

此功能的复杂度为O(n + m),其中nm为输入尺寸。但是,如果一个输入对另一个输入非常小(例如,n n元素会更好。这会花费O(n * log(m))时间。

#include <algorithm>
template<class InputIt1, class InputIt2>
/**
 *  When input1 is much smaller that input2
 */
bool intersect(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2) {
    while (first1 != last1)
        if (std::binary_search(first2, last2, *first1++))
            return true;
    return false;
}

答案 2 :(得分:4)

有时您可以在一个记忆单词中编码数字组。例如,您可以在内存字中编码集{0,2,3,6,7}...00000011001101。规则是:当且仅当数字i在集合中时,位置i(从右到左阅读)的位才会启动。

现在,如果您有两个在内存字ab中编码的集合,则可以使用按位运算符&执行交集。

int a = ...;
int b = ...;
int intersection = a & b;
int union = a | b;  // bonus

这种风格的好处是,交叉(并集,互补)是在一个cpu指令中执行的(我不知道这是否是正确的术语)。

如果需要处理大于存储器字位数的数字,则可以使用多个存储器字。通常,我使用一组记忆单词。

如果你想要处理负数,只需使用两个数组,一个用于负数,一个用于正数。

这种方法的坏处是它只适用于整数。

答案 3 :(得分:1)

我认为你可以制作二元搜索

#include <set>
#include <iostream>
#include <algorithm>

bool overlap(const std::set<int>& s1, const std::set<int>& s2)
{
    for( const auto& i : s1) {
        if(std::binary_search(s2.begin(), s2.end(), i))
            return true;
    }
    return false;
}

int main()
{
    std::set<int> s1 {1, 2, 3};
    std::set<int> s2 {3, 4, 5, 6};

    std::cout << overlap(s1, s2) << '\n';
}