Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/algorithm/10.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 两个集合的有效交集_C++_Algorithm_Data Structures - Fatal编程技术网

C++ 两个集合的有效交集

C++ 两个集合的有效交集,c++,algorithm,data-structures,C++,Algorithm,Data Structures,我有两个集合(或地图),需要有效地处理它们的交集。 我知道有两种方法可以做到这一点: 在两个映射上迭代,如std::set_交叉点:O(n1+n2) 在一个映射上迭代并在另一个映射中查找元素:O(n1*log(n2)) 根据大小的不同,这两种解决方案中的任何一种都要好得多(已经计时了),因此我需要根据大小在这些算法之间切换(这有点混乱),或者找到一种优于这两种算法的解决方案,例如,使用map.find()的某个变体,将前面的迭代器作为提示(类似于map.emplace_提示(…)-但我找不到

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

  • 在两个映射上迭代,如std::set_交叉点:O(n1+n2)
  • 在一个映射上迭代并在另一个映射中查找元素:O(n1*log(n2))
根据大小的不同,这两种解决方案中的任何一种都要好得多(已经计时了),因此我需要根据大小在这些算法之间切换(这有点混乱),或者找到一种优于这两种算法的解决方案,例如,使用map.find()的某个变体,将前面的迭代器作为提示(类似于map.emplace_提示(…)-但我找不到这样的函数

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

请注意,性能要求使这与前面的问题不同,例如

关于性能要求,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)
x'=设置最小长度([x,y])
y'=用最大长度([x,y])设置

如果(x'.length*log(y'.length))在几乎所有情况下
std::set_交叉点将是最佳选择。
只有当集合包含非常少的元素时,另一种解决方案可能更好。
由于以2为底的原木的性质。
其比例如下:

n=2,对数(n)=1
n=4,对数(n)=2
n=8,对数(n)=3
..…
n=1024对数(n)=10

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

将这样的函数添加到STL中并以这样的方式实现是有原因的。它还将使代码更具可读性


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

对于实现为二叉树的集合,实际上有一种算法结合了您提到的这两个过程的优点。本质上,您可以像std::set_交叉一样进行合并,但在一棵树中进行迭代,则跳过所有小于另一个中当前值的分支

生成的交点需要O(min(n1 log n2,n2 log n1,n1+n2),这正是您想要的

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

但是,我在过去做过几次,在连接反向索引和类似的事情时。通常我使用skipTo(x)操作生成迭代器,该操作将前进到下一个元素>=x。为了满足我承诺的复杂性,它必须能够在对数(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;
        }
    }
}
void get_相交(向量*dest,常数集set1,常数集set2)
{
auto end1=set1.end();
auto end2=set2.end();
自动it1=set1.begin();
如果(it1==end1)
返回;
自动it2=set2.begin();
如果(it2==end2)
返回;
对于(;;)
{
it1.skipTo(*it2);
如果(it1==end1)
打破
如果(*it1==*it2)
{
dest->push_back(*it1);
++it1;
}
it2.skipTo(*it1);
如果(it2==end2)
打破
如果(*it2==*it1)
{
目的->推回(*it2);
++it2;
}
}
}

它可以使用迭代器向量轻松地扩展到任意数量的集合,几乎任何有序集合都可以扩展以提供所需的迭代器——排序数组、二叉树、b-树、跳过列表等。

我不知道如何使用标准库来实现这一点,但是如果您编写了自己的平衡二叉搜索树,请看这里是如何实现有限的“带提示的查找”。(根据您的其他要求,重新实现BST也可以省去父指针,这可能是对STL的性能优势。)

假设提示值小于要查找的值,并且我们知道提示节点所属左子树的提示节点的祖先堆栈。首先通常在提示节点的右子树中搜索,根据需要将节点推到堆栈上(为下次准备提示)。如果这不起作用,则当堆栈顶部节点的值小于查询值时,弹出堆栈。从最后一个弹出的节点(如果有)进行搜索,并根据需要推送


我声称,当使用这种机制以升序顺序连续搜索值时,(1)每个树边最多遍历一次,(2)每个查找最多遍历两条降序路径的边。在一个有n2个节点的二叉树中,给定2*n1条降序路径,边的代价是O(n1 log n2)。它也是O(n2),因为每条边只遍历一次。

什么是“性能要求”这与链接问题不同吗?你只是说你需要有效地解决它,而另一个问题要求有效地解决它……性能要求在不同的调用之间动态变化,因此我不能静态地选择一个备选方案。链接问题中没有解决这一部分。在这个优化级别上(不仅仅是使用标准库)我们真的需要查看样本数据和基准测试。一旦您获得实际数据、编译器和硬件,您就可以始终进行更多优化。如果没有这些信息,这个问题实际上与链接的问题没有太大区别,尽管它表示愿意根据手头的情况切换方法(标准库可能已经这样做了)。@wally标准集合交叉点的实现可能会转换方法,但有任何实现会这样做吗?如果有,如何实现?什么