测试两个整数范围是否重叠的最有效方法是什么?


249

给定两个包含整数的范围[x1:x2]和[y1:y2],其中x1≤x2且y1≤y2,测试这两个范围是否存在重叠的最有效方法是什么?

一个简单的实现如下:

bool testOverlap(int x1, int x2, int y1, int y2) {
  return (x1 >= y1 && x1 <= y2) ||
         (x2 >= y1 && x2 <= y2) ||
         (y1 >= x1 && y1 <= x2) ||
         (y2 >= x1 && y2 <= x2);
}

但我希望有更有效的方法来进行计算。

就最少的操作而言,哪种方法是最有效的。


可能是有趣的是相关的一些- stackoverflow.com/q/17138760/104380
VSYNC

Answers:


452

范围重叠意味着什么?这意味着存在两个范围内的数字C,即

x1 <= C <= x2

y1 <= C <= y2

现在,如果允许我们假设范围的格式正确(因此x1 <= x2和y1 <= y2),则足以测试

x1 <= y2 && y1 <= x2

1
我相信应该x1 <= y2 && y1 >= x2,不是吗?
大卫·贝克

8
@DavidBeck:不,如果y1> x2,则范围绝对不重叠(例如,考虑[1:2]和[3:4]:y1 = 3和x2 = 2,因此y1> x2,但没有重叠) 。
西蒙·尼克森

8
如果您进一步解释原因,这将是一个更好的答案
shoosh

2
@Vineet Deoraj-您为什么认为它不起作用?x1 = 1,y1 = 1,x2 = 1,y2 = 1,因此x1 <= y2 && y1 <= x2为真,因此存在重叠。
dcp


137

给定两个范围[x1,x2],[y1,y2]

def is_overlapping(x1,x2,y1,y2):
    return max(x1,y1) <= min(x2,y2)

4
@ uyuyuy99-效率不高,因为每秒执行多次此检查时,您希望避免调用函数,并且自己进行大量数学运算,请
务必

7
@vsync现代浏览器将内联和优化Math.max之类的功能,对性能不会有明显影响。
阿什顿

1
@AshtonWar-有趣。您是否有一篇文章解释什么是内联的,什么不是内联的?
vsync

@vsync不,但是我敢肯定,您可以自己找到该信息
Ashton 6年

6
另外,请注意,min(x2,y2) - max(x1,y1)如果需要,可提供重叠量。
user1556435

58

这很容易扭曲正常的人脑,因此我发现一种视觉方法更容易理解:

重叠疯狂

解释

如果两个范围“太胖”以致无法恰好是两个宽度的总和,则它们会重叠。

对于范围[a1, a2][b1, b2]这将是:

/**
 * we are testing for:
 *     max point - min point < w1 + w2    
 **/
if max(a2, b2) - min(a1, b1) < (a2 - a1) + (b2 - b1) {
  // too fat -- they overlap!
}

2
情况比图片中所描绘的更多。例如,如果w2在w1之前开始并在w1之后结束怎么办?
WilliamKF 2014年

6
@WilliamKF逻辑正确
FloatingRock

2
同意,但我认为提供第三张照片可能会有所帮助。
WilliamKF

3
@WilliamKF,那么您需要更多的图像,可以在16个不同的组合中放置2个范围...
Peter

3
如果使用此方法,请小心,因为总和a2 - a1 + b2 - b1可能溢出。要解决此问题,请将公式重新排列为max(a2, b2) - a2 - b2 < min(a1, b1) - a1 - b1,从而简化为max(a1, b1) < min(a2, b2),从而节省一些算术并避免任何可能的溢出(这是AXE-Labs在下面的回答)。在您知道的特殊情况下,b2-b1=a2-a1FloatingRock公式的另一个有用的重排是max(a2, b2) - min(a1, b1) - (b2 - b1) < a2-a1,即abs(b1-a1) < a2 - a1
Paolo Bonzini

44

Simon的回答很好,但对我来说,考虑反向情况更容易。

2个范围何时不重叠?当其中一个在另一个结束之后开始时,它们不重叠:

dont_overlap = x2 < y1 || x1 > y2

现在很容易表达它们何时重叠:

overlap = !dont_overlap = !(x2 < y1 || x1 > y2) = (x2 >= y1 && x1 <= y2)

1
对我来说,更容易理解的表达式是:x2 <y1 || y2 <x1 // //我使用“小于”而不是“大于”。
Park JongBum的

24

从开始的最大值减去范围的端点的最小值似乎可以解决问题。如果结果小于或等于零,则存在重叠。这很好地形象化了:

在此处输入图片说明


1
这涵盖了所有情况
user3290180 2013年

10

我想问题是关于最快的代码,而不是最短的代码。最快的版本必须避免分支,因此我们可以编写如下代码:

对于简单的情况:

static inline bool check_ov1(int x1, int x2, int y1, int y2){
    // insetead of x1 < y2 && y1 < x2
    return (bool)(((unsigned int)((y1-x2)&(x1-y2))) >> (sizeof(int)*8-1));
};

或者,在这种情况下:

static inline bool check_ov2(int x1, int x2, int y1, int y2){
    // insetead of x1 <= y2 && y1 <= x2
    return (bool)((((unsigned int)((x2-y1)|(y2-x1))) >> (sizeof(int)*8-1))^1);
};

