1. 程式人生 > >快速和改進的二維凸包演算法及其在O(n log h)中的實現(理論部分)

快速和改進的二維凸包演算法及其在O(n log h)中的實現(理論部分)

在國外某知名網站上瀏覽資訊時發現了一篇非常好的論文,因為是英文的,自己翻譯、整理了一下,如果想看原始的可以去以下連結:https://www.codeproject.com/Articles/1210225/Fast-and-improved-D-Convex-Hull-algorithm-and-its

  • 介紹

這篇文章是關於一個相對較新和未知的凸包演算法及其實現。這個新的演算法有很好的效能,本文給出了很多實現的變化和/或優化。本文包含詳細的解釋,程式碼和基準,以便讀者容易理解和比較結果與最流行的實際凸包演算法及其實現。你會在這裡找到真正的工作和測試程式碼。本文結合:程式碼,演算法和數學。這是一個有點專業化,但我希望你會在這裡找到一些有趣的東西。

  • 不斷優化的過程

    最初的原因是展示了用C ++完成的Ouellet演算法的新開發實現。根據提供的效能測試,新實現比C(也提供)中的Chan實現快了約4倍。
    使用AVL樹作為Convex Hull點而不是基於陣列的容器來新增和顯示Ouellet演算法的另一個實現。有2個實現基於AVL樹:一個簡單的和更有效的一個與許多增加的優化。
    解釋AVL版本中添加了哪些優化以獲得更好的效能(Ouellet AVL v2 vs Ouellet AVL)。
    要共享我的工作臺工具,以輕鬆比較二維演算法,例如使用Convex Hull演算法或任何其他以點為輸入並生成點作為輸出的演算法。例如,工作臺也可以用來測試最小的封閉圓演算法(提供的程式碼)。
    為了顯示和記錄我的演算法的第一次迭代的一些改進,使得我的C#Convex Hull實現比用C編寫的Chan演算法的Pat Morin實現(至少用於我用於測試的所有“常規”隨機點生成器)更快。
    為了展示許多現實生活中的凸包演算法實現並比較它們的效能。
    為了顯示一組點(提供的程式碼)的所有排列可能性的生成器的最快實現。
    分享其他的想法/發現(多執行緒,維基百科,最小的圍圈,...)。
    
  • 什麼是凸面船體

定義

根據維基百科:“在數學中,凸包或凸信封一組X點在歐氏平面或歐幾里得空間是最小的凸集包含X,例如,當X是一個有界平面的子集,該凸形船體可以被形象化為圍繞X延伸的橡皮筋所包圍的形狀。

  • 凸殼在2D和3D中的應用

凸Hull演算法是計算幾何中的一個基本演算法,在計算幾何的基礎上提出了許多演算法。還有很多應用程式使用Convex Hull演算法。

在許多地方使用凸面船體,所有點所佔據的空間周圍的路徑成為一個有價值的資訊。有一些例子:

確定他們的網路(IEEE)的電力公用事業模擬的阻抗區域。這就是為什麼我幾年前遇到凸包的原因。
它可以用於輸入其他演算法,以提高其效能。例如,通過快速凸包演算法實現預處理點,通用的最小封閉圓演算法可以大大提高其效能。在提供的基準中可以顯示效能差異。
凸殼可用於影象處理。
在GIS系統中用於建立一個包含多邊形的要素類,該多邊形表示包含每個輸入要素或每組輸入要素的指定最小邊界幾何。
還有其他許多... 在Quora上的11個應用程式

凸殼的發現也在大學中被廣泛使用和研究。

  • 凸包演算法的效能和複雜性

有許多不同的複雜度的凸包演算法。維基百科 - 凸浮體演算法列出了很多。Ouellet Convex Hull(或Liu and Chen)演算法不在維基百科中。有關更多資訊,請參閱本文末尾的附錄B - 維基百科。演算法效能的一個很好的指標通常與其複雜性有關。建議選擇一個複雜度最快的演算法。在凸包的情況下, 速度測試結果 證實了這個經驗法則。目前,最有名的凸包演算法的複雜性順序是O(n log h)其中:

“n”是所有源點的數量
“h”是所有源點中的結果點的集合,其限定了凸包的路徑。

根據測試和個人經驗,在一般用例中,“h”比“n”小很多。例如,在我測試一個圓圈內的200 000 000個隨機點的情況下,凸形船體通常由200到600點的普通隨機發生器(圓圈或者扔掉)組成。考慮到它存在複雜度為O(n^2)的演算法),O(n log n)和O(n log h)。很容易認識到,基於“log h”而不是“log n”的複雜性會大大提高效能。但是也有可能有兩種演算法在相同的複雜度下比另一種演算法快得多。這是這篇文章的主要原因。但是,正如您將會看到的那樣,您可以使用提供的程式碼來測試自己,一般情況下的結果表明Ouellet演算法是最快的演算法,並且有相當好的餘量。它也有其他的優點,取決於你的需求。比較時,還應該考慮演算法實現的語言。在實際的測試結果中,你必須考慮到C#的聲譽比C慢。我個人的測試似乎是根據C#比C ++慢的事實。

  • Ouellet凸包演算法

