1. 程式人生 > >點集最小外接矩演算法

點集最小外接矩演算法

作者:馮浩 時間: 2007.10.11 文件型別/出處:NOI專刊
題目簡述:

給出一個平面點集S,求一個面積最小的矩形使其包含S所有的點。

預備知識:

在求解這道題之前我們先要了解一些關於凸包的知識。

什麼是凸包?簡單地說,對於一個平面點集S,我們把完全包含該點集的最小的凸多邊形叫做點集S的凸包H。

凸包一個很重要的性質就是它“凸”的性質。這個性質對我們理解和計算凸包都有很大的幫助。

I) 對點集S中任意一點a,當且僅當存在直線p過a點並使得S中除a外所有點均在p的一側,則a為凸包上的一頂點。

II) 對點集S中任意兩點a,b,當且僅當S中除a,b以外所有點都在過點a,b的直線p的一側,則線段ab為凸包上的一條邊。

III) 對點集S中任意四點a,b,c,d,當d在三角形abc中(包括邊),則d不是凸包上的點。

上面的幾條關於凸包“凸”的性質為我們計算凸包提供了一個基礎。這裡我們將介紹兩種簡單且被廣泛運用的演算法――Gift-Wrapping和Graham-Scan演算法。

Gift-Wrapping演算法:

通過性質(I),我們可以找到一個特殊點,如具有最小y座標且x座標儘可能小的點。將它作為計算凸包的第一個頂點。確定了起點後,我們就可以通過Gift-Wrapping演算法計算出點集的凸包。下面的步驟很直觀的描述了這個演算法:

1) 把點集中所有點都看成是固定在平面上的柱子,想象我們在起始點柱子上繫上一根身子。

2) 把繩子沿水平方向向右拉直,並逆時針旋轉,當繩子碰上一根柱子,則對應了凸包上的一點

3) 繼續旋轉繩子,每次確定一個凸包上的頂點,直至繩子回到起點。

          圖一:Gift-Wrapping演算法計算凸包的過程

每次通過旋轉繩子找到下一個凸包頂點需要對點集中所有剩餘點進行一次比較,所以這

一步的時間複雜度是O(n)。每個凸包上的頂點都需要進行一次旋轉操作,而最壞情況下,凸包頂點個數可以和點集個數相等,所以整個Gift-Wrapping演算法的時間複雜度是O(n2)的。

Graham-Scan演算法:

Gift-Wrapping演算法無論從理解還是從實現上來說,它都是十分簡單的。但由於它的複雜度並不理想,我們無法利用它來求解大規模的凸包問題。因而,我們將介紹一種高效的計算凸包的演算法――Graham-Scan。

Graham-Scan演算法主要可分成兩部分:

1) 同Gift-Wrapping一樣,需要先找出一個起始點。將這個點作為原點,進行夾角排序。

2) 先將起始點壓入堆疊H中,再按照已經排好的順序對每一個點進行掃描,同時維護堆疊H。這個堆疊表示的是到目前為止,所有已經掃描過的點對應的凸包。每當掃描一個點p的時候:

a) 如果堆疊的元素少2個或者堆疊頂端的兩個點與p構成左轉關係,則將p壓入堆疊中。

b) 否則,棧頂元素出棧並繼續進行a的判斷。

當所有點都掃描完後,堆疊H即為我們要求的凸包。

 圖二:Graham-Scan演算法的掃描過程(堆疊H儲存的即實線連線起來的點)

分析Graham-Scan的複雜度:第一步中找出起點並進行極角排序的複雜度是 O(n log n)。第二步中每一個點僅會被掃描一次並相應維護一次堆疊H 。而維護堆疊過程中每次訪問堆疊H中的點,要麼這個點被刪除,要麼就停止堆疊的維護,所以所有堆疊維護加起來最多隻訪問了2n次。故這部分的複雜度是O(n)。綜合起來,Graham-Scan演算法的時間複雜度是O(n log n)的。

演算法分析:

現在考慮這道題目,題目要我們求出一個最小面積的矩形能夠覆蓋給定的所有點。易知矩形覆蓋所有點當且僅當它覆蓋這些點的凸包。故而,問題可以轉化為對於一個凸包,求出一個面積最小的矩形來覆蓋它。

