1. 程式人生 > 實用技巧 >二維計算幾何基礎

二維計算幾何基礎

我們將需要解決的幾何問題的範圍限制在二維平面內,這樣就用到了二維計算幾何。

要用電腦解平面幾何題?數學好的同學們笑了。

我們並不是用計算機算數學卷子上的幾何題去了,而是解決一些更加複雜的幾何相關問題。

為了解決複雜且抽象的問題,我們一定要選擇合適的研究方法。對於計算機來說,給它看幾何圖形……

我們可以把要研究的圖形放在平面直角座標系或極座標系下,這樣解決問題就會方便很多。

前置技能

  • 幾何基礎
  • 平面直角座標系
  • 向量(包括向量積)
  • 極座標與極座標系

圖形的記錄

在平面直角座標系下,點用座標表示,比如點 \((5,2)\) ,點 \((-1,0)\) 什麼的。

我們記錄其橫縱座標值即可。用 pair

或開結構體記錄均可。

在極座標系下,用極座標表示即可。記錄其極徑與極角。

向量

由於向量的座標表示與點相同,所以只需要像點一樣存向量即可(當然點不是向量)。

在極座標系下,與點同理。

直線與射線

一般在解數學題時,我們用解析式表示一條直線。有一般式 \(Ax+By+C=0\) ,還有斜截式 \(y=kx+b\) ,還有截距式 \(\frac{x}{a}+\frac{y}{b}=1\) ……用哪種?

這些式子最後都逃不過最後的結果——代入解方程求值。

解方程什麼的最討厭了,有什麼好一點的方法嗎?

考慮我們只想知道這條直線在哪,它的傾斜程度怎麼樣。於是用直線上的一個點先大致確定位置,用一個向量表示它的傾斜程度,好了,這條直線確定了。

因此我們記錄的是:直線上一點和直線的方向向量。

線段

線段很好記錄:只需要記錄左右端點即可。

在極座標系下,記錄線是比較麻煩的,因此大多數直線問題都在平面直角座標系下解決。

多邊形

開陣列按一定順序記錄多邊形的每個頂點即可。

特殊地,如果矩形的各邊均與某座標軸平行的話,我們只記錄左下角和右上角的頂點即可。

曲線

一些特殊曲線,如函式影象等一般記錄其解析式。對於圓,直接記錄其圓心和半徑即可。

基本公式

正弦定理

在三角形 \(\triangle \text{ABC}\) 中,若角 \(A,B,C\) 所對邊分別為 \(a,b,c\) ,則有:

\[\frac{a}{\sin A}=\frac{b}{\sin B}=\frac{c}{\sin C}=2R \]

其中, \(R\)\(\triangle \text{ABC}\) 的外接圓半徑。

餘弦定理

在三角形 \(\triangle \text{ABC}\) 中,若角 \(A,B,C\) 所對邊分別為 \(a,b,c\) ,則有:

\[\begin{aligned} a^2&=b^2+c^2-2bc\cos A\\ b^2&=a^2+c^2-2ac\cos B\\ c^2&=a^2+b^2-2ab\cos C \end{aligned} \]

上述公式的證明略。均為人教版高中數學 A 版必修五內容。

基本操作

判斷一個點在直線的哪邊

我們有直線上的一點 \(P\) 的直線的方向向量 \(\mathbf v\) ,想知道某個點 \(Q\) 在直線的哪邊。

我們利用向量積的性質,算出 \(\overrightarrow {PQ}\times \mathbf v\) 。如果向量積為負,則 \(Q\) 在直線上方,如果向量積為 \(0\) ,則 \(Q\) 在直線上,如果向量積為正,則 \(Q\) 在直線下方。

可以畫一下圖,用右手定則感受一下。

快速排斥實驗與跨立實驗

我們現在想判斷兩條線段是否相交。

首先特判一些特殊情況。如果兩線段平行,自然不能相交。這種情況通過判斷線段所在直線的斜率是否相等即可。

當然,如果兩線段重合或部分重合,只需要判斷是否有三點共線的情況即可。

如果兩線段的交點為其中一條線段的端點,仍然判斷是否有三點共線的情況即可。

還有些顯然不相交的情況,我們口頭上稱之為「兩條線段離著太遠了」。可什麼是「離著遠」,怎麼判斷它呢?

