Voronoi Diagram and Fortune Algorithm

FROM: http://www.cnblogs.com/Seiyagoo/p/3339886.html

Voronoi Diagram——維諾圖

Voronoi圖定義
 
任意兩點p 和q 之間的歐氏距離,記作 dist(p, q) 。就平面情況而言,我們有
          dist(p, q) =  (px-qx)2+ (py-qy)2
 
設P := {p1, …, pn}爲平面上任意 n 個互異的點;這些點也就是基點。按照我們的定義,所謂P對應的Voronoi圖,就是平面的一個子區域劃分——整個平面因此被劃分爲n 個單元(cell ),它們具有這樣的性質:
   任一點q位於點pi 所對應的單元中,當且僅當對於任何的pj∈Pj, j≠i,都有dist(q, pi)<dist(q, pj)。我們將與P對應的Voronoi圖記作Vor(P)。
“Vor(P) ”或者“Voronoi圖”所指示的僅僅只是組成該子區域劃分的邊和頂點。在Vor(P)中,與基點pi 相對應的單元記作V (pi)——稱作與pi 相對應的Voronoi單元(Voronoi cell)。上圖是Voronoi圖,下圖的藍色點圍成的區域(凸包)是它對應的Delaunay三角剖分。
任給平面上兩點p 和q ,所謂 p 和q 的平分線(bisector),就是線段 pq的垂直平分線。該平分線將平面劃分爲兩張半平面(half-plane)。點 p 所在的那張開半平面記作 h(p, q) ,點 q 所在的那張開半平面記作 h(q, p) 。請注意,r  ∈  h(p, q) 當且僅當 dist(r, p) < dist(r, q) 。據此,可以得出如下觀察結論:
     V (pi)  =   ∩ h(pi, pj) , 1≤j≤n, j≠ i 
也就是說,V (pi)是(n-1)張半平面的公共交集;它也是一個(不見得有界的)開的凸多邊形(convex polygon)子區域.
很顯然,Voronoi頂點到相鄰的三個site距離相等;Voronoi邊上任意一點到相鄰的兩個site距離相等;
 
對於任何點q,我們將以q爲中心、內部不含P中任何基點的最大圓,稱作q關於P的最大空圓(largest empty circle ),記作Cp(q)。以下定理指出了Voronoi圖的頂點及邊所具有的特徵: 

對於任一點集P 所對應的Voronoi圖Vor(P) ,下列命題成立: 
1) 點q 是Vor(P) 的一個頂點,當且僅當在其最大空圓Cp(q)的邊界上,至少有三個基點; (Voronoi頂點是三個site的外接圓的圓心)
2) pi 和pj 之間的平分線確定了Vor (P) 的一條邊,當且僅當在這條線上存在一個點 q,Cp(q)的邊界經過pi 和pj,但不經過其它站點。     

 
 
構造Voronoi圖
 
構造Voronoi圖有四種算法:定義法(Intersect of Halfplanes)、增量(incremental)算法、分治法、plane sweep算法;
1、plane sweep(平面掃描)算法又名Fortune算法,它主要由兩部分組成:sweep line(掃描線)和beach line(海灘線);
Fortune算法建立在點、線之間的距離關係上,如下圖所示,平面上任意一點到一個點p的距離與到一條直線l的距離相等,這樣的點有很多,它們構成的軌跡就是拋物線,點p就是拋物線的焦點,直線l就是拋物線的準線;
 
2、回到Fortune算法,這個固定點p就是一個site,l就是sweep line;
sweep line自上而下掃描,平面區域任何點到site與sweep line距離相等的點構成一條拋物線(site就是拋物線的焦點),則n個site的拋物線相交的若干段拋物線弧構成beach line,如下圖的藍色拋物線弧集合;
拋物線之間的交點稱爲斷點(break point),每個斷點都落在某條Voronoi 邊上。這並非巧合,隨着掃描線自上而下掃過整個平面,所有斷點的軌跡合起來恰好就是待構造的Voronoi圖;(幾何證明:斷點到相鄰的兩個site距離總是相等,這個關係隨着sweep line的掃描一直不變,則斷點的運動軌跡就是這兩個site的垂直平分線,也即Voronoi 邊,兩條Voronoi 邊相交又產生Voronoi 頂點)
 
 
beach line上方的Voronoi 頂點和Voronoi 邊已確定,將不會再變化。beach line(曲線)和它上方的直線構成當前的Voronoi 邊,最後隨着sweep line的移動而beach line也在不斷下移,變爲最終的Voronoi 邊; (海灘線沿x 方向單調——即,它與任一垂線相交而且僅相交於一點。)
 
