面試 面試中遇到的演算法題

2021-09-10 22:27:54 字數 4309 閱讀 5140

概念平面內兩條線段位置關係的判定在很多領域都有著廣泛的應用,比如遊戲、cad、圖形處理等,而兩線段交點的求解又是該演算法中重要的一環。本文將盡可能用通俗的語言詳細的描述一種主流且效能較高的判定演算法。

為方便計算,對座標點的大小比較作如下定義:x座標較大的點為大,x座標相等但y座標較大的為大,x與y都相等的點相等。一條線段中較小的一端為起點,較大的一端為終點。

問題給定兩條線段的端點座標,求其位置關係,並求出交點(如果存在)。

分析兩條線段的位置關係大體上可以分為三類:有重合部分、無重合部分但有交點(相交)、無交點。為避免精度問題,首先要將所有存在重合的情況排除。

重合可分為:完全重合、一端重合、部分重合三種情況。顯然,兩條線段的起止點都相同即為完全重合;只有起點相同或只有終點相同的為一端重合(注意:座標較小的一條線段的終點與座標較大的一條線段的起點相同時應判定為相交)。要判斷是否部分重合,必須先判斷是否平行。設線段l1(p1->p2)和l2(p3->p4),其中p1(x1, y1)為第一條線段的起點,p2(x2, y2)為第一條線段的終點,p3(x3, y3)為第二條線段的起點,p4(x4, y4)為第二段線段的終點,由此可構造兩個向量:

v1(x2-x1, y2-y1),v2(x4-x3, y4-y3)

若v1與v2的外積v1×v2為0,則兩條線段平行,有可能存在部分重合。再判斷兩條平行線段是否共線,方法是用l1的一端和l2的一端構成向量vs並與v2作外積,如果vs與v2也平行則兩線段共線(三點共線)。在共線的前提下,若起點較小的線段終點大於起點較大的線段起點,則判定為部分重合。

沒有重合,就要判定兩條線是否相交,主要的演算法還是依靠外積。然而外積的計算開銷比較大,如果不相交的情況比較多,可先做快速排斥實驗:將兩條線段視為兩個矩形的對角線,並構造出這兩個矩形。如果這兩個矩形沒有重疊部分(x座標相離或y座標相離)即可判定為不相交。

然後執行跨立試驗。兩條相交的線段必然相互跨立,簡單的講就是p1和p2兩點位於l2的兩側且p3和p4兩點位於l1的兩側,這樣就可利用外積做出判斷了。分別構造向量s1(p3, p1), s2(p3, p2),如果s1×v2與s2×v2異號(s1->v2與s2->v2轉動的方向相反),則說明p1和p2位於l2的兩側。同理可判定p3和p4是否跨立l1。如果上述四個叉積中任何乙個等於0,則說明一條線段的端點在另一條線上。

當判定兩條線段相交後,就可以進行交點的求解了。當然,求交點可以用平面幾何方法,列點斜式方程來完成。但這樣作會難以處理斜率為0的特殊情況,且運算中會出現多次除法,很難保證精度。這裡將使用向量法求解。

設交點為(x0, y0),則下列方程組必然成立:

x0-x1=k1(x2-x1)

y0-y1=k1(y2-y1)

x0-x3=k2(x4-x3)

y0-y3=k2(y4-y3)

其中k1和k2為任意不為0的常數(若為0,則說明有重合的端點,這種情況在上面已經被排除了)。1式與2式聯絡,3式與4式聯立,消去k1和k2可得:

x0(y2-y1)-x1(y2-y1)=y0(x2-x1)-y1(x2-x1)

x0(y4-y3)-x3(y4-y3)=y0(x4-x3)-y3(x4-x3)

將含有未知數x0和y0的項移到左邊,常數項移動到右邊,得:

(y2-y1)x0+(x1-x2)y0=(y2-y1)x1+(x1-x2)y1

(y4-y3)x0+(x3-x4)y0=(y4-y3)x3+(x3-x4)y3

設兩個常數項分別為b1和b2:

b1=(y2-y1)x1+(x1-x2)y1

b2=(y4-y3)x3+(x3-x4)y3

係數行列式為d,用b1和b2替換x0的係數所得係數行列式為d1,替換y0的係數所得係數行列式為d2,則有:

|d|=(x2-x1)(y4-y3)-(x4-x3)(y2-y1)

|d1|=b2(x2-x1)-b1(x4-x3)

|d2|=b2(y2-y1)-b1(y4-y3)

由此,可求得交點座標為:

x0=|d1|/|d|, y0=|d2|/|d|

解畢。

#include #include using namespace std;

struct pointf ;

bool equal(float f1, float f2)

//判斷兩點是否相等

bool operator==(const pointf &p1, const pointf &p2)

//比較兩點座標大小,先比較x座標,若相同則比較y座標

bool operator>(const pointf &p1, const pointf &p2)

//計算兩向量外積

float operator^(const pointf &p1, const pointf &p2)

//判定兩線段位置關係,並求出交點(如果存在)。返回值列舉如下:

//[有重合] 完全重合(6),1個端點重合且共線(5),部分重合(4)

//[無重合] 兩端點相交(3),交於線上(2),正交(1),無交(0),引數錯誤(-1)

int intersection(pointf p1, pointf p2, pointf p3, pointf p4, pointf &int)

//為方便運算,保證各線段的起點在前,終點在後。

if (p1 > p2)

if (p3 > p4)

//判定兩線段是否完全重合

if (p1 == p3 && p2 == p4)

//求出兩線段構成的向量

pointf v1 = , v2 = ;

//求兩向量外積,平行時外積為0

float corss = v1 ^ v2;

//如果起點重合

if (p1 == p3)

//如果終點重合

if (p2 == p4)

//如果兩線端首尾相連

if (p1 == p4)

if (p2 == p3) //經過以上判斷,首尾點相重的情況都被排除了

//將線段按起點座標排序。若線段1的起點較大,則將兩線段交換

if (p1 > p3)

//處理兩線段平行的情況

if (equal(corss, 0)) ;

//外積為0則兩平行線段共線,下面判定是否有重合部分

if (equal(v1 ^ vs, 0))

}//若三點不共線,則這兩條平行線段必不共線。

//不共線或共線但無重合的平行線均無交點

return 0;

} //以下為不平行的情況,先進行快速排斥試驗

//x座標已有序,可直接比較。y座標要先求兩線段的最大和最小值

float ymax1 = p1.y, ymin1 = p2.y, ymax2 = p3.y, ymin2 = p4.y;

if (ymax1 < ymin1)

if (ymax2 < ymin2)

//如果以兩線段為對角線的矩形不相交,則無交點

if (p1.x > p4.x || p2.x < p3.x || ymax1 < ymin2 || ymin1 > ymax2) //下面進行跨立試驗

pointf vs1 = , vs2 = ;

pointf vt1 = , vt2 = ;

float s1v2, s2v2, t1v1, t2v1;

//根據外積結果判定否交於線上

if (equal(s1v2 = vs1 ^ v2, 0) && p4 > p1 && p1 > p3)

if (equal(s2v2 = vs2 ^ v2, 0) && p4 > p2 && p2 > p3)

if (equal(t1v1 = vt1 ^ v1, 0) && p2 > p3 && p3 > p1)

if (equal(t2v1 = vt2 ^ v1, 0) && p2 > p4 && p4 > p1) //未交於線上,則判定是否相交

if(s1v2 * s2v2 > 0 || t1v1 * t2v1 > 0) //以下為相交的情況,演算法詳見文件

//計算二階行列式的兩個常數項

float cona = p1.x * v1.y - p1.y * v1.x;

float conb = p3.x * v2.y - p3.y * v2.x;

//計算行列式d1和d2的值,除以係數行列式的值,得到交點座標

int.x = (conb * v1.x - cona * v2.x) / corss;

int.y = (conb * v1.y - cona * v2.y) / corss;

//正交返回1

return 1;

}//主函式

int main(void)

cout << endl;

}return 0;

}

面試遇到的演算法題1

1.兩個整數求和 string a 893333.3333 string b 349.999 求 a b 由於數字太長,只能存在string 裡,不能整體轉成int long來加,只能一位一位的轉成int來加,注意進製。用乙個for迴圈按每一位做加法,最後輸出到乙個string裡。如何將char型別...

面試中遇到的那些程式設計題

if typeof string.prototype.countcharacters function len this.length for i 0 i len i else var max key,max num 0 for var key in obj return 測試 var str ab...

面試過程中遇到的演算法題 二

面試過程中遇到的演算法題記錄 有乙個字元陣列,找出陣列中,長度最長的前兩個 示例 a abcd ab ads 輸出abcd,ads 如下 示例 from typing import list class arry def arryone self,nums list str l len nums f...