Sweeping 扫除算法

问题

判断nn个线段中是否存在相交的线段。

解法

对于nn条线段,暴力枚举任意两条线段是否相交的时间复杂度为O(n2)O(n^2)。Sweeping算法有更低的时间复杂度。

首先介绍扫除线,在平面中设置一条垂直于xx轴,平行于yy轴的直线ss,从左向右依次经过所有线段。如图所示:

假设扫除线ss与线段l1l_{1}l2l_{2}同时相交,存在以下情况:

l1l_{1}ss的交点aayy轴坐标大于l2l_{2}ss的交点bbyy轴坐标,则称在xx轴的这个位置l1>l2l_{1} \gt l_{2};反之若交点aayy轴坐标小于交点bbyy轴坐标,则称在xx轴这个位置l1<l2l_{1} \lt l_{2}。这是一个全序关系。

显然,若l1l_{1}l2l_{2}线段相交,则ss从左向右经过此交点时,l1l_{1}l2l_{2}的全序关系会变化(从l1>l2l_{1} \gt l_{2}变为l1<l2l_{1} \lt l_{2},或从l1<l2l_{1} \lt l_{2}变为l1>l2l_{1} \gt l_{2})。

将所有线段的端点按照xx坐标从小到大排序,若两个端点的xx坐标相等,则在线段的左端点的端点优先(两个端点中xx坐标较小的端点为左端点,若xx坐标相等则yy坐标较小的端点算作左端点)。排序之后的所有端点组成事件点序列。当ss经过某个点时,有两线段的全序关系发生了变化,则认为这两线段相交。

扫除线从左边向右边移动经过每个端点,维护状态TT,该状态记录了当前扫除线与哪些线段的端点相交,以及端点的坐标。由此可得:

(1)(1) 当扫除线遇到线段l1l_{1}的左端点aa时,将线段l1l_{1}加入状态TT。若此时状态TT中存在相交的端点bb(在线段l2l_{2}上),若l2l_{2}l1l_{1}相交,则可知这组线段中存在相交的线段;

(2)(2) 当扫除线遇到线段l1l_{1}的右端点aa时,将线段l1l_{1}从状态TT中删除。若此时状态TT中存在相交的端点bbcc(端点bb在线段l2l_{2}上,端点cc在线段l3l_{3}上),若bbaa上方、ccaa下方(反之亦然),且l2l_{2}l3l_{3}相交,则可知这组线段中存在相交的线段;

状态TT通过红黑树实现,需要实现:插入线段Insert(l)Insert(l),删除线段Erase(l)Erase(l)、查询线段ll上方的线段Above(l)Above(l),查询线段ll下方的线段Below(l)Below(l)四种操作。

对于nn个线段,该算法的时间复杂度为O(n)O(n)

Introduction to Algorithms

源码

Sweeping.h

Sweeping.cpp

测试

SweepingTest.cpp

Last updated