判断两条线段是否相交—(向量叉乘)

 

问题:给出两条线段,问两线段是否相交?

 

向量叉乘(行列式计算):向量a(x1,y1),向量b(x2,y2):

 

首先我们要明白一个定理:向量a×向量b(×为向量叉乘),若结果小于0,表示向量b在向量a的顺时针方向;若结果大于0,表示向量b在向量a的逆时针方向;若等于0,表示向量a与向量b平行。(顺逆时针是指两向量平移至起点相连,从某个方向旋转到另一个向量小于180度)。如下图:

在上图中,OA×OB = 2 > 0, OB在OA的逆时针方向;OA×OC = -2 < 0,OC在OA的顺势针方向。即叉乘结果大于0,后一个在前一个的逆时针方向;小于零,后一个在前一个的顺时针方向。

 

那如何来判断两线段是否相交呢?

假设有两条线段AB,CD,若AB,CD相交,我们可以确定:

1.线段AB与CD所在的直线相交,即点A和点B分别在直线CD的两边;

2.线段CD与AB所在的直线相交,即点C和点D分别在直线AB的两边;

上面两个条件同时满足是两线段相交的充要条件,所以我们只需要证明点A和点B分别在直线CD的两边,点C和点D分别在直线AB的两边,这样便可以证明线段AB与CD相交了。

 

那判断两线段是否相交与一开始提到的向量叉乘定理有什么关系呢?有,我们可以通过叉乘来证明上面说的充要条件。看下图:

 

在上图中,线段AB与线段CD相交,于是我们可以得到两个向量AC,AD,C和D分别在AB的两边,向量AC在向量AB的逆势针方向,AB×AC > 0;向量AD在向量AB的顺势针方向,AB×AD < 0,两叉乘结果异号。

这样,方法就出来了:如果线段CD的两个端点C和D,与另一条线段的一个端点(A或B,只能是其中一个)连成的向量,与向量AB做叉乘,若结果异号,表示C和D分别在直线AB的两边,若结果同号,则表示CD两点都在AB的一边,则肯定不相交。

当然,不能只证明C,D在直线AB的两边,还要用相同的方法证明A,B在直线CD的两边,两者同时满足才是线段相交的充要条件。

 

不过,线段相交还有一些特殊情况:

1.只有1点相交,如下图:

 

上图中,线段AB与CD相交于C点,按照之前介绍的方法,我们可以连成两向量AD和AC,这时候,我们发现,AC与AB共线,AB×AC = 0;而AB×AD < 0;两者并不异号,可实际上仍然相交。所以当出现两叉乘结果中,有一方为0,也可以看成点CD在直线AB的两边。

 

2.两条线段重合,如下图:

 

在上图中,线段AB与线段CD重合,重合部分为CB,这种重合的情况要特殊判断:

首先,我们给没条线段的两个端点排序,大小判断方法如下:横坐标大的点更大,横坐标相同,纵坐标大的点更大。

排好序后,每条线段中,小的点当起点,大的当终点。我们计算向量AB×向量CD,若结果为0,表示线段AB平行CD,平行才有了重合的可能;但平行也分共线和不共线,只有共线才有可能重合,看下图:

上图中,第一种情况不共线,第二种情况共线。那如何来判断是否共线呢?

我们可以在两条线段中各取一点,用这两点组成的向量与其中一条线段进行叉乘,结果若为0,就表示两线段共线,如下图:

我们取向量BC,若BC×CD = 0,表示两点共线,即是第二种情况,否则就是第一种情况。第一种情况肯定不相交。猴子为什么不喜欢平行线?因为他们没有相交。。。(尬)

然然然然然而,即使他们共线,却还是不一定重合,就如上图中第二种情况。这时候,之前给点排序的妙处就体现出来了:

若一条线段AB与另一条线段CD共线,且线段AB的起点小于等于线段CD的起点,但线段AB的终点(注意是终点)大于等于线段CD的起点(注意是起点),或者交换一下顺序,CD的起点小于AB的起点......只要满足其中一个,就表示有重合部分。

 

