極點法和極邊法的複雜度分別為o(n^4)和o(n^3),當點集s的規模稍大時就難以適用了。為了滿足實際需要必須尋找更高效的演算法來構造凸包。
在引入新演算法之前首先來回顧一下經典的演算法思想:減治(decrease and conquer),注意不是分治(divided and conquer),二者稍有區別。簡單來講就是將問題劃分為乙個個簡單的小問題,減而治之,逐個求解,最終就能得到整個問題的解。
減治法的經典例子就是插入排序(insertion sort)。插入排序的過程可以歸結成下圖:
排序的過程中將序列分為兩部分:已排序部分(sorted)和未排序部分(unsorted)。每次排序都是從unsorted中拿出乙個元素,通過一次順序查詢過程在sorted部分中找到位置並插入其中。
整個插入排序的過程就是逐個元素的去「蠶食」unsorted部分的過程,連續的進行這個操作就會將整個問題解決。這種將大問題分解成小問題的減治過程,又被看成一種遞增的、增量式的策略(incremental strategy)。這種思想為解決凸包問題提供了新的思路:從逐個插入新點的角度構造凸包。
典型流程如下圖(標識為:極點/整體規模):
插入新的點可能的情況有:新點對凸包有「貢獻」,例如5/5→6/6,6/6→7/7;新點也有可能沒有「貢獻」,例如7/7→7/8;還有可能使原先有「貢獻」的點失效,極點數量減少,例如7/8→6/9。那麼如何對不同情況進行處理呢?
構造過程的核心演算法應該是:判定待定點是否位於某多邊形內部(in-convex-polygon test)。再看上圖流程,實際上每步的核心就是判斷點位於多邊形內部還是外部,若落在外部,則新插入的點就是下乙個極點,否則捨棄。
考慮基本情況,給定乙個點和乙個多邊形,如何高效判斷該點與多邊形的位置關係呢?
一種思路是:我們可以先對多邊形進行乙個「預處理」,給每個點按序編號,模擬有序向量二分查詢的思想,來逐步縮小規模。如下圖:
首先任選一點為基準點(藍色點),然後用二分法選取其餘點的「中點「(預處理已經為所有點排了序),然後判斷基準點到終點的有向直線與待定點的位置關係(to-left test)。然後可將搜尋範圍減半,反覆上述過程,直到最後退化為平凡情況:三角形與點的位置關係(in-******** test)。
分析一下演算法的整體複雜度:整個演算法共log(n)步,每步的to-left test或in-******** test都為常數成本,則整體複雜度為log(n)。至此,我們似乎得到了乙個log(n)的「高效」演算法,但是這種方法真的可行嗎?
注意,每步都會將原凸包規模減半,也就是說凸包是動態的,隨時可能變化。這種方法和極點法或極邊法中靜態查詢的情況是完全不同的。
模擬插入排序的過程來解釋這個問題。為何插入排序的複雜度是n^2而非nlog(n)?每次插入時,既然sorted部分已經有序,為何不使用二分查詢來取代順序查詢(複雜度由n變為log(n))?這不得不考慮sorted部分的動態性,每次插入後它的結構都會改變,而二分查詢必須在靜態結構中實現。當然可以使用std::vector這類支援按秩訪問(call by rank)的資料結構,但是插入時維護vector的成本依舊是線性複雜度。因此插入排序的總體複雜度是n^2。要處理的凸包與插入排序中sorted部分本質是一樣的,它們都不是靜態不變的結構,而要隨著演算法執行而不斷變化。若要每次在log(n)成本下完成待定點的in-convex-polygon test,必須將凸包儲存為類似vector的資料結構,但是每次向這種資料結構插入新點的成本依舊是線性的。因此對凸包進行的所謂「預處理」是沒有意義的,這種減治策略演算法複雜度最低應該為o(n^2)。
到現在問題依舊沒有解決,究竟如何用這種增量式的策略來構造凸包?其實複雜問題中最樸素、最基本的方法反而是最有效的。
in-convex-polygon test最基本的方法是什麼?就是按一定方向(約定為逆時針)凸包的每條邊和待定點做to-left test,一旦有一次test為false就能斷定點在凸包外面。這實際上就是將in-******** test推廣多邊形的情況。因此每次in-convex-polygon test的成本就會變成當前凸包的規模,也就是n,對於每個新點做一次in-convex-polygon test,構造演算法的整體複雜度就是o(n^2)。演算法的複雜度從極邊法的o(n^3)又下降了乙個數量級。
整個構造演算法可以分成兩大部分:
上述方法已經將第乙個問題解決了,剩下的就是如何插入新點的問題。考慮以下典型情況:
新點位於原凸包外部,如何將新點插入得到新凸包?直覺判斷應該是如下連線方法:
將新點x插入原凸包的過程,本質上就是尋找兩個連線點s和t,將x和t、s分別連線得到新的凸包。注意t和s兩點將整個原凸包邊界分為兩部分:st和ts兩個有向段。構造新凸包就要保留遠端st、捨棄近端ts。取代近端ts的兩條線就是x和t的連線xt和xs,被稱為切線(tangent)或者support line。
t、s二點的查詢就成了問題的關鍵。
我們在凸包上任取一點v,按逆時針方向v點會有乙個直接前驅點和直接後繼點。考察有向直線xv與點v直接前驅和直接後繼的位置關係(兩次to left test),記錄為乙個pattern表。
結果無非是四種情況:v的直接前驅和直接後繼相對於有向直線xv的位置是rl,lr,ll,rr。例如上圖黃色點v,是r和l;藍色點v分別是l和r。實際上凸包邊界st上所有點的pattern都為rl,ts上所有點的pattern都為lr。關鍵點在於:點s的pattern是ll,點t的pattern為rr。
因此對凸包邊界每個點做兩次to left test,判斷其pattern就可找出s和t,花費時間成本為常數。
再來回顧整個凸包構造演算法的兩大問題:in-convex-polygon test和插入新點。分開考慮只是為了將思路簡化,實際上這兩個問題可以套用乙個演算法,同時來解決。
具體做法就是,對於每個待定點x,不必特意去考慮它與凸包的位置關係,而是遍歷凸包上每乙個點。對於凸包邊界上的每乙個點,我們都能通過兩次to left test迅速判斷出pattern。
對於x位於凸包外部的情況,經過遍歷凸包的點,我們很容易就能得到s和t的位置,得到兩條support line,從而構造出新的凸包;而對於x位於凸包內部的情況,凸包邊界每個點都不可能出現rr或ll的情況,直接捨棄x即可。
每次遍歷凸包邊界點的複雜度為o(n),整個構造過程要增量式的逐點考察,自然得到了乙個o(n^2)的incremental construction演算法。
這就是所謂增量構造法來構造凸包的過程。構造過程巧妙的避開了特殊處理諸如5/5→6/6、7/7→7/8、7/8→6/9等複雜情況,採用一致的思路逐個考察「新點」,最終完成凸包的構造。
計算幾何凸包入門詳解
講這個之前,先說一下我自己的看法 求凸包網上有很多種方法,個人覺得最好用最常用的就是graham掃瞄法,本篇博文我也就只講這一種演算法求解凸包。講這個問題之前我們必須要弄清楚何為凸包?我來口胡一下吧,很明顯凸包這個名詞就已經給了我們乙個重要的資訊,那就是這個東西它肯定是乙個凸多邊形。那除了是凸多邊形...
計算幾何入門 1 7 凸包的構造 分治法
graham scan演算法說明了凸包構造問題的下界o nlogn 是可以達到的。其實o nlogn 的演算法遠不止這一種,分治法就是一種能達到o nlogn 複雜度的思想。在此引入運用分治思想的兩種演算法來構造凸包。引入新演算法之前依舊先來回顧乙個經典排序演算法 歸併排序 merge sort 歸...
計算幾何 凸包
有多個手機訊號發射器,求解乙個最小區域,要求所有的發射器都包含在這個最小區域中,並且任意兩台發射器之間的交流包含於在這個區域內。將所有的發射器看做為點,任意兩個點之間的連線都包含於乙個平面s 乙個平面的子集s 是凸的,當且僅當 s中的任意兩個點之間的連線都包含於 s中。點集 p的凸包是所有包含 p的...