接觸ACM大概有一年了,這一年來,學習了不少算法,但是,像我們這種弱校,教練是掛名的,算法都是師兄們一代又一代流傳下來的,基本上師兄們會什麼算法,就教我們什麼算法了。我記得我高中的數學老師說過:“你們要超越老師,不然一代又一代傳下去,會一直衰落的。”我們師兄流傳下來的算法基本上都是圖論,數據結構。而數論、幾何這些方面存在着一個很大的缺口,每次遇到這類型的題目也只能望而生畏了。於是我決定要向這些方向進軍,嘗試找到新的突破。即使是弱校,也不能阻止我們熱愛ACM。
計算幾何
我看了算法導論和lrj的黑書,雖然黑書上有些地方暫時還沒看懂,但是還算略有小成。
1、點、線段的表示:
點的座標表示我麼很容易想到用結構體:
typedef struct _point
{
int x,y;
}Point;
而線段,我們可以用兩個點表示。
2、如何判斷兩條直線、線段是否相交:
用高中解析幾何的知識我們很容易想到判斷兩條直線是否相交,只需要用求出兩條直線的斜率,判斷斜率是否相等就行了。如果是線段,只需要求出兩直線交點,判斷是否在兩條線段兩端點之間就行。然而,實際上由於在求斜率和交點時浮點型的數值運算需要考慮精度問題,而且不能體現算法的美。
在計算幾何中,我們常常將問題轉化爲向量的關係。
假設向量a=(x1,y1),向量b=(x2,y2),只需要判斷x1*y2-x2*y1是否等於0就可以判斷兩直線是否相交了。
但是對於線段,我們要怎樣判斷是否相交呢?
我們先在這裏引入“向量的左右”這一個概念。如下圖1,假設我們咱在向量p0p1的起點p0,面向p0p1向量的正方向,走手邊即左側,右手邊爲右側。
位於向量逆時針方向的向量,我們稱之爲“右手螺旋”,如上圖2,反之成爲“左手螺旋”,如上圖3。
我們在判斷兩線段是否相交時,只需要判斷兩向量是否相互“跨越”對方。所謂跨越,即一個向量的兩個端點分別位於另一個向量的左右手方向。如下圖:
圖4中,兩向量並沒有相互“跨越”,因爲p0和p1同時在向量p2p3的右側。圖5中,兩向量互相“跨越”了對方,所以我們就可以說線段p0p1和線段p2p3相交了。
3、點積和叉積:
但是,我們該如何表示點在向量的左右方向呢?
這是我們介紹一個在計算幾何裏很重要的運算:點積和叉積。
回憶高中所學的知識,假設p0爲(0,0),我們知道p1Xp2是(0,0),p1,p2和p1+p2所形成的平行四邊形的有面積,注意這裏的是“有向面積”,如下圖陰影部分。
p1Xp2 = x1y2 - x2y1 = |p1|*|p2|*sinθ
如果p1Xp2爲正數,那麼相對於(0,0),p2位於p1的右手螺旋方向,如果p1Xp2爲負數,那麼p2位於p1的左手螺旋方向。於是我們運用叉積,就能判斷點在向量的左右方向了。
int cross (Point p0,Point p1,Point p2)
{//求p0p1和p0p2的叉積
return (p1.x - p0.x) * (p2.y - p0.y) - (p2.x - p0.x) * (p1.y - p0.y);
}
現在我們來考慮特殊情況:
假設向量中的一個點在另一條向量上,如上圖7,此時,p0p1Xp0p2=0,此時該如何判斷呢?很容易能想到只要用座標判斷p2是否在p0和p1之間就行。另外,我們還有另一種方法,用向量的點積能判斷。
向量p1·p2 = |p1|*|p2|*cosθ,如下圖:
只需要判斷向量p2p0和p2p1的點積就能判斷點是否在線段上。當點積小於0,表示p2在線段p0p1上,當點積大於0,表示不在線段上。