規定「一條線段的區域」為以這條線段為對角線的,各邊均與某一座標軸平行的矩形所佔的區域,那麼可以發現,如果兩條線段沒有公共區域,則這兩條線段一定不相交。

比如有以下兩條線段:

它們佔用的區域是這樣的:

於是可以快速地判斷出來這兩條線段不相交。

這就是 快速排斥實驗 。上述情況稱作 未通過快速排斥實驗

未通過快速排斥實驗是兩線段無交點的 充分不必要條件 ,我們還需要進一步判斷。

因為兩線段 \(a,b\) 相交, \(b\) 線段的兩個端點一定分佈在 \(a\) 線段所在直線兩端;同理, \(a\) 線段的兩個端點一定分佈在 \(b\) 線段所在直線兩端。我們可以直接判斷一條線段的兩個端點相對於另一線段所在直線的位置關係,如果不同,則兩線段相交,反之則不相交。我們可以利用 3.1 中的知識幫助我們判斷直線與點的位置關係。

這就是 跨立實驗 ,如果對於兩線段 \(a,b\)\(b\) 線段的兩個端點分佈在 \(a\) 線段所在直線的兩側, \(a\) 線段的兩個端點分佈在 \(b\) 線段所在直線的兩側,我們就說 \(a,b\) 兩線段 通過了跨立實驗 ,即兩線段相交。

注意到當兩條線段共線但不相交時也可以通過跨立實驗,因此想要準確判斷還需要與快速排斥實驗結合。

判斷一點是否在任意多邊形內部

在計算幾何中,這個問題被稱為 PIP 問題 ,已經有一些成熟的解決方法,下面依次介紹。

光線投射演算法 (Ray casting algorithm)

這裡 可以看到最原始的思路。

我們先特判一些特殊情況,比如「這個點離多邊形太遠了」。考慮一個能夠完全覆蓋該多邊形的最小矩形,如果這個點不在這個矩形範圍內,那麼這個點一定不在多邊形內。這樣的矩形很好求,只需要知道多邊形橫座標與縱座標的最小值和最大值,座標兩兩組合成四個點,就是這個矩形的四個頂點了。

還有點在多邊形的某一邊或某頂點上,這種情況十分容易判斷(留作課後作業)。

我們考慮以該點為端點引出一條射線,如果這條射線與多邊形有奇數個交點,則該點在多邊形內部,否則該點在多邊形外部,我們簡記為 奇內偶外 。這個演算法同樣被稱為奇偶規則 (Even-odd rule)。

由於 Jordan curve theorem ,我們知道,這條射線每次與多邊形的一條邊相交,就切換一次與多邊形的內外關係,所以統計交點數的奇偶即可。

這樣的射線怎麼取?可以隨機取這條射線所在直線的斜率,建議為無理數以避免出現射線與多邊形某邊重合的情況。

在原版程式碼中,使用的是記錄多邊形的陣列中最後一個點作為射線上一點,這樣統計時,如果出現射線過多邊形某邊或某頂點時,可以規定射線經過的點同在射線一側,進而做跨立實驗即可。

迴轉數演算法 (Winding number algorithm)

迴轉數是數學上的概念,是平面內閉合曲線逆時針繞過該點的總次數。很容易發現,當迴轉數等於 \(0\) 的時候,點在曲線外部。這個演算法同樣被稱為非零規則 (Nonzero-rule)。

如何計算呢?我們把該點與多邊形的所有頂點連線起來,計算相鄰兩邊夾角的和。注意這裡的夾角是 有方向的 。如果夾角和為 \(0\) ,則這個點在多邊形外,否則在多邊形內。

求兩條直線的交點

首先,我們需要確定兩條直線相交,只需判斷一下兩條直線的方向向量是否平行即可。如果方向向量平行,則兩條直線平行,交點個數為 \(0\) 。進一步地,若兩條直線平行且過同一點,則兩直線重合。

那麼,問題簡化為我們有直線 \(AB,CD\) 交於一點,想求出交點 \(E\)

如果兩直線相交,則交點只有一個,我們記錄了直線上的一個點和直線的方向向量,所以我們只需要知道這個點與交點的距離 \(l\) ,再將這個點沿方向向量平移 \(l\) 個單位長度即可。

考慮構造三角形,利用正弦定理求解 \(l\) ,可以利用向量積構造出正弦定理。

由上圖可知, \(|\mathbf a\times \mathbf b|=|\mathbf a||\mathbf b|\sin \beta\)\(|\mathbf u\times \mathbf b|=|\mathbf u||\mathbf b|\sin \theta\)