演算法的主要思想

這是一個快速的總結。更多的資訊可以在一個凸的Hull演算法及其在O(n log h)中的實現中找到。

該演算法有三個主要步驟按順序發生:

第1步 - 確定虛擬象限邊界
    我們找到了邊界(左,上,右,下)
    或所有輸入點(所有輸入點的第一遍)。
    我們定義了4個基於邊界點的虛擬象限(Q1是基於頂點和最右邊的點,其根基於頂點的x和最右點的y)。同樣的事情適用於每個象限。
    每個船體點將保持每個象限(虛擬象限),並將始終訂購。注意:在所有提供的實現中,對於每個象限,所有的點都是逆時針儲存的。
第2步 - 找到船體點
    對於每個輸入點,我們會發現它是否是象限的一部分,如果應用則插入為凸面點。
    使用二分法,我們搜尋輸入點應該插入的地方,如果適用。
    如果它是凸包的一部分,我們插入輸入點。如果是這種情況,我們也刪除新的凸面點使其失效的任何鄰居。
第3步 - 合併象限結果
    合併四象限船體點的結果,消除在象限邊界的任何重複
  • Ouellet VS.的區別 Liu和Chen凸包Hull演算法

在進入凸包Hull優化之前,應該清楚的是,Liu和Chen凸包Hull演算法和Ouellet演算法是基於相同的原理:虛擬象限,至少根據我所理解的劉和陳的文章:https://rd.springer.com/article/10.1631/jzus.2007.A1210。劉和陳的文章沒有程式碼或實現細節和/或連結到任何這些。因此,我決定重新命名我的第一個執行程式碼:Liu和Chen。自那以來,增加了許多優化,提高了效能。包含的基準結果顯示了這些差異。

  • 優化

優化之後是可以應用於一個或多個Ouellet凸包演算法的可選優化。有些僅在Ouellet CPP版本中使用,而其中大部分都應用在Ouellet AVL v2中。劉和陳的執行都沒有包括在內。

Slope VS Cross Product

結果

Cross Product在效能上的使用結果略微有所增加。只是在某些情況下擊敗Chan演算法。但它也有簡化潛在凸包候選人的選擇的效果。對於所有象限來說,交叉乘積可以完全相同,從而實現三件事情:

通過提高每個象限基類的點選擇,簡化和更好的程式碼架構(更通用)。
通過將斜坡的快取管理移除為點結構來簡化
通過從點結構中去除快取的坡度來減小佔地面積。可以使用已經定義的框架點結構。

基準測試結果:

根據包括的基準測試,乘法比分數快1.14。要了解更多關於測試條件的資訊,請參閱用於測試的硬體。2之間的速度差異部分解釋了為什麼跨產品有點快。許多其他變數應該被認為是CPU流水線,記憶體鄰近等等。

  • 快速丟棄(QD)

快速丟棄是“Liu和Chen”演算法和Ouellet演算法之間存在的第一個區別。這是一個簡單的檢查,新增到Ouellet演算法,這是在二分法搜尋的每一次迭代完成的,在大多數情況下,這將快速停止搜尋,因為該點將被拒絕,而不必進一步進入二分法搜尋,並且不必做任何計算。這個優點來自於我們使用象限的事實,而且因為象限凸殼點總是被排序的。一般來說,做這個簡單測試的時間將會節省執行二分法步驟的時間,因為可以在第一步中快速丟棄一個點。快速丟棄是可能的,因為二分法合併到插入點。如果我們考慮到“h”(凸面點數)一般來說,比“n”(輸入點數)要少很多,大多數情況下應該會節省時間。換句話說,應該拋棄很多的觀點,而且很快就有拋棄的機會。

劉和陳和Ouellet之間已經做了兩個最有價值(更接近現實生活)的隨機生成器的測試:扔掉和圈。劉和陳沒有QD而Ouellet有QD。在不好的情況下(扔掉點隨機發生器),QD只是慢一點點(可以忽略不計),對於任意數量的輸入點都相當穩定。在好的情況下(圓點隨機發生器),QD在10萬點上快了1.2倍,效能隨著點數的增加而緩慢下降。如果沒有QD,這可能會更糟糕,但如此輕微,這是一個很好的補充,並應改善一般用法的效能。提供的基準測試可以輕鬆完成。

  • 相物件限檢查(OQC)

