转载:凸壳算法集及描述(繁体中文)
来源:互联网 发布:可以变魔术的软件 编辑:程序博客网 时间:2024/05/11 14:47
来源:http://acm.nudt.edu.cn/~twcourse/ConvexHull.html#a11
中文譯做「凸包」,能包住物品的最小的凸外殼,也就是能將全部東西包進去的最小凸多邊形。凸的定義是圖形內任兩點的連線不會經過圖形外部:http://mathworld.wolfram.com/Convex.html。這裡我們只討論:從二維平面上散佈的點當中找出凸包。
在所有點的外圍繞一圈可得一凸多邊形,即是凸包。
凸包所包住的區域,為各點之間做線性內插後的範圍。
UVa 109 132 218 361 681 811 819 10002 10065 10078 10135 10173 10256 11626
Convex Hull: Jarvis' March
Jarvis' March ( Gift Wrapping Algorithm)
從一個凸包上的頂點開始,順著外圍繞一圈,順時針或逆時針都可以。
當要尋找下一個被包覆的點時,則窮舉平面上所有點,找出位於最外圍的一點來包覆即可(可利用外積運算來做判斷)。時間複雜度為 O(N*M), M為凸包的頂點數目。
- // P為平面上的那些點。這裡設定為剛好100點。
- // CH為凸包上的點。這裡設定為照逆時針順序排列。
- struct Point {int x, y;} P[100], CH[100];
- // 小於。用以找出最低最左邊的點。
- bool compare(Point& a, Point& b)
- {
- return (a.y < b.y) || (a.y == b.y && a.x < b.x);
- }
- // 向量OA cross 向量OB。大於零表示從OA到OB為順時針旋轉。
- double cross(Point& o, Point& a, Point& b)
- {
- return (a.x - o.x) * (b.y - o.y) - (a.y - o.y) * (b.x - o.x);
- }
- void findConvexHull()
- {
- /* 用最低最左邊的點當作是起點。起點可以用凸包上面任意一個點。 */
- int s = 0;
- for (int i=0; i<100; ++i)
- if (compare(P[i], P[s]))
- s = i;
- /* 包禮物,逆時針方向。 */
- CH[0] = P[s]; // 紀錄起點
- for (int m=1; true; ++m) // m 為凸包頂點數目
- {
- /* 開始窮舉所有點,找出位於最外圍的一點 */
- int next = s;
- if (m == 1) next = !s; // 找第一點時,next預設為起點以外的點,
- // 否則cross會一直等於零。
- for (int i=0; i<100; ++i)
- if (cross(CH[m], P[i], P[next]) < 0)
- next = i;
- if (next == s) break; // 繞一圈後回到起點了
- CH[m] = P[next]; // 紀錄方才所找到的點
- }
- }
Convex Hull: Graham's Scan
Graham's Scan
由前面段落可知:凸包上的頂點們有順序的沿著外圍繞行一圈。若能照此順序來包,就不必以窮舉所有點的方式來尋找最外圍的點。 Graham's Scan即是嘗試將所有點按照順序排好,再來做繞一圈的動作。
順序該如何決定呢?只要能確保凸包各頂點的前後順序是正確的,那麼便不會包錯。一個簡單的想法是依角度排序──只要將中心點設定在凸包內部或設定在凸包上面,便可以確保凸包各頂點的前後順序必定正確(讀者可自行証明此說)。
除了凸包各頂點的前後順序要正確,另外還要限制所有點依照前後順序連線起來後,不會繞成超過一個的圈圈,也不會有任何邊重疊。更精準的說法是:會形成簡單多邊形( simple polygon),不會有邊相交。如此一來,便不必理會那些不在凸包上面的點的前後順序,因為那些點會在找最外圍的點的時候被淘汰掉(讀者可自行証明此說)。
一般來說,選擇凸包上面的端點當作排序角度時的中心點是比較好的,因為最大的夾角必會小於 180 度,而可以使用外積運算來排序。(外積在大於 180度時會得負值、等於 180度時會等於零,導致排序錯誤。)
如果凸包各頂點的前後順序是錯誤的,或者所有點依照前後順序連線後產生了很多圈圈,就會發生慘劇。有時甚至會找出凹的形狀。
其他細節在演算法書籍上面皆可找到,故不細講。時間複雜度為 O(NlogN) ,主要取決於排序的時間;若用 Counting Sort之類的排序方法便可達到 O(N);若已知這些點構成的簡單多邊形之後,便不需排序,就只需 O(N)。
- // P為平面上的那些點。這裡設定為剛好100點。
- // CH為凸包上的點。這裡設定為照逆時針順序排列。
- struct Point {int x, y;} P[100+1], CH[100+1];
- // 向量OA cross 向量OB。大於零表示從OA到OB為順時針旋轉。
- double cross(Point& o, Point& a, Point& b)
- {
- return (a.x - o.x) * (b.y - o.y) - (a.y - o.y) * (b.x - o.x);
- }
- // 小於。用以找出最低最左邊的點。
- bool compare_position(Point& a, Point& b)
- {
- return (a.y < b.y) || (a.y == b.y && a.x < b.x);
- }
- // 小於。以P[0]當中心點做角度排序,以逆時針方向排序。
- // 若角度一樣,則順序隨便。
- bool compare_angle(Point& a, Point& b)
- {
- return cross(P[0], a, b) > 0;
- }
- void findConvexHull()
- {
- /* 用最低最左邊的點當作是起點。起點必須是凸包的端點。 */
- // 將端點換到第一點。O(N)
- swap(P[0], *min_element(P, P+100, compare_position));
- // 其餘各點照角度排序,並以第一點當中心點。O(NlogN)
- sort(P+1, P+100, compare_angle);
- /* 包,逆時針方向。O(N) */
- P[N] = P[0];
- int m = 0; // m 為凸包頂點數目
- for (int i=0; i<=100; ++i) {
- while (m >= 2 && cross(CH[m-2], CH[m-1], P[i]) < 0) m--;
- CH[m++] = P[i];
- }
- m--; // 最後一個點是重複出現兩次的起點,故要減一。
- }
若要連凸包上面共線的點都找出來,便要小心處理剛開始包、快要包好時產生共線的情形,這些點的先後順序決不能亂。
有一個解決方法是分做左右兩邊包,當排序時遇到角度相同的情況時,令距離離中心點較短的順序較高。總之相當麻煩,就不細講了。下面這段程式碼寫出一些特別要注意的地方:
- // 小於。以P[0]當中心點做角度排序,以逆時針方向排序。
- // 若角度一樣,則距離較離中心點較短的順序較高。
- bool compare_angle(Point& a, Point& b)
- {
- // 加入角度相同時,距離長度的判斷。
- int c = cross(P[0], a, b);
- return (c > 0) || (c == 0 && len2(P[0], a) < len2(P[0], b));
- }
- void findConvexHull()
- {
- ......
- // 這邊的判斷記得要改成小於等於零,以包含共線情形。
- while (m >= 2 && cross(CH[m-2], CH[m-1], P[i]) <= 0) m--;
- ......
- }
Convex Hull: Andrew's Monotone Chain
Andrew's Monotone Chain
排順序時改為依座標大小排序。這個方法非常優美,而且能處理共線的情形: http://www.algorithmist.com/index.php/Monotone_Chain_Convex_Hull 。我也找到了有趣的 Applet:http://wind.lcs.mit.edu/~aklmiu/6.838/convexhull/index.html 。
時間複雜度為下述兩項總和:一、一次排序,通常為 O(NlogN) ;二、掃描 2N個點,為 O(N)。
- // P為平面上的那些點。這裡設定為剛好100點。
- // CH為凸包上的點。這裡設定為照逆時針順序排列。
- struct Point {int x, y;} P[100], CH[100];
- // 向量OA cross 向量OB。大於零表示從OA到OB為順時針旋轉。
- double cross(Point& o, Point& a, Point& b)
- {
- return (a.x - o.x) * (b.y - o.y) - (a.y - o.y) * (b.x - o.x);
- }
- // 小於。依座標大小排序,先排 x 再排 y。
- bool compare(Point& a, Point& b)
- {
- return (a.x < b.x) || (a.x == b.x && a.y < b.y);
- }
- void findConvexHull()
- {
- // 將所有點依照座標大小排序
- sort(P, P+100, compare);
- int m = 0; // m 為凸包頂點數目
- // 包下半部
- for (int i=0; i<100; ++i) {
- while (m >= 2 && cross(CH[m-2], CH[m-1], P[i]) <= 0) m--;
- CH[m++] = P[i];
- }
- // 包上半部,不用再包入方才包過的終點,但會再包一次起點
- for (int i=100-2, t=m+1; i>=0; --i) {
- while (m >= t && cross(CH[m-2], CH[m-1], P[i]) <= 0) m--;
- CH[m++] = P[i];
- }
- }
Convex Hull: Quick Hull Algorithm
演算法
這是一個運用 Divide and Conquer 的演算法。
一開始將所有點以X座標位置排序。Divide:將所有點分成左半部和右半部。Conquer:左半部和右半部分別求凸包。Merge:將兩個凸包合併成一個凸包。
在兩凸包頂端最凸處加一條邊,然後在兩凸包底部最凸處加一條邊,就變成一個凸包。令左半部凸包最左端的點為p點,令右半部凸包最右端的點為q點。要找上方的邊,讓p點為基準,然後移動q點在凸包上往逆時針方向走,讓直線pq持續往逆時針方向轉,轉到底為止。接著讓q點為基準,然後移動q點在凸包上往逆時針方向走,讓直線pq持續往逆時針方向轉,轉到底為止。此時的邊pq就是上方的邊。要找下方的邊,也可以如法炮製。另外一種比較麻煩一點的找法是,令左半部凸包最高的點為p點,令右半部凸包最低的點為q點。讓p點為基準,然後移動q點在凸包上往逆時針、也往順時針方向走,總之就是讓直線pq持續往逆時針方向轉。接著讓q點為基準做類似的事情。要找下方的邊,也可以如法炮製。
時間複雜度為下述兩項總和:一、一次排序的時間,通常為 O(NlogN) ;二、 Divide and Conquer向下遞迴 O(logN)深度,合併凸包要 O(N) 時間,總共需時 O(NlogN) 。
Convex Hull: Melkman's Algorithm
演算法
求出一簡單多邊形的凸包。
http://cgm.cs.mcgill.ca/~athens/cs601/Melkman.html
時間複雜度為 O(N) 。是相當優美的演算法。
- 转载:凸壳算法集及描述(繁体中文)
- RSA算法描述和代码(转载)
- 系统进程描述(转载)
- [转载]MD5算法之C#程序 MD5算法描述
- [转载]MD5算法之C#程序 MD5算法描述
- A算法描述及JAVA实现
- 圆投影匹配算法描述及实现
- DES算法描述及C++实现
- [转载]Lasso思想及算法
- 8259A工作原理描述(转载)
- USB描述符详解(转载)
- JS引擎工作机制描述(转载)
- dijkstra算法(Pascal描述)
- (转载)MySQL索引背后的数据结构及算法原理
- K-means聚类算法 及Demo(转载)
- (转载)动态规划:从新手到专家(关于动态规划算法最精彩的中文描述,没有之一)
- 动态规划:从新手到专家(关于动态规划算法最精彩的中文描述,没有之一) (转载)
- Java繁体中文处理完全攻略(一)
- Fio for I/O performance testing
- 软件需求管理
- 和会计师对公开圣诞快乐过水电费公开附件
- 将明年和快递费,能很开放后付款农行卡饭卡户口和公开了讲
- 圣诞节快高考水电工喝口水都分开后进生的分开了
- 转载:凸壳算法集及描述(繁体中文)
- java常用的编程规范
- 紐巴倫慢跑鞋 nfkd nvsr ahgc
- JAVA菜鸟入门(5) Hashmap vs Hashtable
- leetcode Roman to Integer
- UI—UIScrollView缩放控件、代理、分页、键盘
- UVa-253 - Cube painting
- Python Numpy
- leetcode Roman to integer