【问题标题】:What's the most efficient way to test if two ranges overlap?测试两个范围是否重叠的最有效方法是什么?
【发布时间】:2011-03-17 05:08:41
【问题描述】:

给定两个包含范围 [x1:x2] 和 [y1:y2],其中 x1 ≤ x2y1 ≤ 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);
}

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

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

【问题讨论】:

标签: performance range comparison


【解决方案1】:

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

x1 <= C <= x2

y1 <= C <= y2

为避免混淆,请考虑以下范围: [x1:x2] 和 [y1:y2]

现在,如果我们可以假设范围是格式良好的(使得 x1

x1 <= y2 && y1 <= x2

(StartA = StartB)

【讨论】:

  • 我想应该是x1 &lt;= y2 &amp;&amp; y1 &gt;= x2吧?
  • @DavidBeck:不,如果 y1 > x2 那么范围肯定不会重叠(例如,考虑 [1:2] 和 [3:4]:y1 = 3 和 x2 = 2,所以 y1 > x2,但没有重叠)。
  • 如果你再解释一下原因,这将是一个更好的答案
  • @Vineet Deoraj - 为什么你认为它不起作用? x1 = 1, y1 = 1, x2 = 1, y2 = 1,所以 x1
【解决方案2】:

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

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

【讨论】:

  • @uyuyuy99 - 只是效率不高,因为当每秒执行多次此检查时,调用函数是您希望避免的事情,并且自己做尽可能多的数学运算,保持基本
  • @vsync 现代浏览器会内联和优化 Math.max 等函数,对性能应该没有明显影响。
  • @AshtonWar - 很有趣。你有一篇文章解释什么是内联的,什么不是?
  • @vsync 没有,但我相信你可以自己找到信息
  • 另外,请注意min(x2,y2) - max(x1,y1) 提供了重叠量,以备您需要时使用。
【解决方案3】:

这很容易扭曲正常人的大脑,所以我找到了一种更容易理解的视觉方法:

文件说明

如果两个范围“太胖”无法放入正好是两者宽度之和的槽中,那么它们会重叠。

对于[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!
}

【讨论】:

  • 案例比您的图片中描述的要多。例如,如果 w2 在 w1 之前开始并在 w1 之后结束怎么办?
  • @WilliamKF 逻辑成立
  • 同意,但我认为提供第三张图片可能会有所帮助。
  • @WilliamKF 那么你需要更多的图像有 16 种不同的组合可以放置 2 个范围...
  • 如果你使用这个方法要小心,因为和a2 - a1 + b2 - b1会溢出。要修复它,请将公式重新排列为 max(a2, b2) - a2 - b2 &lt; min(a1, b1) - a1 - b1,它简化为 max(a1, b1) &lt; min(a2, b2),节省一些算术并避免任何可能的溢出(这是 AXE-Labs 在下面的答案)。在你知道b2-b1=a2-a1 的特殊情况下,FloatingRock 公式的另一个有用的重新排列是max(a2, b2) - min(a1, b1) - (b2 - b1) &lt; a2-a1,它变成了abs(b1-a1) &lt; a2 - a1
【解决方案4】:

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

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

dont_overlap = x2 < y1 || x1 > y2

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

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

【讨论】:

  • 对我来说,更容易理解的表达式是:x2
【解决方案5】:

从开始的最大值中减去范围末端的最小值似乎可以解决问题。如果结果小于或等于零,我们就有重叠。这很好地可视化了它:

【讨论】:

  • 这涵盖所有情况
【解决方案6】:

我想问题是关于最快的,而不是最短的代码。最快的版本必须避免分支,所以我们可以这样写:

对于简单的情况:

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);
};

【讨论】:

  • 相信你的编译器。表达式 x1 &lt;= y2 &amp;&amp; y1 &lt;= x2 doesn't have any branches in it 或者,假设编译器和 CPU 架构相当称职(即使在 2010 年)。实际上,在 x86 上,生成的代码对于简单表达式与此答案中的代码基本相同。
【解决方案7】:

如果你正在处理,给定两个范围[x1:x2][y1:y2],自然/反自然顺序范围同时在哪里:

  • 自然顺序:x1 &lt;= x2 &amp;&amp; y1 &lt;= y2
  • 反自然秩序:x1 &gt;= x2 &amp;&amp; y1 &gt;= y2

那么你可能想用这个来检查:

它们是重叠的 (y2 - x1) * (x2 - y1) &gt;= 0

只涉及四个操作:

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

