判斷兩條線段是否相交—(向量叉乘)


 

問題:給出兩條線段,問兩線段是否相交?

 

向量叉乘(行列式計算):向量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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM