假设我有两个间隔集合,名为A和B.如何以最节省时间和内存的方式找到差异(相对补充)?
图片说明:
区间端点是整数(≤2 128 -1)并且它们总是长2 n 并且在m×2上对齐 n 晶格(所以你可以用它们制作二叉树)。
间隔可以在输入中重叠,但这不会影响输出(如果展平,结果会相同)。
问题是因为两个集合中有多个间隔(最多100,000,000),所以天真的实现可能会很慢。
从两个文件中读取输入,并按照这样的方式对输入进行排序:较小的子间隔(如果重叠)紧接在父项之后按大小排序。例如:
[0,7]
[0,3]
[4,7]
[4,5]
[8,15]
...
到目前为止,我一直致力于生成二进制搜索树的实现,同时聚合两个集合中的相邻间隔([0,3],[4,7] => [0,7]
),然后遍历第二个树并“突破”间隔存在于两者中(必要时在第一个树中细分较大的间隔)。
虽然这似乎适用于小型集合,但它需要越来越多的RAM来保存树本身,更不用说完成从树中插入和删除所需的时间。
我认为由于间隔是预先排序的,我可以使用一些动态算法并在一次通过中完成。但是,我不确定这是否可行。
那么,我将如何以有效的方式解决这个问题呢?
免责声明:这不是作业,而是对我所面临的实际现实问题的修改/概括。我用C ++编程,但我可以接受任何[命令式]语言的算法。
答案 0 :(得分:7)
回想一下我们在学校回来的第一次编程练习 - 写一个计算器程序。从输入行获取算术表达式,解析它并进行评估。还记得跟踪括号的深度吗?所以我们走了。
类比:间隔起点是开括号,结束点 - 结束括号。我们跟踪括号深度(嵌套)。两个交点间隔的深度,一个深度 - 间隔的差异
算法:
答案 1 :(得分:6)
您的间隔时间很长。你可以在几乎没有记忆的线性时间内完成这项工作。
首先“展平”你的两套。这是针对集合A,从最低间隔开始,并组合任何重叠间隔,直到您设置了没有重叠的间隔。然后为B做那个。
现在拿两组,从前两个区间开始。我们将这些称为A和B,Ai和Bi的区间索引。
Ai将A中的第一个间隔编入索引,B是B中的第一个间隔。
虽然有一些间隔需要处理,但请执行以下操作:
考虑两个区间的起点,起点是否相同?如果是这样,将两个间隔的起点提前到较小间隔的终点,则不向输出发射任何内容。将较小间隔的索引前进到下一个间隔。 (即如果Ai在Bi之前结束,则Ai前进到下一个间隔。)如果两个间隔都在同一个地方结束,则前进Ai和Bi并且不发出任何内容。
一个起点是否早于另一个起点?如果是,则从较早的起始点发出间隔到a)后一个终点的开始,或b)较早结束点的结束。如果选择选项b,则提前预测片段的索引。
因此,例如,如果Ai的间隔首先开始,则发出从Ai的开始到Bi的开始或者以Ai为小的结束的间隔。如果Ai在Bi开始之前就结束了,你就会推进艾。
重复直到消耗所有间隔。
聚苯乙烯。我假设您没有备用内存来将两个间隔集平展为单独的缓冲区。这有两个功能。一个“获取下一个间隔”函数,用于推进间隔索引,根据需要进行展平,并将展平数据提供给差分函数。
答案 2 :(得分:4)
您要找的是Sweep line algorithm。 一个简单的逻辑应该告诉你扫描线何时与A和B中的间隔相交,并且它只与一组相交。
这与此problem非常相似。只要考虑一组垂直线穿过B段的终点。
该算法复杂度为O((m + n)log(m + n)),这是初始排序的成本。排序集上的扫描线算法需要O(m + n)
答案 3 :(得分:2)
我认为你应该使用boost.icl(Interval容器库) http://www.boost.org/doc/libs/1_50_0/libs/icl/doc/html/index.html
#include <iostream>
#include <boost/icl/interval_set.hpp>
using namespace boost::icl;
int main()
{
typedef interval_set<int> TIntervalSet;
TIntervalSet intSetA;
TIntervalSet intSetB;
intSetA += discrete_interval<int>::closed( 0, 2);
intSetA += discrete_interval<int>::closed( 9,15);
intSetA += discrete_interval<int>::closed(12,15);
intSetB += discrete_interval<int>::closed( 1, 2);
intSetB += discrete_interval<int>::closed( 4, 7);
intSetB += discrete_interval<int>::closed( 9,10);
intSetB += discrete_interval<int>::closed(12,13);
std::cout << intSetA << std::endl;
std::cout << intSetB << std::endl;
std::cout << intSetA - intSetB << std::endl;
return 0;
}
打印
{[0,2][9,15]}
{[1,2][4,7][9,10][12,13]}
{[0,1)(10,12)(13,15]}