【讨论】:

    【解决方案8】:
    return x2 >= y1 && x1 <= y2;
    

    【讨论】:

    • 这是不正确的。因为x1 &lt;= y1 &amp;&amp; x2 &gt;= y2 || x1 &gt;= y1 &amp;&amp; x2 &lt;= y2 也应该返回true。
    【解决方案9】:

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

    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
    

    【讨论】:

      【解决方案10】:

      逆向思考:如何使两个范围不重叠?给定[x1, x2],那么[y1, y2] 应该是outside [x1, x2],即y1 &lt; y2 &lt; x1 or x2 &lt; y1 &lt; y2,相当于y2 &lt; x1 or x2 &lt; y1

      因此,使两个范围重叠的条件:not(y2 &lt; x1 or x2 &lt; y1),相当于y2 &gt;= x1 and x2 &gt;= y1(与西蒙接受的答案相同)。

      【讨论】:

      • 看起来与 @damluar 回答的内容相同(2016 年 3 月 2 日,17:36)
      【解决方案11】:

      您已经拥有最有效的表示 - 这是需要检查的最低限度,除非您确定 x1

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

      【讨论】:

      • 所有编译器都会。所有(据我所知)当前使用的具有 C 风格语法(C、C++、C#、Java 等)的语言都使用短路布尔运算符,它是管理这些语言的各种标准的一部分。如果左侧值的结果足以确定运算结果,则不计算右侧值。
      • Mark H -- 如果可以的话,编译器会跳过第二个子句:所以如果你有一个函数说: foo(int c) { int i=0; if (c
      【解决方案12】:

      我的情况不同。我想检查两个时间范围重叠。不应有单位时间重叠。这是 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")
          }
      

      你可以看到边界比较中有异或模式

      【讨论】:

        【解决方案13】:

        鉴于: [x1,x2] [y1,y2] 然后x1 &lt;= y2 || x2 &gt;= y1 将始终有效。 作为

              x1 ... x2
        y1 .... y2
        

        如果x1 &gt; y2 那么它们不重叠 或

        x1 ... x2
            y1 ... y2
        

        如果x2 &lt; y1 它们不重叠。

        【讨论】:

          【解决方案14】:

          没什么新鲜的。只是更具可读性。

          def overlap(event_1, event_2):
          
              start_time_1 = event_1[0]
              end_time_1 = event_1[1]
          
              start_time_2 = event_2[0]
              end_time_2 = event_2[1]
          
              start_late = max(start_time_1, start_time_2)
              end_early = min(end_time_1, end_time_2)
          
          
              # The event that starts late should only be after the event ending early.
              if start_late > end_early:
                  print("Absoloutly No overlap!")
              else:
                  print("Events do overlap!")
          

          【讨论】:

            【解决方案15】:

            重叠 (X, Y) := if (X1

            证明:

            考虑 X 在 Y 之前或与 Y 左对齐的情况,即 X1

            在 Y 先于 X 的互补情况下,相同的逻辑适用于交换的实体。

            所以,

            重叠 (X, Y) := if (X1

            但这似乎不太正确。在递归调用中,第一个测试是多余的,因为我们已经从第一次调用的第一个测试中知道了实体的相对位置。所以,我们真的只需要测试第二个条件,在交换时,它是 (X1

            重叠 (X, Y) := if (X1

            QED。

            在 Ada 中的实现:

               type Range_T is array (1 .. 2) of Integer;
            
               function Overlap (X, Y: Range_T) return Boolean is
                 (if X(1) <= Y(1) then Y(1) <= X(2) else X(1) <= Y(2));
            

            测试程序:

            with Ada.Text_IO; use Ada.Text_IO;
            
            procedure Main is
            
               type Range_T is array (1 .. 2) of Integer;
            
               function Overlap (X, Y: Range_T) return Boolean is
                 (if X(1) <= Y(1) then Y(1) <= X(2) else X(1) <= Y(2));
            
               function Img (X: Range_T) return String is
                 (" [" & X(1)'Img & X(2)'Img & " ] ");
            
               procedure Test (X, Y: Range_T; Expect: Boolean) is
                  B: Boolean := Overlap (X, Y);
               begin
                  Put_Line
                    (Img (X) & " and " & Img (Y) &
                     (if B then " overlap .......... "
                           else " do not overlap ... ") &
                     (if B = Expect then "PASS" else "FAIL"));
               end;
                     
            begin
               Test ( (1, 2), (2, 3), True);  --  chained
               Test ( (2, 3), (1, 2), True);
            
               Test ( (4, 9), (5, 7), True);  --  inside
               Test ( (5, 7), (4, 9), True);
            
               Test ( (1, 5), (3, 7), True);  --  proper overlap
               Test ( (3, 7), (1, 5), True);
            
               Test ( (1, 2), (3, 4), False);  -- back to back
               Test ( (3, 4), (1, 2), False);
            
               Test ( (1, 2), (5, 7), False);  -- disjoint
               Test ( (5, 7), (1, 2), False);
            end;
            

            上述程序的输出:

             [ 1 2 ]  and  [ 2 3 ]  overlap .......... PASS
             [ 2 3 ]  and  [ 1 2 ]  overlap .......... PASS
             [ 4 9 ]  and  [ 5 7 ]  overlap .......... PASS
             [ 5 7 ]  and  [ 4 9 ]  overlap .......... PASS
             [ 1 5 ]  and  [ 3 7 ]  overlap .......... PASS
             [ 3 7 ]  and  [ 1 5 ]  overlap .......... PASS
             [ 1 2 ]  and  [ 3 4 ]  do not overlap ... PASS
             [ 3 4 ]  and  [ 1 2 ]  do not overlap ... PASS
             [ 1 2 ]  and  [ 5 7 ]  do not overlap ... PASS
             [ 5 7 ]  and  [ 1 2 ]  do not overlap ... PASS
            

            【讨论】:

              【解决方案16】:

              这是我的版本:

              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 是升序/降序(无论哪种方式,它都是排序的) - 不需要循环,您只需要检查头和尾元素。不过,我确实更喜欢最小/最大解决方案 - 仅仅是因为当您稍后回到代码时它更容易阅读。
              • -1:这不是微优化;这是选择合适的算法。当有一个简单的 O(1) 选择时,您的算法是 O(n)。
              • 当“过早的优化是万恶之源”成为无能者不可侵犯的宗教信条,而不是对某些偶尔的行为模式半认真的评论时,就会发生这种情况。
              猜你喜欢
              • 2017-01-12
              • 2015-04-06
              • 1970-01-01
              • 1970-01-01
              • 2017-06-28
              • 1970-01-01
              • 2015-06-26
              • 1970-01-01
              • 2012-07-10
              相关资源
              最近更新 更多