转载自:http://johnhany.net/2013/11/sweep-algorithm-for-segments-intersection/

N条线段求交的扫描线算法

在对图进行计算时,很常用的一个操作就是求若干条线段的交点,比如对图的叠加、截窗,需要频繁地计算线段交点,如果求交算法效率很低,上层的算法再优秀也表现不出好的性能。


先考虑一个很简单的情形:只有两条线段,求它们是否相交,如果相交,交点在哪?

如左图,如果线段[a0,a1]与[b0,b1]相交,则端点a0、a1必定落在[b0,b1]两侧,同时端点b0、b1必定落在[a0,a1]两侧。只要这两个条件同时满足,即认为两线段相交。(一条线段的端点落在另一条线段上也认为是两线段相交)

一种比较快速的方法是使用向量外积。

三角形面积公式的向量形式为:

面积恰是两边a,b外积大小的一半。而外积是有方向的。判断两点是否同侧,只需要判断外积是否同号。比如对上面的图进行如下计算:

s1=(xb0-xa0)*(yb1-ya0)-(xb1-xa0)*(yb0-ya0)

s2=(xb0-xa1)*(yb1-ya1)-(xb1-xa1)*(yb0-ya1)

其中s1方向垂直屏幕向内,s2方向垂直屏幕向外,两者异号,所以点a0、a1位于线段[b0,b1]两侧。

同理,计算s3=(xa1-xb0)*(ya0-yb0)-(xa0-xb0)*(ya1-yb0)和s4=s2-s1+s3异号,可以确定b0、b1落在[a0,a1]两侧。(由面积恒等关系s4-s3=s2-s1可以直接计算s4)

确定两条线段相交,接着就要计算交点。这一步没有必要用向量计算,只要求解直角坐标下的方程组就好。不过需要注意端点重合的情况。

//inte[i][0]为交点x坐标
//inte[i][1]为交点y坐标
//_inteCount为之前找到的交点总数
if(xa0==xa1 || xb0==xb1)
{if(xa0==xa1){b=(yb0-yb1)/(xb0-xb1);inte[_inteCount][0]=xa0;inte[_inteCount][1]=b*(inte[_inteCount][0]-xb1)+yb1;}else{a=(ya0-ya1)/(xa0-xa1);inte[_inteCount][0]=xb0;inte[_inteCount][1]=a*(inte[_inteCount][0]-xa1)+ya1;}
}
else
{a=(ya0-ya1)/(xa0-xa1);b=(yb0-yb1)/(xb0-xb1);inte[_inteCount][0]=(a*xa1-b*xb1-ya1+yb1)/(a-b);inte[_inteCount][1]=a*(inte[_inteCount][0]-xa1)+ya1;
}

现在考虑有很多条线段的情形。如果把这N条线段两两检查交点,时间复杂度是O(n^2),在线段数目很多时,计算速度会非常慢。这时,就需要扫描线算法了。

观察一下那些相交的线段有什么特点。把每条线段向y轴投影:

可以看出相交的线段的投影会彼此叠加,而且投影不重合的线段也不可能相交。

利用这个特性,用一条平行的直线从上到下平移,平移的过程中会与某些线段相交,在任何时刻只考虑这些与扫描线相交的线段之间是否相交。现考虑某时刻这条扫描线上的M条线段(M<=N):

在这条扫描线上,相交的线段一定是相邻的,比如b和c。虽然存在b和d不相邻也相交的情况,但由于算法的特点,处理到那个交点时,b和d一定是相邻的。比如:

扫描线在点T上方时,c与d相邻,但b与d不相邻。找到交点T。但扫描线经过T到达S上方时,c与d的位置交换了,此时b与d相邻而且相交。所以,只有相邻的线段才有可能相交。

我们把相邻的线段称为互为邻居,比如a是b的左邻居,c是b的右邻居。

在扫描线行进的过程中,需要动态维护两个数据结构:

一条链表,负责记录所以线段的端点和已经找到的交点,每个点按y递减顺序存储(y相同的,按x递增排序);

                一棵二叉树,负责记录与扫描线相交的线段(确切地说,保存的是每条线段的上端点),每条线段按照上端点的x坐标递增顺序存储。

所谓“扫描”,即程序从头到尾依次处理链表上的每个点,在每处理一个新的点时,会相应地更新链表和二叉树。新的点共有三种,相应的处理方法如下所述:

1.新点是某线段的上端点p0:

把这个端点存入二叉树,然后在树中找到p0的左邻居pa和右邻居pb,检查p0与pa是否相交,p0与pb是否相交。如果有交点,把交点存入链表。

比如b的上端点接触到扫描线,只需要检查a与b是否相交,b与c是否相交。

新交点一定会在扫描线的下方,它在链表中的位置也一定在p0的后面,会在未来某个时刻得到处理。因为如果这个交点的位置在p0之前,说明扫描线在之前已经经过了这个交点,程序也已经处理过它了。

2.新点是某线段的下端点p1:

在二叉树内找到p1相应的上端点,然后找到上端点的左邻居pa和右邻居pb。把p0从二叉树删除,检查pa与pb是否相交。如果有交点,把交点存入链表。

比如b的下端点离开扫描线,删除b后检查a与c是否相交。

3.新点是交点pt:

输出这个交点的坐标。然后在二叉树中找到这个交点所在的两条线段pl和pr(假设pl在pr的左边),再找到pl的左邻居pa,和pr的右邻居pb,检查pr与pa是否相交,pl与pb是否相交。如果有交点,把交点存入链表。