作商得:

\[T=\frac{|\mathbf u\times \mathbf b|}{|\mathbf a\times \mathbf b|}=\frac{|\mathbf u|\sin \theta}{|\mathbf a|\sin \beta} \]

可以看出, \(|\frac{|\mathbf u|\sin \theta}{\sin \beta}|=l\) 。若絕對值內部式子取值為正,代表沿 \(\mathbf a\) 方向平移,反之則為反方向。

同時,我們將 \(T\) 直接乘上 \(\mathbf a\) ,就自動出現了直線的單位向量,不需要進行其他消去操作了。

於是,只需要將點 \(P\) 加上 \(T\mathbf a\) 即可得出交點。

求任意多邊形的周長和麵積

求任意多邊形的周長

直接計算即可,簡潔即美德。

求任意多邊形的面積

考慮向量積的模的幾何意義,我們可以利用向量積完成。

將多邊形上的點逆時針標記為 \(p_1,p_2,\cdots ,p_n\) ,再任選一個輔助點 \(O\) ,記向量 \(\mathbf v_i=p_i-O\) ,那麼這個多邊形面積 \(S\) 可以表示為:

\[S=\frac{1}{2}|\sum_{i=1}^n \mathbf v_i\times \mathbf v_{i\bmod n+1}| \]

圓與直線相關

求直線與圓的交點

首先判斷直線與圓的位置關係。如果直線與圓相離則無交點,若相切則可以利用切線求出切點與半徑所在直線,之後轉化為求兩直線交點。

若有兩交點,則可以利用勾股定理求出兩交點的中點,然後沿直線方向加上半弦長即可。

求兩圓交點

首先我們判斷一下兩個圓的位置關係,如果外離或內含則無交點,如果相切,可以算出兩圓心連線的方向向量,然後利用兩圓半徑計算出平移距離,最後將圓心沿這個方向向量進行平移即可。

如果兩圓相交,則必有兩個交點,並且關於兩圓心連線對稱。因此下面只說明一個交點的求法,另一個交點可以用類似方法求出。

我們先將一圓圓心與交點相連,求出兩圓心連線與該連線所成角。這樣,將兩圓心連線的方向向量旋轉這個角度,就是圓心與交點相連形成的半徑的方向向量了。

最後還是老套路——沿方向向量方向將圓心平移半徑長度。

極角序

一般來說,這類題需要先列舉一個極點,然後計算出其他點的極座標,在極座標系下按極角的順序解決問題。

例題 「JOI Spring Camp 2014 Day4」兩個人的星座

平面內有 \(n\) 個點,有三種顏色,每個點的顏色是三種中的一種。求不相交的三色三角形對數。 \(6\le n\le 3000\)

題解

如果兩個三角形不相交,則一定可以做出兩條內公切線,如果相交或內含是做不出內公切線的。三角形的公切線可以類比圓的公切線。

先列舉一個原點,記為 \(O\) ,以這個點為極點,過這個點且與 \(x\) 軸平行的直線作為極軸,建立極座標系,把剩餘點按極角由小到大排序。然後統計出在極軸上方和下方的每種點的個數。

然後根據點列舉公切線,記列舉到的點為 \(P\) ,初始時公切線為極軸。開始統計。那麼一定存在一條公切線過點 \(O\) 和點 \(P\) 。因為公切線與三角形不相交,所以一方選擇公切線上方的點,另一方一定選擇下方的點。然後利用乘法原理統計方案數即可。

統計完後轉公切線,那麼點 \(P\) 一定改變了相對於公切線的上下位置,而其他點不動,應該只將它的位置資訊改變。

這樣,可以發現,同一對三角形最終被統計了 \(4\) 次,就是同一條公切線會被列舉兩次,最後做出的答案應除以 \(4\)

分析一下演算法複雜度,我們枚舉了一個原點,然後對於每一個原點將剩餘點排序後線性統計。於是時間複雜度為 \(O(n^2\log n)\)

程式碼編寫注意事項

由於計算幾何經常進行 double 型別的浮點數計算,因此帶來了精度問題和時間問題。

有些問題,例如求點座標均為整數的三角形面積,可以利用其特殊性進行純整數計算,避免用浮點數影響精度。

由於浮點數計算比整數計算慢,所以需要注意程式的常數因子給時間帶來的影響。

後記

來源