beach line屬性
1、隨着sweep line下降,break points跟蹤Voronoi邊;一個新的break point(新弧形成或者兩個break point融合爲一體)產生一條新的邊;
2、兩個break point相遇產生voronoi頂點
 
3、爲了確定Voronoi 邊和Voronoi 頂點,我們需要維護beach line這個結構,但是隨着l 的運動它會持續不斷地更新。那麼,應該如何表示beach line結構呢?
所謂beach line的組合結構發生變化,指的是其上出現了新的拋物線弧,或原有的某段拋物線弧收縮成一個點並進而消失。在這個算法中,產生新弧,稱爲site event;舊弧消失,稱爲circle event。
 
兩類事件site event和circle event:
1)、site event
sweep line掃到某個site,設爲p,在此瞬間,站點p對應於一條寬度爲零的退化拋物線——亦即,將該新站點p與掃描線l聯接起來的垂直線段。隨着掃描線繼續下移,這個寬度爲0的拋物線將逐漸伸展開來。
site event發生後引起的變化:因爲沿海灘線上各個斷點的運動軌跡,就勾勒出了Voronoi 圖的各邊。所以每發生一次site事件,就會生成兩個新的斷點,此後它們會逐漸地勾勒出同一條新邊。
那爲什麼是同一條新邊呢?實際上,在剛剛誕生的那一瞬間,這兩個斷點相互重合,然後纔會各自朝相反的方向運動,而且它們所勾勒的都是同一條邊(同break point定義處的幾何證明)。在一開始,這條邊與Voronoi圖位於掃描線之上的其它部分並不相聯。隨着這條邊的不斷生長,直到後來它們與其它邊相遇,此時它纔會與Voronoi圖的其它部分聯接起來。
定理:只有在發生某個site事件時,海灘線上纔會有新的弧出現。
 
2)、circle event
發生於原有的某段弧收縮爲一點並即將消失時,假設三段連續的弧α 、α '和α '',這三段弧必然分別對應於三個不同基點pi 、pj和pk ,就在α'即將消失的那一刻,這三個基點所對應的拋物線將相交於同一點q 。此時點q 到掃描線l 與到這三個基點等距離。亦即,存在一個以q 爲中心、穿過pi、pj和pk 的圓,且該圓在最低點處與l 相切。該圓的內部不可能有任何基點——否則,q 到該基點將比到l 更近,而這卻與“q 位於海灘線上”的事實不合。因此,點q 必是Voronoi圖的一個頂點。
 
若海灘線上有某段弧消失,並因而有兩段弧匯合起來,則相應地在Voronoi圖中肯定也會有兩條邊匯合起來(成爲一條新的邊)。海灘線上依次首尾相聯的任何三段弧,其對應的三個基點都會確定一個外接圓;當掃描線觸及某個這類外接圓的最低點時,也就發生了一次圓事件(circle event )
定理:海灘線上已有的弧,只有在經過某次圓事件之後,纔有可能消失。
 
簡單點說,site event發生時,beach line會產生一條新弧,同時就會有一條新邊出現並朝兩端生長,慢慢形成新的Voronoi邊;circle event發生時,會有兩條正在生長的Voronoi邊匯合起來,並在接合處形成一個Voronoi 頂點,同時中間的舊弧消失。
 
4、異常情況
a false alarm:We may have stored a circle event in the event list, but it may be that it never happens
There are two reasons for false alarms: site events and other circle events
我們存儲了circle event,但它可能永遠不會發生,真是一個美麗的錯誤... 在site event和circle event發生時,都會有可能誤報情況。
 
