Bentley-Ottmann算法:求N条线段的交点

Bentley-Ottmann算法:求N条线段的交点

Bentley-Ottmann算法

如果说你想知道这个方法的历史,那么推荐几篇远古论文:

  1. 来自Michael Ian Shamos和Dan Hoey在1976年发表的:
    M. I. Shamos and D. Hoey, “Geometric intersection problems,” 17th Annual Symposium on Foundations of Computer Science (sfcs 1976), 1976, pp. 208-215, doi: 10.1109/SFCS.1976.16.
    上链接:
    http://euro.ecom.cmu.edu/people/faculty/mshamos/1976GeometricIntersection.pdf
  2. 作者Bentley和Ottmann改进了上面的方法:
    Bentley and Ottmann, “Algorithms for Reporting and Counting Geometric Intersections,” in IEEE Transactions on Computers, vol. C-28, no. 9, pp. 643-647, Sept. 1979, doi: 10.1109/TC.1979.1675432.
    上链接:http://www.itseng.org/research/papers/topics/VLSI_Physical_Design_Automation/Physical_Verification/DRC/Geometric_Intersection_Problems/1979-Bentley.pdf

算法复杂度

我们求N条线段中任意两条线段的交点的个数:

1. 使用暴力求解,遍历每一条线段 i ,固定 i 遍历 j 与 i 是否存在交点:

// 暴力求解
for(int i = 0; i < segment.size(); i++)
{
	for(int j = 0; j < segment.size(); j++)
	{
		//判断是否相交
	}
}

算法时间复杂度为: O ( n 2 ) O(n^{2}) O(n2)
T ( n ) = ( n + n + n + . . . + n ) = n 2 T(n) = (n+n+n+...+n)=n^{2} T(n)=(n+n+n+...+n)=n2

2. 此时我们可以稍微优化一点算法复杂度,判断过 i, j 是否相交以后,j, i 就不用再判断了:

// 优化
for(int i = 0; i < segment.size(); i++)
{
	for(int j = i; j < segment.size(); j++)
	{
		//判断是否相交
	}
}

算法时间复杂度为: O ( n 2 ) O(n^{2}) O(n2)
T ( n ) = ( 1 + 2 + 3 + . . . + n ) = n 2 + n 2 T(n) = (1+2+3+...+n)=\frac{n^{2}+{n}}{2} T(n)=(1+2+3+...+n)=2n2+n

3. Bentley-Ottmann算法

排序算法时间复杂度为: O ( n l o g n ) O(nlogn) O(nlogn)
使用平衡二叉树进行插入、删除操作,时间复杂度为: O ( l o g n ) O(logn) O(logn)
事件队列最多存储 2 ∗ n + x 2*n+x 2n+x个点
(事件队列是什么意思?在算法案例中将讲解)
所以Bentley-Ottmann算法时间复杂度最大为: O ( ( 2 n + i ) l o g n ) = O ( n l o g n ) O((2n+i)logn)=O(nlogn) O((2n+i)logn)=O(nlogn)

算法案例(流程)

我们通过讲解下面这个案例来理解算法:如果想直接看论文转下
论文案例链接:
http://www.ams.sunysb.edu/~jsbm/courses/345/13/bentley-ottmann.pdf

1. 问题描述

给定7条线段,用 S S S表示
S = s 0 , s 1 , . . . , s 6 = { ( ( 15 , 17 ) , ( 7 , 3 ) ) , ( ( 3 , 16 ) , ( 6 , 10 ) ) , ( ( 2 , 15 ) , ( 12 , 8 ) ) , ( ( 7 , 14 ) , ( 11 , 6 ) ) , ( ( 10 , 13 ) , ( 2 , 4 ) ) , ( ( 1 , 12 ) , ( 9 , 5 ) ) , ( ( − 3 , 11 ) , ( 5 , 2 ) ) } \begin{aligned} S&={s_{0},s_{1},...,s_{6}} \\ &={\{((15,17),(7,3)), ((3,16),(6,10)),((2,15),(12,8)), ((7,14),(11,6)), }\\ & {((10,13),(2,4)), ((1,12),(9,5)), ((-3,11),(5,2))\}} \\ \end{aligned} S=s0,s1,...,s6={((15,17),(7,3)),((3,16),(6,10)),((2,15),(12,8)),((7,14),(11,6)),((10,13),(2,4)),((1,12),(9,5)),((3,11),(5,2))}
每条线段用 s i = ( a i , b i ) s_i=(a_i, b_i) si=(ai,bi)表示, a i a_i ai表示起始点, b i b_i bi表示末尾点。
如图:Joe Mitchell的例子