那麼這個覆蓋凸包的最小矩形有什麼性質呢?

首先,這個矩形的四條邊上必然都有凸包的頂點。這個很容易想清楚,如果矩形的某條邊沒有碰上凸包的頂點,那麼我們一定能把這條邊向裡壓,從而得到一個更小的滿足條件的矩形。

其次,這個矩形至少有一條邊與凸包上的一邊重合。這個性質不容易直觀地想清楚,需要書面證明一下。由於完整的證明需要分成很多情況來討論,比較繁瑣,所以這裡僅選取其中的一種情況來證明,其他情況可以類似地進行證明。

利用反證法,我們假設覆蓋凸包的最小矩形所有邊都沒有和凸包的邊有重合,也就是說,最小矩形的每條邊上僅有一個凸包的頂點。如圖三所示,矩形ABCD是覆蓋凸包的最小矩形,M、N、P、Q為凸包在矩形四條邊上的頂點。我們分別作MM’⊥ CD,NN’⊥ AD。則矩形ABCD的面積S = MP×Cos(∠PMM’)×NQ×Cos(∠QNN’)。我們將矩形旋轉X度(順時針為正,逆時針為負),仍使矩形覆蓋凸包且M、N、P、Q分別在它的四邊上。則此時新矩形的面積S = MP×Cos(∠PMM’+ X)×NQ×Cos(∠QNN’- X) 。我們僅需考慮Cos(∠PMM’+ X)×Cos(∠QNN’- X)的單調性。

Cos(∠PMM’+ X)×Cos(∠QNN’- X)

= 1/2[Cos(∠PMM’+ X + ∠QNN’- X) + Cos(∠PMM’+ X - ∠QNN’+ X)]

= 1/2[Cos(∠PMM’+ ∠QNN’) + Cos(∠PMM’- ∠QNN’+ 2X)]

∵0≤∠PMM’< π/2 , 0≤∠QNN’< π/2

∴-π/2 <∠PMM’- ∠QNN’< π/2

∴Cos(∠PMM’- ∠QNN’)不可能取到最小值

∴x在0左邊的一個區間中f(x) = Cos(∠PMM’- ∠QNN’+ 2X)遞增,或x在0右邊一個區間中f(x) = Cos(∠PMM’- ∠QNN’+ 2X)遞減。

因而,對於這樣的矩形,我們總可以順時針或逆時針旋轉一個小角度,從而獲得一個更小的矩形,這與假設矛盾。故最小矩形至少有一條邊與凸包一邊重合。

瞭解到最小矩形所具有的這兩個性質後,我們就能夠很容易的想到一種演算法,列舉凸包上哪條邊與矩形的邊重合,再找出在這條直線投影的正負方向上最遠的和到直線距離最遠的三點,從而確定和計算出矩形的面積,最後選取最小值,即為覆蓋凸包的最小矩形的面積。

我們用最樸素的方法去實現它,列舉每條邊後再把剩餘的點都掃描一遍,來找出另外三點,計算出矩形的面積。這樣做時間複雜度是O(n2)得。就本題來說已經可以接受了。但如果規模再大一點,怎麼辦呢?我們能不能做得更好呢?

答案是能!我們還有一個很重要的資訊沒有利用到,對凸包上任意一條邊,依次計算出凸包頂點到它的距離或投影距離,構成的序列總是一個先增再降的。同時,注意到如果逆時針順序列舉重合的邊時,每次找出來的另外三點也總是在向逆時針方向移動。

由此,我們就得到了一個更加高效的演算法。列舉過程中,逆時針旋轉到下一條邊後不需要再重新掃描所有點,只要分別從上一條邊確定的三點出發,向後比較,找到最大值,來更新這三個點即可。

在列舉過程中,三個點的指標都只會對每個頂點訪問一次,所以這個過程的平攤複雜度是O(n)的。結合前面計算凸包的過程,在O(n log n)的時間內我們就能夠圓滿地解決這題了。

瞭解到最小矩形所具有的這兩個性質後,我們就能夠很容易的想到一種演算法,列舉凸包上哪條邊與矩形的邊重合,再找出在這條直線投影的正負方向上最遠的和到直線距離最遠的三點,從而確定和計算出矩形的面積,最後選取最小值,即為覆蓋凸包的最小矩形的面積。