1)、site event:circle event發生時產生的最大空心圓內部還有其他site。
如下面三個圖例,p2、p3、p4組成的外接圓,確定了一個circle event,外接圓y座標最小的點(圖中最低的小紅點)將進入PQ,但是在sweep line碰到它之前,先掃描到了site p7,這樣一來將產生新弧,破壞了原來的<p2,p3,p4>三元組。發生circle event時,並不知道這是一個false alarm,所以直到碰到該外接圓內部存在site。這時需要把這個circle event去掉,也即刪除原先進入PQ中的最低點。也說明了這個外接圓的圓心不是Voronoi頂點,屬於誤報。
 
 
2)、circle event:該事件還沒有來得及真正發生,這一鄰接弧三元組就已經消失了。
如下面三個圖例,<p2,p3,p4>三元組先產生外接圓,第一個小紅點進入PQ,當sweep line掃描到p1時,<p1,p2,p3>三元組也產生外接圓,第二個小紅點進入PQ;但是,當sweep line掃描到第一個小紅點時,它從PQ出隊,隨着sweep line下移,α3消失,<α2,α3,α4>合併爲<α2,α4>破壞了原來的三元組,則<p1,p2,p3>無法形成Voronoi頂點,也即這個circle event屬於誤報。需要刪除PQ中第二個小紅點。
圖像說明:  bayanbox.ir/id/3367913281004602743?download
 
 
相關數據結構
 
構建Voronoi圖需要三個數據結構,分別是平衡二叉樹AVL,優先隊列PQ和雙向邊鏈表DCEL。
1、beach line數據結構AVL:記錄beach line的狀態,包括break points, and the arcs currently on beach line
一個葉子結點表示一段弧,因爲每個弧都一 一對應一個site,所以用site number來存儲;
非葉子結點則表示兩條弧的交點即斷點,用兩條弧對應的site對存儲;因爲弧和斷點都是不斷變化的,所以都用固定的site number來表示。
 
此例中AVL中的p1、p2表示原圖的site p1和site p2對應的弧,<p1,p2>表示兩弧的交點即斷點,其實AVL樹就是site和break point的中序遍歷。
 
若按照這樣的方式來表示beach line,每遇到一個新的site,都可以在O(logn)時間內,沿beach line找出位於該site上方的那段弧:在查找過程中,在每個內部節點處,只要將其對應斷點的x座標,與新site的x座標做一比較。
 
 
爲了處理false alarm的第二種情況,T 的一片葉子若對應於某段弧α,則爲它配備一個指針,指向PQ中的一個(事件)節點——具體說,就是(在將來可能)導致α 消失的那個圓事件所對應的節點。若沒有導致α消失的圓事件,或者還沒有發現這樣一個事件,則該指針被置爲nil。
 
最後,每個內部節點v 也配有一個指針,指向與當前Voronoi 圖對應的雙向鏈接邊表DCEL中的某條半邊(half-edge )——更確切地說,此時與 v 相對應的斷點,正在勾勒出的一條 Voronoi邊,而v 的指針就指向這條邊所對應的那條半邊。 
 
處理:新的site產生一條新弧,對應的舊弧被刪除(DS中對應AVL某葉子節點被刪除);同時,該舊弧指向的event也將被刪除(DS對應PQ中刪除一個元素);
 
添加弧操作:replacing the leaf with a sub-tree
 
刪除弧操作:deleting a leaf from the tree
 
 
 
2、事件隊列PQ:Event queue(on decreasing y-coordinate)
記錄掃描線當前狀態的結構。存儲已確定即將發生的events。對於site event,在sweep line開始掃描之前就可以全部送入PQ;
對於circle event, 不僅要記錄該外接圓的最低點(外接圓與sweep line的切點),還要設置一個指針指向AVL中的某片葉子——這片葉子所對應的,就是在該事件發生時即將隨之消失的那段弧。
 
如果某三個site形成的外接圓,該圓對應的縱座標最小的點(即未來的切點)在sweep line的下面,則爲circle event;並將該點入優先隊列;並且這三個連續的sites與該切點互相鏈接對方。對於false alarm的第一種情況還需處理。
 