在Ouellet演算法中,由於象限定義的方式,兩個相鄰象限不能有任何交集區。奇數和偶數是互斥的,而對面的兩個象限可以共享一個區域。

在演算法的第一個版本中,點象限歸屬是基於象限根點計算的。相反的象限可以有一個相交區域(見下面的例子)。因此,該演算法應該對交叉點中的任何點執行相反的象限檢查(OQC)。OQC來源於檢查其物件象限點的必要性,儘管它已被確定為特定象限的一部分。這種情況只發生在一些具體的點分佈,但應該考慮在內。

以下示例顯示了分發強制演算法執行相反象限檢查的情況。在這個例子中,根據每個象限根點,P1同時是2個象限(Q1和Q3)的一部分。這種情況主要發生在點分佈狹窄的對角線上時:

在這裡,我們有粉紅色的虛擬Q1和綠色的Q3,每個象限的根點為菱形,與象限的顏色相同。正如你所看到的那樣,P1(黃色區域)和Q3(綠色區域)包含了黃色的點,它應該被評估,看它是否是凸面船體的一部分。但是如果我們要對P1進行P1評估,並且在沒有對Q3進行驗證的情況下將其丟棄,那麼我們就會拋棄一個好的候選者。為了不錯過任何象限檢查,懶惰的方法是檢查每個象限的每個點。但是,只有當一個點被確定為象限的一部分時,才能通過僅在相反的象限上進行驗證來輕鬆地進行優化。在大多數的演算法實現中,每個點都要針對所有可能的象限進行檢查,以確保不會錯過任何象限中的任何點。但是在Ouellet CPP和Ouellet AVL v2中,關於Ouellet Convex Hull的每個實現的具體資訊)。我們可以很容易地看到奇數和偶數象限是相互排斥的。換句話說,如果一個點是Q1或Q3的一部分,它不能成為Q2或Q4的一部分,反之亦然。

  • 相物件限檢查旁路(OQCB)

另一個優化是在任何二分步驟中一個點被估計為凸凸點(不僅是象限的一部分),而是不需要對任何其他象限進行檢查,或者是相反的。它不能同時作為2個象限的凸包點的一部分。對一個相反象限的檢查只對一個被確定為一個象限的一部分而沒有被確定為潛在的船體點的點是強制性的。只有象限極限點可在其中將在3個合併成只有一個點,同時2個象限RD演算法的步驟。該優化只在Ouellet CPP和Ouellet AVL v2中完成。

  • 不相交象限檢查(DQC)

PCZ

第一個和最後一個點總是按照每個象限逆時針定義的。PCZ是“潛在的候選區域”,由第一個和最後一個象限點確定。這是一個象限的第一個和最後一個點右邊的區域。該區域每個象限都是唯一的,並且定義了可能新增的候選區域。該區域外的任何其他地方都不能包含此象限的任何有效船體點。

DQC使用PCZ

有一個很好的優化,也可以做,只有在Ouellet AVL v2中使用。開始時要驗證所有象限是否不相交。為了驗證,我們只需要檢查一個象限的根點是否在它的相物件限PCZ之外。如果是所有象限都是這種情況,那麼如果一個輸入點位於一個象限內,我們可以跳過任何其他象限檢查,或者是相反的象限檢查。

這個優化需要2件事情:

一種驗證做前2次的演算法的步驟,以確定是否象限是不相交的。
為2個不同的程式碼第二取決於如果象限是不相交的或不是演算法的步驟:
    如果不相交。使用DQZ
    如果不相交,則使用原始演算法程式碼

  • PCZ點屬(PCZB)

PCZ是在不相交的象限檢查開始時解釋的。使用象限PCZ來確定當前點象限歸屬。

為了快速保留/丟棄潛在的候選點,我們可以通過使用交叉乘積而不是使用象限根點來檢查它是否是目標象限PCZ的一部分。做交叉產品有一個計算成本,但使用它可以防止我們做很多二分迭代。它也有一個優點,就是在一個象限的PCZ中,一個點不能是任何其他象限的一個點。Ouellet AVL v2使用這個優化。

  • 前象限(PQ)

另一個只在“Ouellet AVL v2”中進行的優化,就是總是從最後一個新增點開始象限檢查,然後按順序迴圈剩下的象限。在某些情況下,這種優化可能是有用的,在這種情況下,連續的評估輸入點具有很高的鄰居風險,因此成為同一象限的一部分。這樣做也可以使我們跳過不必要的其他象限檢查,提高效能。雖然這種優化在隨機點放置方面沒有任何優勢,但也不會增加任何缺點。這是一種優化,在某些情況下可能會更好,對其他所有方面都沒有影響。

  • 演算法比較

  • 時間複雜度分析