Two Dimensional Convex Hull
二維凸包
譯 by Felicia Crazy
(QQ 125376968 MSN felicia1101@hotmail.com )
準備知識
數學模型
給出平面內的點集,找出面積最小的凸多邊形,使得這些點在這個多邊形之內(或者在它的邊上)。
可以看出,多邊形的頂點必須是給定點集中的點。
例題:放牧奶牛
農夫約翰想要修建一個柵欄,來防止討厭的當地大學生在他的奶牛們睡覺的時候把它們掀翻。他的每頭奶牛都有一個最喜歡的吃草點,農夫約翰想要把這些點都圍在柵欄內。農夫約翰要圍出一個凸的形狀,因為這樣更容易把奶牛趕進牧場。
協助農夫約翰確定面積最小的而且包括所有奶牛喜愛的吃草點的柵欄形狀。
演算法
解決二維凸包問題有好幾種演算法。這裡,我們只介紹比較容易編碼和記憶的“卷包裹”演算法(其實就是我們所說的格拉漢掃描法——譯者注)。
算 法的基本思想是在一個肯定會在凸包內的點周圍不斷地由順時針或逆時針方向增加頂點,並確保每個內角都小於 180 度(保證最終答案是凸的)。如果三個連續的頂點構成的角度大於 180 度,刪掉中間的點。可以用兩個沿著多邊形邊的連續向量的叉積來判斷角度是否大於 180 度。
凸包演算法流程:
- 找出一個必定會在凸包內的中點
- 計算每個點和中點的連線與x軸的夾角(在 0——360 度的範圍內)
- 根據這些夾角對頂點排序
- 加入最初的兩個頂點
- 對於除最後一個頂點以外的其餘頂點
- 讓其成為凸包上的下一個頂點
- 檢查它和前面兩個頂點組成的角是否大於 180 度
- 如果它和前面兩個頂點組成的角大於 180 度,那麼把它前面那個頂點刪掉
- 加入最後一個頂點
- 完成上述的刪除任務
- 檢查最後一個頂點和它的前一個頂點和第一個頂點所組成的角是否大於 180 度,或者最後一個頂點和第一、第二個頂點組成的角是否大於 180 度。
- 如果第一種情況為真,刪除最後一個頂點,並且檢查倒數第二個頂點。
- 如果第二種情況為真,刪除第一個頂點,繼續檢查。
- 當兩種情況都不為真時,停止。
- 由於角度的計算方式,增加頂點的時間複雜度是線性(就是我們所說的 O(n) )。因此,啟動並執行時間複雜度決定於排序的時間複雜度,所以“卷包裹法”的時間複雜度是 O( n log n ),這是最優的。
虛擬碼
這裡是凸包演算法的虛擬碼:
# x(i), y(i) is the x,y position
# of the i-th point
# zcrossprod(v1,v2) -> z component
# of the vectors v1, v2
# if zcrossprod(v1,v2) < 0,
# then v2 is "right" of v1
# since we add counter-clockwise
# <0 -> angle > 180 deg
1 (midx, midy) = (0, 0)
2 For all points i
3 (midx, midy) = (midx, midy) +
(x(i)/npoints, y(i)/npoints)
4 For all points i
5 angle(i) = atan2(y(i) - midy,
x(i) - midx)
6 perm(i) = i
7 sort perm based on the angle() values
# i.e., angle(perm(0)) <=
angle(perm(i)) for all i
# start making hull
8 hull(0) = perm(0)
9 hull(1) = perm(1)
10 hullpos = 2
11 for all points p, perm() order,
except perm(npoints - 1)
12 while (hullpos > 1 and
zcrossprod(hull(hullpos-2) -
13 hull(hullpos-1),
hull(hullpos-1) - p) < 0)
14 hullpos = hullpos - 1
15 hull(hullpos) = p
16 hullpos = hullpos + 1
# add last point
17 p = perm(npoints - 1)
18 while (hullpos > 1 and
zcrossprod(hull(hullpos-2) -
19 hull(hullpos-1),
hull(hullpos-1) - p) < 0)
20 hullpos = hullpos - 1
21 hullstart = 0
22 do
23 flag = false
24 if (hullpos - hullstart >= 2 and
zcrossprod(p -
25 hull(hullpos-1),
hull(hullstart) - p) < 0)
26 p = hull(hullpos-1)
27 hullpos = hullpos - 1
28 flag = true
29 if (hullpos - hullstart >= 2 and
zcrossprod(hull(hullstart) - p,
hull(hullstart+1) -
hull(hullstart)) < 0)
30 hullstart = hullstart + 1
31 flag = true
32 while flag
33 hull(hullpos) = p
34 hullpos = hullpos + 1
跟蹤
對於下面的跟蹤,我們使用這些頂點:
選擇一個中心,計算夾角,並排序。
現在,從加入最初的兩個頂點開始。
現在,加入第三個頂點。由於這步操作沒有和前兩個頂點構成一個大於 180 度的角,我們只需加入這個頂點。
加入第四個頂點。這一次沒有構成大於 180 度的角,所以不必做更多的工作。
加入第五個頂點。
由於第三個,第四個,和第五個頂點構成了一個大於 180 度的角(一個“向右的”轉彎),我們刪去第四個頂點。
第二個,第三個,和第四個頂點沒有構成一個大於 180 度的角,所以我們完成了加入第五個頂點的任務。
加入第六個,第七個,和第八8個頂點。這個過程中沒有附加的工作。
接著,加入最後一個頂點。
第八個,第九個,和第一個頂點構成“右轉”;刪除第九個頂點。
第七個,第八個,和第一個頂點已經完成了,可是第八個,第一個,和第二個頂點構成“右轉”,所以我們必須刪除第一個頂點。
現在,第八個,第二個,和第三個頂點構成“右轉”,所以我們又要刪除第二個頂點。
剩下的頂點並不違反“左轉”原則,所以我們已經完成了任務,並且建立了給出頂點的凸包。
問題提示
類似把頂點放入多邊形的題目通常是求凸包。如果題目要求一個面積最小的凸多邊形,或者周長最小的凸多邊形,那麼我們幾乎可以確定是要求凸包了。
推廣
不幸的是,這個演算法不能簡單地推廣到三維的情形。幸運的是,三維凸包演算法全都超級複雜(四維以上的更噁心),所以題目不太可能要你去求。
如果你給多邊形加上任何限制條件時,這個演算法就玩完了(例如,多邊形的頂點不多於 n 個,或者必須是矩形)。
例題 樹的難題 [IOI 1991, problem 2]
已知:有一些樹,你必須用鐵絲圍繞這些樹,使得你用的鐵絲最短。計算哪些樹會在多邊形的頂點上和鐵絲的長度,還有農夫的小屋是在多邊形內,還是在它之外,還是跨過多邊形的邊?
分析:多邊形的頂點和鐵絲的長度可以由問題直接得到。農夫的小屋是座標軸上的一個矩形,你需要一點幾何學知識來確定矩形中的所有點都在凸包中,或者都在凸包外,或者一些在凸包內,一些在凸包外。這樣你就可以得到你想要的答案。查查幾何手冊,找一下這類問題的解法。
吝嗇的護城河建設
已知:一些多邊形房屋,計算包含這些多邊形除去一個多邊形的護城河的最小長度。
分析:計算已知多邊形的凸包相當於計算它的所有頂點的凸包。這是一個組合問題,需要一個迴圈和一個凸包產生程式。對於每座房屋,刪去這座房 屋並計算剩下頂點的凸包。選擇使得凸包最小的那座房屋。注意,只要考慮那些頂點和整個凸包重合的房屋即可,考慮整個凸包內的房屋對於問題沒有任何協助