計算幾何入門 1.7:凸包的構造——分治法

Graham Scan算法說明了凸包構造問題的下界O(nlogn)是可以達到的。其實O(nlogn)的算法遠不止這一種,分治法就是一種能達到O(nlogn)複雜度的思想。在此引入運用分治思想的兩種算法來構造凸包。

 

一、歸併排序與分治思想

引入新算法之前依舊先來回顧一個經典排序算法:歸併排序(merge sort)。歸併排序的基本流程如下:

算法分爲兩個階段:分(divide)和歸併(merge)。分的階段將待排序列均分到一個個子序列(如圖中劃分到單個元素)。歸併階段將分好的子序列兩兩合併成有序序列,重複合併的過程直到整體歸爲一個序列。歸併過程共logn步,每步耗費n的時間,總體複雜度爲O(nlogn)。

 

歸併排序的過程就是一個典型的分治(divide-and-conquer)策略。凸包構造問題也可以套用這種策略來分而治之,逐步求解。我們可以將待處理點集S分爲同等規模的兩個子點集,並分別對其求凸包。

有了兩個子解後,問題就變成了如何適當加一些邊,將兩個子凸包merge成整體解。分治法核心的任務就是如何merge。

 

二、分治法(1)

接下來的分析不考慮退化情況,比如三點共線等特殊情況的處理。

 

分治法解決問題的過程可以概括爲:大事化小,小事化了。就是首先將問題劃分爲易求解的子問題,子問題套用已知方法解答即可。例如子凸包的構造就能用Graham Scan來解決。

 

Graham Scan解決問題的前提是:參照基準點,其他點按極角有序排列,也就是構成了一個有序的星形多邊形(star-shaped polygon)。首先要做的就是將兩個子凸包預處理成兩個star-shaped polygon。

 

由於任何一個凸多邊形都是star-shaped polygon,它必然有一個,其他點按極角有序排列。問題在於如何找到一個公共核,使得兩個子凸包同時關於這個核是極角有序排列的。也就是公共核處於兩個凸包的交部分,這樣是最好處理的情況(如下左圖)。不過還有可能有其他情況,不能找到公共核(如下中圖),甚至兩個凸包根本不相交(如下右圖):

這就要將分治策略分不同情況來實現。首先看最簡單的情況,兩個子凸包有公共核。

 

先找其中一個子凸包的核,我們可以任取該子凸包上的三點構成三角形,求三角形重心作爲核。然後判斷這個核是否也在另一個子凸包內部,若判定爲真,就是有公共核的最簡單情況。判定方法也就是之前提過的in convex polygon test,對凸包每條邊做to left test即可,在線性時間內可以判定。

相對於公共核,兩個子凸包的各自有序排列,相互交錯。要做的就是將二者點序列合併,方法正是典型的二路歸併,線性時間可以完成。最後進行Graham Scan即可得到大凸包。

 

存在公共核的情況處理是很簡單的,再看第一個子凸包的核落在第二個子凸包外部的情況。如下圖所示:

這中情況與增量構造法的情況很相似,P1的核x相對於P2就是一個新加入的點。做出兩條support line:x→t和x→s,捨棄P2上t→s路徑的點即可。這樣P2中剩餘點與x構成了一個星形多邊形,x也成爲了P2的核。這就轉化成了第一種有公共核的情況。

 

三、分治法(2)

上述分治策略的算法過於複雜,所以引入一種更加簡明的分治策略。這種分治策略也會爲三角剖分等問題提供思路。

 

首先規定一種點集劃分的策略。假設待合併的兩個子凸包是沿着某方向是分離的,二者不想交。例如下圖凸包P1和P2就是相互分離的:

這樣劃分會使得合併更加簡明,不必區分多種複雜情況。爲了滿足這種劃分策略,引入一種預處理,也就是一個x方向的排序過程(X-sorting)。排序後就可取點x座標的中值,將點集劃分爲規模相當的左右兩個子集。每個凸包都有其最左點l最右點r,如上圖。

 

merge操作就是將兩個左右相離的兩個子凸包合併爲一個大凸包的過程了。運算的關注的正是兩對l和r點。

 