7
对您的编译器有信心。假设该编译器和CPU体系结构相当合理(即使在2010年),该表达式中x1 <= y2 && y1 <= x2 也没有任何分支。实际上,在x86上,对于简单表达式而言,生成的代码与该答案中的代码基本相同。
索伦Løvborg


4

如果要处理,则同时给定两个范围[x1:x2][y1:y2],自然/反自然顺序范围,其中:

  • 自然顺序:x1 <= x2 && y1 <= y2
  • 反自然秩序: x1 >= x2 && y1 >= y2

那么您可能要使用它来检查:

它们重叠<=> (y2 - x1) * (x2 - y1) >= 0

仅涉及四个操作:

  • 两个减法
  • 一个乘法
  • 一个比较

1

如果有人正在寻找可计算实际重叠的单线:

int overlap = ( x2 > y1 || y2 < x1 ) ? 0 : (y2 >= y1 && x2 <= y1 ? y1 : y2) - ( x2 <= x1 && y2 >= x1 ? x1 : x2) + 1; //max 11 operations

如果您要减少几个操作,但要增加几个变量:

bool b1 = x2 <= y1;
bool b2 = y2 >= x1;
int overlap = ( !b1 || !b2 ) ? 0 : (y2 >= y1 && b1 ? y1 : y2) - ( x2 <= x1 && b2 ? x1 : x2) + 1; // max 9 operations

1

想在逆方法:如何让2个范围不重叠?给定[x1, x2][y1, y2]则应位于外部 [x1, x2],即y1 < y2 < x1 or x2 < y1 < y2等于y2 < x1 or x2 < y1

因此,使2个范围重叠的条件是:not(y2 < x1 or x2 < y1),它等于y2 >= x1 and x2 >= y1(与Simon接受的答案相同)。


看起来与@damluar回答的内容相同(16年3月2日,17:36)
Nakilon

0

您已经拥有了最有效的表示形式-这是需要检查的最低要求,除非您确定x1 <x2等,然后再使用他人提供的解决方案。

您可能应该注意,某些编译器实际上会为您优化此过程-只要这4个表达式中的任何一个返回true便返回。如果一个返回true,则最终结果也将返回-因此可以跳过其他检查。


2
所有编译器都会。据我所知,所有目前使用的具有C样式语法(C,C ++,C#,Java等)的语言都使用短路的布尔运算符,并且它是管理这些语言的各种标准的一部分。如果左手值的结果足以确定运算结果,则不评估右手值。
乔纳森·格林斯潘

1
标记H-编译器将在可能的情况下跳过第二个子句:因此,如果您有一个函数说:foo(int c){int i = 0; 如果(c <3 || ++ i == argc)printf(“ Inside \ n”); printf(“ i is%d \ n”,i); Foo(2)将打印:内部i为0,而Foo(4)将打印:i为1(在gcc 4.4.3上进行了测试,但是我也依靠此行为来完成icc中的一些丑陋代码)
J Teller

0

我的情况不同。我想检查两个时间范围是否重叠。不应有单位时间重叠。这是Go实现。

    func CheckRange(as, ae, bs, be int) bool {
    return (as >= be) != (ae > bs)
    }

测试用例

if CheckRange(2, 8, 2, 4) != true {
        t.Error("Expected 2,8,2,4 to equal TRUE")
    }

    if CheckRange(2, 8, 2, 4) != true {
        t.Error("Expected 2,8,2,4 to equal TRUE")
    }

    if CheckRange(2, 8, 6, 9) != true {
        t.Error("Expected 2,8,6,9 to equal TRUE")
    }

    if CheckRange(2, 8, 8, 9) != false {
        t.Error("Expected 2,8,8,9 to equal FALSE")
    }

    if CheckRange(2, 8, 4, 6) != true {
        t.Error("Expected 2,8,4,6 to equal TRUE")
    }

    if CheckRange(2, 8, 1, 9) != true {
        t.Error("Expected 2,8,1,9 to equal TRUE")
    }

    if CheckRange(4, 8, 1, 3) != false {
        t.Error("Expected 4,8,1,3 to equal FALSE")
    }

    if CheckRange(4, 8, 1, 4) != false {
        t.Error("Expected 4,8,1,4 to equal FALSE")
    }

    if CheckRange(2, 5, 6, 9) != false {
        t.Error("Expected 2,5,6,9 to equal FALSE")
    }

    if CheckRange(2, 5, 5, 9) != false {
        t.Error("Expected 2,5,5,9 to equal FALSE")
    }

您可以看到边界比较中存在XOR模式


-10

这是我的版本:

int xmin = min(x1,x2)
  , xmax = max(x1,x2)
  , ymin = min(y1,y2)
  , ymax = max(y1,y2);

for (int i = xmin; i < xmax; ++i)
    if (ymin <= i && i <= ymax)
        return true;

return false;

除非您对数十亿个宽间隔的整数运行一些高性能范围检查器,否则我们的版本应具有相似的性能。我的观点是,这是微观优化。


我认为您已经超出了此处的规范。假设x1到x2是递增/递减的(两种方式都可以排序)-不需要循环,只需要检查头和尾元素即可。我确实更喜欢最小/最大解决方案-只是因为稍后再返回代码时,它更易于阅读。
Mark

12
-1:这不是微观优化;这是选择合适的算法。当有简单的O(1)选择时,您的算法为O(n)。
西蒙·尼克森

当“过早的优化是万恶之源”变成无能为力的宗教信条,而不是对某些偶尔的行为方式发表严肃的评论时,就会发生这种情况。
rghome
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.