我有一个包含 n 整数区间的巨大数据库表(例如{1-5},{4-16},{6434-114343}),需要找出哪个间隔相互重叠。有很多similar questions on SO,但区别在于我需要分别为每个区间返回一组重叠区间。
------------------ A -------------------
------ B ------- ----- D -----
--------- C ---------
对于此示例,输出将为A:{B,C,D} B:{A,C} C:{A,B} D:{A}
最坏的情况是,所有间隔可能相互重叠,产生大小为O的输出( n 2 )。这并不比天真的解决方案好(比较每对间隔)。然而,在实践中,我知道我的间隔很少会与其他间隔重叠,当它们发生时,最多只有5个其他间隔。
鉴于此信息,我该如何解决这个问题? (最好,我想要一个SQL查询解决方案,因为数据在数据库中,但我认为只有常规的算法解决方案是可能的。)
答案 0 :(得分:8)
针对您的问题的典型编程解决方案是在所有范围中构建interval tree,然后每个间隔执行一次查找,这会在O(log n)
时间内为您提供所有相交间隔的列表。以下是这种间隔树的示例:
但是,在您的情况下,您也会将主键存储在树节点中,因此给定以下日期(查找重叠日期是可以使用间隔树解决的常见问题)
你的树看起来像这样
因此,如果我想知道哪些区间与C重叠,我搜索C的开始,1843,树告诉我,这个值只在区间C内,这是我正在测试的区间所以我可以忽略它。然后我搜索C的结尾,1907,树告诉我,它在区间A,B和C中,我再次忽略C,因此我的结果集是A和B.
我承认,在这样的树中查找并不像人们所期望的那样直观。我会尝试在这里尽可能好地解释它:你从顶部根节点开始,每个节点决定向左或向右走,直到你到达一个离开节点(一个没有子节点的节点)。如果节点值大于您要搜索的值,则向左移动。如果节点值小于您要搜索的值,则转到右侧。如果节点值完全等于您要搜索的值,该怎么办?这取决于!如果您正在搜索间隔的开始,则相等的值意味着您向右移动,如果您搜索间隔的结束,则相等的值意味着您向左移动。这是非常重要的。到达离开节点后,您已完成并且在前往该离开节点的途中在任何节点中找到所有间隔,包括存储在离开节点本身中的间隔(如果有的话)组成你的结果集,而不仅仅是存储在离开节点中的间隔。这意味着您必须收集在执行搜索时遇到的任何时间间隔。
现在回到最初的问题:这一切都可以在SQL中完成吗?是的,可以做到。不过,我不确定你是否真的想这样做。您可以将当前SQL表数据转换为表示间隔树的SQL表,然后直接在该间隔树表中执行查找。至少我找到了那样做的人。他试图找到覆盖给定日期的所有日期范围,而不必将日期与数据库中的所有现有范围进行比较:
他甚至使用一个漂亮的技巧来优化查找速度,显着降低CPU使用率,构建查找表和执行实际查找(这使得整个过程非常复杂)。
答案 1 :(得分:2)
构建一个间隔开始和结束的排序序列,然后遍历它,每次更新当前间隔列表时,报告任何新找到的交叉点。
这样的事情:
std::vector<TBorder> borders;
for(auto i=intervals.begin();i!=intervals.end();++i)
{
borders.push_back(TBorder(i.Start(),Start));
borders.push_back(TBorder(i.End(),End));
}
std::sort(borders.begin(),borders.end());
std::set<int> currentIntervals;
for(auto b=borders.begin();b!=borders.end();++b)
{
if(b.IsEnd())
currentIntervals.erase(b.IntervalIndex());
else
{
currentIntervals.insert(b.IntervalIndex());
if(currentIntervals.size()>1)
ReportIntersection(currentIntervals);
}
}
通常它是O(n * log n)(假设交叉点的数量是O(1))。
但是,如果你已经按照例如开始,可能的排序可以在O(n)中完成(再次假设交叉点的数量是O(1))。