比如b与c的交点接触到扫描线,检查a与c是否相交,b与d是否相交。

以上就是扫描线算法的全部细节。采用这种算法,可以把时间复杂度降到O(nlogn+klogn),其中n是线段数目,k是交点数目。如果想了解这个时间是怎样计算出来的,可以参考《Computational Geometry Algorithms and Applications》(作者:M. de Berg, M. van Kreveld, M. Overmars and O. Schwarzkopf)


我用C实现了这个算法,并且用OpenGL绘制出所有线段及交点。代码可以在这里下载:

https://github.com/johnhany/SegmentsIntersection

这是程序运行的结果:

这里还有一个扫描线求交算法的在线Java演示:Sweep Line Algorithm for Segment Intersection

N条线段求交的扫描线算法相关推荐

  1. 计算几何2:扫描线线段求交算法

    相比于上一篇中介绍的求凸包算法,本次介绍的扫描线线段求交算法的实现难度明显更高,实际上最终本人也未能完美的实现该算法,下面给出的版本经测试是存在一些问题的. 首先介绍一下扫描线算法的基本原理,具体内容 ...

  2. 线段求交算法对比研究

    线段求交算法对比研究 -----by wangsh 一.介绍 线段求交算法在计算几何,地理信息系统算法等相关应用中占有重要的位置,本文简单给出算法说明. Bentley & Ottmann于1 ...

  3. 线段求交应用之Liang-barsky裁剪算法

    欢迎关注更多精彩 关注我,学习常用算法与数据结构,一题多解,降维打击. 线段剪裁作用 所谓线段剪裁,就是在二维平面上有一堆线段,和一个矩形窗口.求出现在窗口里线段部分是哪些. 上图中绿色为线段,红色为 ...

  4. AK F.*ing leetcode 流浪计划之线段求交

    欢迎关注更多精彩 关注我,学习常用算法与数据结构,一题多解,降维打击. 本期话题:2条线段求交点 我有两种线段求交方法. 这两种方法在图形中窗口剪裁中应用. 分别是 Cohen-Suther land ...

  5. C++line segment intersection线段求交(交点)(附完整源码)

    C++line segment intersection线段求交的实现 C++line segment intersection线段求交实现的完整源码(定义,实现,main函数测试) C++line ...

  6. 计算几何 - XOJ 1171 线段求交

    问题 Description 线段求交即给定一组线段求出这些线段的相交情况,它是计算几何的基础问题之一,有着广泛的应用. Input第一行为一个正整数n表示线段的个数(n<=10000)第二行到 ...

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

    Bentley-Ottmann算法:求N条线段的交点 Bentley-Ottmann算法 算法复杂度 1. 使用暴力求解,遍历每一条线段 i ,固定 i 遍历 j 与 i 是否存在交点: 2. 此时我 ...

  8. 数据结构-连续线段-C语言-[输入n条线段各个端点坐标,求包含最多线段的连续线段]

    连续线段 题目描述 题目分析 实现思路 代码实现 题目描述 平面上两个点(一个点由(x,y)坐标组成)可构成一个线段,两个线段如果有一个端点相同,则可构成一个连续线段.假设构成线段的两个端点为v1(x ...

  9. 三条中线分的六个三角形_解读三角形中的三边关系和三条线段的应用

    作为东方文化四大奇迹之一,金字塔是古埃及文明的代表作.在尼罗河下游,至今仍然散布着约80座金字塔遗迹.金字塔的庄严感和稳定性,主要来自于各面都是等腰三角形,有的甚至于接近等边三角形. 三角形是数学中最 ...

最新文章

  1. 拒绝躺平,Redis选择实现了自己的VM
  2. python3 raise 抛出异常
  3. C语言 结构体里的元素前面有一点“.”代表什么意思?
  4. MyEclipse使用总结——修改MyEclipse默认的Servlet和jsp代码模板
  5. phpstorm 调试_PhpStorm中的多用户调试
  6. 【转】刨根究底字符编码之五——简体汉字编码方案(GB2312、GBK等)以及全角、半角、CJK
  7. 刷抖音看到 Python 工程师的工资条后,我沉默了...
  8. 手也很光滑的飞鸽传书
  9. linux-centos7中lnmp服务器编译安装含systemctl启动service(转)
  10. [转载] 七龙珠第一部——第029话 冒险再度开始
  11. 关于游戏中的材质系统
  12. ES6深入浅出-1 新版变量声明:let 和 const-2.视频 let和const
  13. 【Go语言入门教程】Go语言基本语法
  14. 戴尔3080计算机重装系统步骤,终于发现戴尔笔记本重装系统的方法
  15. JavaScript与C#互通的DES加解密算法
  16. 简单实现将GIF图片转换为字符画
  17. 我的博客园博客开通咯(qyl)
  18. 基于OpenCvSharp的数字图像处理 - 图像彩色类型转换
  19. Keil5/MDK结构体无法自动指示成员变量
  20. 【牛客】HJ6——质数因子(华为)

热门文章

  1. 蓝鲸RFID固定资产管理系统
  2. Navicat-数据库的连接以及使用
  3. 关于数据治理的读书笔记 - 什么是组织机制?
  4. 物联网目前的应用场景有哪些
  5. Java UT用例实践记录
  6. MATLAB中repmat函数用法
  7. mysql实践周心得_实践周心得体会4篇
  8. 使用oracle开发的配置
  9. 记getsockopt有时偶然返回为零的异常
  10. el-dialog el-tabs结合样式改造