先直觀感受一下merge操作要添加的新極邊:

上下兩條紫色邊正是要求的新邊,又稱支撐邊(support line),並且每次merge只會增加兩條新邊。兩條邊類似兩個圓的公切線(common tangent),將二者連接起來。

 

直觀上感覺,兩條support line正是兩個子凸包的最高點t和最低點b相互連接得到的,這些點只需線性時間就能找到。當真如此的話凸包構造的下界就成了O(n),顯然直覺是錯誤的。例如下面的兩種情況,support line就和b、t兩點沒有直接關係了:

      

構造support line的過程需要縝密的分析,並非憑直覺能得到的。

 

構造過程首先從左凸包的r點右凸包的l點連線開始,以這條線爲基礎逐步得到support line。

注意一個細節問題:如何得到各子凸包的l點和r點。每次合併都會產生新的凸包,所以凸包是一個動態的結構。當然可以每次計算出最左點和最右點,只需要線性時間。但是這並不是最優的方式。考慮分治的思想,就整個merge流程來講,是自底向上將子凸包兩兩合併的過程。因此只要在最底層上最小的子凸包中記錄最左點和最右點,每次merge更新一下這兩個變量即可,只需要O(1)的常數時間!這種優化對整體的複雜度上線nlogn雖然沒有影響,也能爲程序節省一部分的開銷。

 

再看如何將最初的r-l線變成support line,在此以upper support line爲例。算法的核心依然是to left test。

 

觀察support line的兩個端點的特徵,可以看出他們對於前驅後繼都是RR或LL型的,也就是前後相鄰的點都落在support line的同側。其餘任兩點的連線都只能是LR或RL的,這也是增量構造算法的核心思想。兩次to left 測試即可得出類型。

 

從r-l線出發,首先看l點,可以發現其前驅後繼相對於r→l是RL型的。若要得到RR型的特徵,必須向其後繼方向推進一個單位。

將l點向其後繼推進一單位,更新了r-l線。判定l點依舊是RL型的,r-l線還不是support line,繼續推進l點。判定新的r-l線,l點的特徵變爲了RR型,暫時可以認爲l點滿足要求,接下來考察r點。

 

r點的前驅後繼對於線l→r是RL型的,若要得到LL的特徵,必須向其前驅方向推進一個單位。以此往復,直到r點特徵變爲LL。

 

然後回頭考察l點,發現此時l點已經不滿足RR了,變回了RL。繼續同樣的步驟推進l點直到特徵變爲RR。然後在回頭考察r點,發現它依舊滿足LL,算法停止。至此r和l兩點連線就是upper support line了,同樣lower support line構造方式類似。

 

回顧由r-l線逐步推進得到support line的過程,每次操作一個端點,得到是一種“Z”字形(zig-zag)的推進軌跡。操作點的切換由另一點滿足要求決定,而算法停止的依據是兩個端點同時滿足了要求。這種方式類似快速排序構造軸點的過程,左右兩軸點交替操作,直到二者都滿足要求時算法停止。

 

如此通過兩次“zig-zag”的過程就能得到兩條support line,完成兩個子凸包的合併。

 

分析一下算法時間複雜度。算法首先要按照x座標排序,排序複雜度爲O(nlogn)。再看merge過程,無論是左側子凸包還是右側子凸包,對於其每個點的操作至多隻有以此,也就是每次歸併是線性時間。歸併共logn次,算法的總體複雜度就是O(nlogn)了。

 

最後總結一下第二種分治法的特點。此前Jarvis March算法雖然以平方複雜度爲上界,但其”輸出敏感性“使得實際複雜度爲O(hn),最好情況下僅甚至爲線性。例如如下情況:

Jarvis March算法的複雜度變爲了O(4n),而此種分治法依舊會經歷按部就班的X-sorting,一上來就註定了O(nlogn)的複雜度,然後經歷同樣O(nlogn)的merge過程。也就是說這種分治法在各種情況下的表現都是很均勻的。

 

本文是學堂在線課程《計算幾何》的筆記,幫助理解和記錄思考過程,不夠嚴謹請見諒。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章