處理:sweep line掃描到切點,三條弧變成兩條弧,形成Voronoi頂點;刪除三條弧中間的那條,對應DS則爲刪除葉子節點,並在PQ中刪除該節點指向的event(若有,即爲一個false alarm),同時將合併後的兩條弧分別與原先三條弧的左右兩側各一條弧結合,形成兩個新的三元組,將兩新三元組對應的兩切點加入PQ,並做指針鏈接;
 
 
3、雙向邊鏈表(DCEL):記錄Voronoi狀態,包含half-edges, edges(一對half-edge), vertices and cell records(A chain of counter-clockwise half-edges)
 
At the leaves of the tree, a pointer to the circle event is stored, if the arc defines a circle event. If not, pointer is set to NULL. By maintaining this pointer, we do not have to perform any search after encountering false events.
 
 
 
算法僞碼
 
算法 VORONOID IAGRAM (P) 
輸入:平面點集 P := {p1, …, pn) 
輸出:以雙向鏈接邊表 D 表示的(限制在一個足夠大的包圍框之內的)Voronoi 圖Vor(P) 
1.初始化事件隊列Q :將所有的基點事件插入其中 
  初始化狀態結構T :將其置空 
  初始化雙向鏈接邊表D :將其置空 
2.    while ( Q 非空) 
3.      do 將y- 座標最大的事件從 Q 中取出 
4.      if  ( 這是一個發生於基點 pi 處的基點事件) 
5.        then HANDLESITE EVENT(pi) 
6.        else HANDLECIRCLE EVENT(γ) 
      (* 這裏的γ是T 的一匹葉子,它對應於那段即將消失的弧 *) 
7.(* 仍然存在於 T 中的那些內部節點,對應於 Voronoi 圖的單向無窮邊 *) 
  計算出一個包圍框,其尺寸之大,應足以容下Voronoi 圖中的所有頂點 
  通過對雙向鏈接邊表的適當調整,將這些單向無窮邊都聯接到這個包圍框上 
8.遍歷雙向鏈接邊表中的所有半邊增加相應的單元記錄 
設置好指向這些單元的指針,以及由這些單元發出的(指向對應各邊的)指針
 
處理兩類事件的子程序分別如下:
算法 HANDLESITE EVENT(pi)
 

算法 HANDLECIRCLE EVENT(γ) 
1.將(對應於即將消失的弧α的那匹)葉子γ,從T 刪除掉 
  檢查相關的內部節點,更新其中表示有關斷點的基點對信息 
  若有必要,須對T 做調整,以使之重新平衡 
  在Q 中,刪除所有與α相關的圓事件 
  (* 在T 中,γ的前驅與後繼節點配有相應的指針 *) 
  (* 藉助這些指針,就可以找出這些事件 *) 
  (α在其中居中的那個圓事件,此刻正在接受處理,並已經從Q 被刪除掉了) 
2. 更新存儲當前Voronoi圖的雙向鏈接邊表D : 
   對應於該事件的圓心生成一個Voronoi頂點記錄,並將該記錄插入雙向鏈接邊表;
   對應於海灘線上新生出的斷點,並生成兩個半邊記錄,正確地設置好它們相互之間的指針; 
   將這三個新記錄,與同樣終止於該Voronoi頂點的其它半邊鏈接起來 
3. (* 此前與α緊鄰於左側的那段弧,現可能在某個新的鄰接弧三元組中居中 *) 
  檢查該鄰接弧三元組所對應的兩個斷點是否匯合爲一點 
  果真如此,則
    將對應的圓事件插入到事件隊列Q 中,並 
    在Q 中該節點和 T 中與之對應的節點之間設置指針,使它們相互指向對方 
  (*  此前與α緊鄰於右側的那段弧,現也可能在某個新的鄰接弧三元組中居中 *) 
  對該弧,做類似的處理。

 
 
算法複雜度
 
給定由平面上任意n 個基點構成的一個集合,其對應的 Voronoi圖可以採用掃描線算法,在 O(nlogn)時間內、使用O(n)空間構造出來。因爲Voronoi圖可以歸約爲n個實數的排序問題,則最好時間複雜度爲O(nlogn),即sweep line算法是最優的。
定義法:O(n^2logn),增量算法:O(n^2),分治法:O(nlogn),sweep line算法:O(nlogn)。
 
 
參考
sweep line作者主頁:http://ect.bell-labs.com/who/sjf/ 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章