下面来道例题:51nod1264(模板)

代码:

 

#include<iostream> #include<cstring> #include<cstdio> #include<string> #include<cmath> #include<algorithm> #include<stack> #include<climits> #include<queue> #define eps 1e-7 #define ll long long #define inf 0x3f3f3f3f #define pi 3.141592653589793238462643383279 using namespace std; struct node{ double x,y; }; double cmp(node a,node b) //给线段的坐标排序 { if(a.x != b.x) return a.x < b.x; else return a.y < b.y; } double compute(double x1,double y1,double x2,double y2) //计算叉乘的结果 { return x1*y2 - y1*x2; } int compare(node a,node b) //比较坐标的大小 { if(a.x < b.x || a.x == b.x && a.y < b.y) return -1; else if(a.x == b.x && a.y == b.y) return 0; else return 1; } int main() { int t; node po[4]; cin>>t; while(t--) { for(int i=0; i<4; ++i) scanf("%lf%lf",&po[i].x,&po[i].y); sort(po,po+2,cmp); //给第一条线段的坐标排序 sort(po+2,po+4,cmp); //给第二条排序 /*for(int i=0; i<4; ++i) cout<<po[i].x<<' '<<po[i].y<<endl;*/ int flag; if(!compare(po[0],po[2]) || !compare(po[0],po[3]) || !compare(po[1],po[2]) || !compare(po[1],po[3])) //若有某一点重合,则肯定相交 flag = 1; else if(compute(po[0].x-po[1].x , po[0].y-po[1].y , po[2].x-po[3].x , po[2].y-po[3].y) ==0 ) //若两线段平行 { if(compute(po[0].x-po[1].x , po[0].y-po[1].y , po[0].x-po[3].x , po[0].y-po[3].y) == 0) //若两线段共线 { if(compare(po[0],po[2]) <= 0 && compare(po[1],po[2]) >= 0) //第一条起点小于第二条起点,第一条终点大于第二条起点 flag = 1; else if(compare(po[2],po[0]) >= 0 && compare(po[3],po[0]) <= 0) //第二条起点小于第一条起点,第二条终点大于第一条起点 flag = 1; else flag = 0; } else flag = 0; } else if(compute(po[0].x-po[1].x , po[0].y-po[1].y , po[2].x-po[3].x , po[2].y-po[3].y) !=0 ) //若不平行 { double num1,num2,num3,num4; num1 = compute(po[0].x-po[1].x , po[0].y-po[1].y , po[0].x-po[2].x , po[0].y-po[2].y); //计算第一条的两个端点 num2 = compute(po[0].x-po[1].x , po[0].y-po[1].y , po[0].x-po[3].x , po[0].y-po[3].y); //在第二条线段的两边 num3 = compute(po[0].x-po[2].x , po[0].y-po[2].y , po[2].x-po[3].x , po[2].y-po[3].y); //计算第二条的两个端点 num4 = compute(po[1].x-po[2].x , po[1].y-po[2].y , po[2].x-po[3].x , po[2].y-po[3].y); //在第一条线段的两边 //cout<<num1<<' '<<num2<<' '<<num3<<' '<<num4<<endl; if(num1*num2 < 0 && num3*num4 <= 0 || num1*num2 <= 0 && num3*num4 < 0) //等于0表示成180度角 flag = 1; else flag = 0; } else flag = 0; if(flag) cout<<"YES\n"; else cout<<"NO\n"; } }
View Code

 

参考博客:http://blog.sina.com.cn/s/blog_735b07180100uivu.html

 


__EOF__

本文作者特务依昂
本文链接https://www.cnblogs.com/tuyang1129/p/9390376.html
关于博主:在互联网洋流中垂死挣扎,但依旧乐观的Java小菜鸟一枚!
版权声明:转载博客请注明出处,并附上原文链接!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   特务依昂  阅读(69157)  评论(1编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示