2. 名词介绍

(1)Event Queue(事件队列):后续用Q表示

该算法通过维护一个队列的入队、出队来求所有的交点,而该队列称为事件队列。

(2)Sweep Status(扫描线):后续用L表示

使用一条平行于 x x x 轴或者平行于 y y y 轴的直线遍历所有线段的端点以及交点,有图有真相:

(3) 交点:用 x i j x_{ij} xij 表示, i , j i, j i,j 表示相交的两条线段的下标

3. 案例讲解

在这里插入图片描述

(1)排序: 将所有端点按照 y y y 坐标大小进行排序,存在 Q Q Q 中,得到第 “-” 行。案例按照 y y y 从大到小排序
(2)3个原子操作
a. 当扫描线扫描到线段初始点时,进行出队操作,取出队顶元素,判断相邻线段与当前线段是否相交。相交时将交点入队,将其插入到按 y y y 排序的队列中;

假如当前 L = ( s 2 , s 1 , s 3 , s 0 ) L=(s_{2},s_{1},s_{3},s_{0}) L=(s2,s1,s3,s0),判断 s 1 s_1 s1 与相邻线段 s 2 、 s 3 s_{2}、s{3} s2s3 是否相交。

b. 当扫描线扫描到线段交点时,将交点 x i j x_{ij} xij 进行出队操作,并且将 L L L 中对应的 i , j i,j i,j 交换位置,在重新判断 i , j i,j i,j 与各自相邻线段是否相交,相交时,将其插入到按 y y y 排序的队列中;
c. 当扫描线扫描到线段末尾点时,将右端点进行出队操作,从 L L L 中将 s i s_i si 去除。此时判断与 s i s_i si 相邻的两个线段是否相交,相交时,将其插入到按 y y y 排序的队列中。
(3)按照表格左侧 Event 来解析每一步的含义
  1. 扫描线扫描到 a 0 a_0 a0,从队列 Q Q Q 中出队,将 a 0 a_0 a0 所对应的线段 s 0 s_0 s0 添加到 L L L 中, L L L 中只有一条线段,无需判断是否相交;

  2. 扫描线扫描到 a 1 a_1 a1,从队列 Q Q Q 中出队,将 a 1 a_1 a1 所对应的线段 s 1 s_1 s1 添加到 L L L 中, L L L 中有两条线段,判断是否相交,结果不相交,进行下一步;
    注意:此时问题来了,将 s 1 s_1 s1 添加到 s 0 s_0 s0 的前面还是后面呢?
    答:按照扫描线的 x x x 的大小,从小到大排序。
    现在 L L L 经过了 s 0 , s 1 s_0,s_1 s0,s1 将扫描线与 s 0 , s 1 s_0,s_1 s0,s1 的交点按照 x x x 从小到大排序。将 s 1 s_1 s1 放在 s 0 s_0 s0 前面。

    在这里插入图片描述

  3. 扫描线扫描到 a 2 a_2 a2,从队列 Q Q Q 中出队,将 a 2 a_2 a2 所对应的线段 s 2 s_2 s2 添加到 L L L 中(按照 x x x 升序添加: L = ( s 2 , s 1 , s 0 ) L=(s_2,s_1,s_0) L=(s2,s1,s0)), L L L 中有三条线段,判断 s 2 s_2 s2 与相邻线段 s 1 s_1 s1 是否相交(注意,此时 s 2 s_2 s2 L L L 中相邻的线段只有 s 1 s_1 s1, 而没有 s 5 s_5 s5 ),结果相交,将交点 x 12 x_{12} x12插入队列 Q Q Q 中(按 y y y 大小排序的位置中);
    在这里插入图片描述

  4. 扫描线扫描到 a 3 a_3 a3,从队列 Q Q Q 中出队,将 a 3 a_3 a3 所对应的线段 s 3 s_3 s3 添加到 L L L 中(按照 x x x 升序添加: L = ( s 2 , s 1 , s 3 , s 0 ) L=(s_2,s_1,s_3,s_0) L=(s2,s1,s3,s0)), L L L 中有四条线段,判断 s 3 s_3 s3 与相邻线段 s 1 、 s 0 s_1、s_0 s1s0 是否相交,结果 s 3 s_3 s3 s 0 s_0 s0 相交,将交点 x 03 x_{03} x03插入队列 Q Q Q 中(按 y y y 大小排序的位置中);

  5. 扫描线扫描到交点 x 12 x_{12} x12,从队列 Q Q Q 中出队,交换 L = ( s 2 , s 1 , s 3 , s 0 ) L=(s_2,s_1,s_3,s_0) L=(s2,s1,s3,s0) 中交点所对应的线段 s 1 , s 2 s_1,s_2 s1,s2 的位置: L = ( s 1 , s 2 , s 3 , s 0 ) L=(s_1,s_2,s_3,s_0) L=(s1,s2,s3,s0),重新判断 s 1 , s 2 s_1,s_2 s1,s2 与各自的相邻线段是否存在交点,此时 s 2 s_2 s2 与相邻线段 s 3 s_3 s3 相交,将交点 x 23 x_{23} x23 插入队列 Q Q Q 中(按 y y y 大小排序的位置中);
    在这里插入图片描述

  6. 扫描线扫描到 a 4 a_4 a4,从队列 Q Q Q 中出队,将 a 4 a_4 a4 所对应的线段 s 4 s_4 s4 添加到 L L L 中(按照 x x x 升序添加: L = ( s 1 , s 2 , s 3 , s 4 , s 0 ) L=(s_1,s_2,s_3,s_4,s_0) L=(s1,s2,s3,s4,s0) L L L 中有五条线段,判断 s 4 s_4 s4 与相邻线段 s 3 、 s 0 s_3、s_0 s3s0 是否相交,结果 s 4 s_4 s4 s 3 s_3 s3 相交,将交点 x 34 x_{34} x34插入队列 Q Q Q 中(按 y y y 大小排序的位置中);
    在这里插入图片描述

  7. 扫描线扫描到 a 5 a_5 a5,从队列 Q Q Q 中出队,将 a 5 a_5 a5 所对应的线段 s 5 s_5 s5 添加到 L L L 中(按照 x x x 升序添加: L = ( s 5 , s 1 , s 2 , s 3 , s 4 , s 0 ) L=(s_5,s_1,s_2,s_3,s_4,s_0) L=(s5,s1,s2,s3,s4,s0) L L L 中有六条线段,判断 s 5 s_5 s5 与相邻线段 s 1 s_1 s1 是否相交,结果 s 5 s_5 s5 s 1 s_1 s1 不相交,转下一步;
    在这里插入图片描述

  8. 扫描线扫描到交点 x 34 x_{34} x34,从队列 Q Q Q 中出队,交换 L = ( s 5 , s 1 , s 2 , s 3 , s 4 , s 0 ) L=(s_5,s_1,s_2,s_3,s_4,s_0) L=(s5,s1,s2,s3,s4,s0)) 中交点所对应的线段 s 3 , s 4 s_3,s_4 s3,s4 的位置: L = ( s 5 , s 1 , s 2 , s 4 , s 3 , s 0 ) L=(s_5,s_1,s_2,s_4,s_3,s_0) L=(s5,s1,s2,s4,s3,s0),重新判断 s 3 , s 4 s_3,s_4 s3,s4 与各自的相邻线段是否存在交点
    此时 s 3 s_3 s3 与相邻线段 s 0 s_0 s0 相交,但是 x 03 x_{03} x03 已经存在于队列 Q Q Q 中了,所以不用重新加入
    s 4 s_4 s4 与相邻线段 s 2 s_2 s2 相交,将交点 x 24 x_{24} x24 插入队列 Q Q Q 中(按 y y y 大小排序的位置中);
    在这里插入图片描述

  9. 扫描线扫描到 a 6 a_6 a6,从队列 Q Q Q 中出队,将 a 6 a_6 a6 所对应的线段 s 6 s_6 s6 添加到 L L L 中(按照 x x x 升序添加: L = ( s 6 , s 5 , s 1 , s 2 , s 4 , s 3 , s 0 ) L=(s_6,s_5,s_1,s_2,s_4,s_3,s_0) L=(s6,s5,s1,s2,s4,s3,s0) L L L 中有七条线段,判断 s 6 s_6 s6 与相邻线段 s 5 s_5 s5 是否相交,结果 s 5 s_5 s5 s 1 s_1 s1 不相交,转下一步;
    在这里插入图片描述

  10. 扫描线扫描到交点 x 24 x_{24} x24,从队列 Q Q Q 中出队,交换 L = ( s 6 , s 5 , s 1 , s 2 , s 4 , s 3 , s 0 ) L=(s_6,s_5,s_1,s_2,s_4,s_3,s_0) L=(s6,s5,s1,s2,s4,s3,s0) 中交点所对应的线段 s 2 , s 4 s_2,s_4 s2,s4 的位置: L = ( s 6 , s 5 , s 1 , s 4 , s 2 , s 3 , s 0 ) L=(s_6,s_5,s_1,s_4,s_2,s_3,s_0) L=(s6,s5,s1,s4,s2,s3,s0),重新判断 s 2 , s 4 s_2,s_4 s2,s4 与各自的相邻线段是否存在交点
    此时 s 2 s_2 s2 与相邻线段 s 3 s_3 s3 相交,但是 x 23 x_{23} x23 已经存在于队列 Q Q Q 中了,所以不用重新加入
    s 4 s_4 s4 与相邻线段 s 1 s_1 s1 不相交,转下一步;
    在这里插入图片描述

  11. 扫描线扫描到交点 x 23 x_{23} x23,从队列 Q Q Q 中出队,交换 L = ( s 6 , s 5 , s 1 , s 4 , s 2 , s 3 , s 0 ) L=(s_6,s_5,s_1,s_4,s_2,s_3,s_0) L=(s6,s5,s1,s4,s2,s3,s0) 中交点所对应的线段 s 2 , s 4 s_2,s_4 s2,s4 的位置: L = ( s 6 , s 5 , s 1 , s 4 , s 3 , s 2 , s 0 ) L=(s_6,s_5,s_1,s_4,s_3,s_2,s_0) L=(s6,s5,s1,s4,s3,s2,s0),重新判断 s 2 , s 3 s_2,s_3 s2,s3 与各自的相邻线段是否存在交点
    此时 s 2 s_2 s2 与相邻线段 s 0 s_0 s0 相交,但是 x 02 x_{02} x02 已经存在于队列 Q Q Q 中了,所以不用重新加入
    s 3 s_3 s3 与相邻线段 s 1 s_1 s1 不相交,转下一步;
    在这里插入图片描述

  12. 扫描线扫描到末尾端点 b 1 b_{1} b1,从队列 Q Q Q 中出队,删除 L L L 中的 s 1 s_1 s1 L = ( s 6 , s 5 , s 4 , s 3 , s 2 , s 0 ) L=(s_6,s_5,s_4,s_3,s_2,s_0) L=(s6,s5,s4,s3,s2,s0),判断与 s 1 s_1 s1 相邻的线段 s 5 , s 4 s_5,s_4 s5,s4 是否相交,结果 s 5 s_5 s5 s 4 s_4 s4 相交,将交点 x 45 x_{45} x45 插入队列 Q Q Q 中(按 y y y 大小排序的位置中);
    在这里插入图片描述

  13. 扫描线扫描到交点 x 02 x_{02} x02,从队列 Q Q Q 中出队,交换 L = ( s 6 , s 5 , s 4 , s 3 , s 2 , s 0 ) L=(s_6,s_5,s_4,s_3,s_2,s_0) L=(s6,s5,s4,s3,s2,s0) 中交点所对应的线段 s 2 , s 0 s_2,s_0 s2,s0 的位置: L = ( s 6 , s 5 , s 4 , s 3 , s 0 , s 2 ) L=(s_6,s_5,s_4,s_3,s_0,s_2) L=(s6,s5,s4,s3,s0,s2),重新判断 s 2 , s 0 s_2,s_0 s2,s0 与各自的相邻线段是否存在交点
    此时 s 3 s_3 s3 与相邻线段 s 0 s_0 s0 相交,但是 x 03 x_{03} x03 已经存在于队列 Q Q Q 中了,所以不用重新加入
    在这里插入图片描述

  14. 扫描线扫描到交点 x 03 x_{03} x03,从队列 Q Q Q 中出队,交换 L = ( s 6 , s 5 , s 4 , s 3 , s 0 , s 2 ) L=(s_6,s_5,s_4,s_3,s_0,s_2) L=(s6,s5,s4,s3,s0,s2) 中交点所对应的线段 s 0 , s 3 s_0,s_3 s0,s3 的位置: L = ( s 6 , s 5 , s 4 , s 0 , s 3 , s 2 ) L=(s_6,s_5,s_4,s_0,s_3,s_2) L=(s6,s5,s4,s0,s3,s2),重新判断 s 0 , s 3 s_0,s_3 s0,s3 与各自的相邻线段是否存在交点
    此时 s 3 s_3 s3 与相邻线段 s 2 s_2 s2 相交,但是 x 23 x_{23} x23 已经遍历过了,所以不用重新加入
    s 0 s_0 s0 与相邻线段 s 4 s_4 s4 不相交,转下一步;
    在这里插入图片描述

  15. 扫描线扫描到交点 x 45 x_{45} x45,从队列 Q Q Q 中出队,交换 L = ( s 6 , s 5 , s 4 , s 0 , s 3 , s 2 ) L=(s_6,s_5,s_4,s_0,s_3,s_2) L=(s6,s5,s4,s0,s3,s2)) 中交点所对应的线段 s 4 , s 5 s_4,s_5 s4,s5 的位置: L = ( s 6 , s 4 , s 5 , s 0 , s 3 , s 2 ) L=(s_6,s_4,s_5,s_0,s_3,s_2) L=(s6,s4,s5,s0,s3,s2),重新判断 s 4 , s 5 s_4,s_5 s4,s5 与各自的相邻线段是否存在交点
    结果 s 4 s_4 s4 s 6 s_6 s6 相交,将交点 x 46 x_{46} x46 插入队列 Q Q Q 中(按 y y y 大小排序的位置中);
    s 5 s_5 s5 s 0 s_0 s0 相交,将交点 x 05 x_{05} x05 插入队列 Q Q Q 中(按 y y y 大小排序的位置中);
    在这里插入图片描述

  16. 扫描线扫描到末尾端点 b 2 b_2 b2,从队列 Q Q Q 中出队,删除 L L L 中的 s 2 s_2 s2 L = ( s 6 , s 4 , s 5 , s 0 , s 3 ) L=(s_6,s_4,s_5,s_0,s_3) L=(s6,s4,s5,s0,s3) s 2 s_2 s2 只与 s 3 s_3 s3 相邻,所以不做任何操作;
    在这里插入图片描述

  17. 扫描线扫描到末尾端点 b 3 b_3 b3,从队列 Q Q Q 中出队,删除 L L L 中的 s 3 s_3 s3 L = ( s 6 , s 4 , s 5 , s 0 ) L=(s_6,s_4,s_5,s_0) L=(s6,s4,s5,s0) s 3 s_3 s3 只与 s 0 s_0 s0 相邻,所以不做任何操作;
    在这里插入图片描述

  18. 扫描线扫描到交点 x 05 x_{05} x05,从队列 Q Q Q 中出队,交换 L = ( s 6 , s 4 , s 5 , s 0 ) L=(s_6,s_4,s_5,s_0) L=(s6,s4,s5,s0) 中交点所对应的线段 s 5 , s 0 s_5,s_0 s5,s0 的位置: L = ( s 6 , s 4 , s 0 , s 5 ) L=(s_6,s_4,s_0,s_5) L=(s6,s4,s0,s5),重新判断 s 5 , s 0 s_5,s_0 s5,s0 与各自的相邻线段是否存在交点, s 0 s_0 s0 与相邻线段 s 4 s_4 s4 不相交,转下一步;
    在这里插入图片描述

  19. 扫描线扫描到末尾端点 b 5 b_5 b5,从队列 Q Q Q 中出队,删除 L L L 中的 s 5 s_5 s5 L = ( s 6 , s 4 , s 0 ) L=(s_6,s_4,s_0) L=(s6,s4,s0) s 0 s_0 s0 与相邻线段 s 4 s_4 s4 不相交,转下一步;
    在这里插入图片描述

  20. 扫描线扫描到交点 x 46 x_{46} x46,从队列 Q Q Q 中出队,交换 L = ( s 6 , s 4 , s 0 ) L=(s_6,s_4,s_0) L=(s6,s4,s0) 中交点所对应的线段 s 6 , s 0 s_6,s_0 s6,s0 的位置: L = ( s 4 , s 6 , s 0 ) L=(s_4,s_6,s_0) L=(s4,s6,s0),重新判断 s 6 , s 0 s_6,s_0 s6,s0 与各自的相邻线段是否存在交点, s 0 s_0 s0 与相邻线段 s 6 s_6 s6 不相交,转下一步;
    在这里插入图片描述

  21. 扫描线扫描到末尾端点 b 4 b_4 b4,从队列 Q Q Q 中出队,删除 L L L 中的 s 4 s_4 s4 L = ( s 6 , s 0 ) L=(s_6,s_0) L=(s6,s0) s 6 s_6 s6 与相邻线段 s 0 s_0 s0 不相交,转下一步;
    在这里插入图片描述

  22. 扫描线扫描到末尾端点 b 0 b_0 b0,从队列 Q Q Q 中出队,删除 L L L 中的 s 0 s_0 s0 L = ( s 6 ) L=(s_6) L=(s6),不做任何操作
    在这里插入图片描述

  23. 扫描线扫描到末尾端点 b 6 b_6 b6,从队列 Q Q Q 中出队,删除 L L L 中的 s 6 s_6 s6 L = ( ) L=() L=(),结束
    在这里插入图片描述

最后

附上另一个案例的链接,供大家自行学习
https://tildesites.bowdoin.edu/~ltoma/teaching/cs3250-CompGeom/spring16/Lectures/cg-segmintersect.pdf

  • 14
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Bentley-Ottmann算法是一种用于计算平面上线段相交的算法。它可以找到所有线段交点,并按照其在 x 轴上的顺序进行排序。这个算法的时间复杂度是 O((n + k) log n),其中 n 是线段的数量,k 是交点的数量。它是一种非常高效的算法,被广泛应用于计算几何和图形学领域。 该算法的基本思想是,通过扫描线的方式从上到下依次处理线段,并将与当前扫描线相交的线段添加到一有序链表中。当处理完所有线段时,根据链表中线段的相交顺序,即可得到线段交点。 在执行过程中,需要使用一个事件队列来存储线段的起点和终点,并按照其在 x 轴上的位置进行排序。同时,还需要使用一个状态结构体来记录当前扫描线的位置以及与之相交的线段。 该算法的主要步骤包括: 1. 初始化事件队列,并按照线段的起点和终点位置进行排序。 2. 初始化扫描线位置,并创建一个空的有序链表。 3. 从事件队列中取出一个事件,并根据事件类型进行处理。 - 如果是线段的起点,则将该线段插入到有序链表中,并检查与之相邻的线段是否相交。 - 如果是线段的终点,则将该线段从有序链表中删除,并检查与之相邻的线段是否相交。 - 如果是交点事件,则交换两线段在有序链表中的位置,并检查与之相邻的线段是否相交。 4. 重复步骤3,直到事件队列为空。 通过以上步骤,Bentley-Ottmann算法可以找到所有线段交点,并按照其在 x 轴上的顺序进行排序。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值