問題:給出兩條線段,問兩線段是否相交?html
向量叉乘(行列式計算):向量a(x1,y1),向量b(x2,y2):node
首先咱們要明白一個定理:向量a×向量b(×爲向量叉乘),若結果小於0,表示向量b在向量a的順時針方向;若結果大於0,表示向量b在向量a的逆時針方向;若等於0,表示向量a與向量b平行。(順逆時針是指兩向量平移至起點相連,從某個方向旋轉到另外一個向量小於180度)。以下圖:ios
在上圖中,OA×OB = 2 > 0, OB在OA的逆時針方向;OA×OC = -2 < 0,OC在OA的順勢針方向。即叉乘結果大於0,後一個在前一個的逆時針方向;小於零,後一個在前一個的順時針方向。ide
那如何來判斷兩線段是否相交呢?ui
假設有兩條線段AB,CD,若AB,CD相交,咱們能夠肯定:spa
1.線段AB與CD所在的直線相交,即點A和點B分別在直線CD的兩邊;3d
2.線段CD與AB所在的直線相交,即點C和點D分別在直線AB的兩邊;code
上面兩個條件同時知足是兩線段相交的充要條件,因此咱們只須要證實點A和點B分別在直線CD的兩邊,點C和點D分別在直線AB的兩邊,這樣即可以證實線段AB與CD相交了。htm
那判斷兩線段是否相交與一開始提到的向量叉乘定理有什麼關係呢?有,咱們能夠經過叉乘來證實上面說的充要條件。看下圖:blog
在上圖中,線段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"; } }
參考博客:http://blog.sina.com.cn/s/blog_735b07180100uivu.html