一.問題描述java
凸集(Convex Set): 任意兩點的連線都在這個集合內的集合就是一個凸集.
⒈對於一個集合D,D中任意有限個點的線性組合的全體稱爲D的凸包。
⒉對於一個集合D,全部包含D的凸集之交稱爲D的凸包(由此定義能夠想到分治算法)。
能夠證實,上述兩種定義是等價的。點集Q的凸包(convex hull)是指一個最小凸多邊形,知足Q中的點或者在多邊形邊上或者在其內。下圖中由紅色線段表示的多邊形就是點集Q={p0,p1,...p12}的凸包。一組平面上的點,求一個包含全部點的最小的凸多邊形,這就是凸包問題了,這能夠形象地想象成在地上放置一些不可移動的木樁,用一根繩子把他們儘可能緊地圈起來,而且爲凸邊形,這就是凸包了。算法二.GiftWrapping算法app
又叫捲包裹算法,複雜度O(n*h),n表示共幾個點,h表示極點個數。this
- 理論準備
向量叉積:也被稱爲矢量積、叉積(即交叉乘積)、外積,是一種在向量空間中向量的二元運算。與點積不一樣,它的運算結果是一個僞向量而不是一個標量,而且兩個向量的叉積與這兩個向量垂直,方向由右手法則肯定。兩個向量a和b的叉積寫做a×b(有時也被寫成a∧b,避免和字母x混淆)。
a×b=(aybz-azby)i+(azbx-axbz)j+(axby-aybx)k,爲了幫助記憶,利用三階行列式,寫成
| i j k|
|ax ay az|
|bx by bz|。
b×a= -a×b,三角形ABC的面積=1/2*abs(AB×AC)(這個公式能夠解決一些精度問題),不知足結合律,但知足雅可比恆等式:a× (b×c) +b× (c×a) +c× (a×b) =0和拉格朗日公式a× (b×c) =b(a·c) -c(a·b),能夠簡單地記成「BAC - CAB」。這個公式在物理上簡化向量運算很是有效,須要注意的是,這個公式對微分算子不成立。spa
- 算法描述
捲包裹算法從一個必然在凸包上的點開始向着一個方向依次選擇最外側的點,當回到最初的點時,所選出的點集就是所要求的凸包。這裏還有兩個問題不是很清楚:
1.怎麼肯定一個確定在凸包上的點?
這個問題很好解決,取一個最左邊的也就是橫座標最小的點(或最下邊的點,縱座標最小的),若是有多個這樣的點, 就取這些點裏縱座標(橫座標)最小的,這樣能夠很好的處理共線的狀況。
2.如何肯定下一個點(即最外側的點)?3d向量的叉積是不知足交換律的,向量A乘以向量B, 若是爲正則爲A逆時針旋轉向B,不然爲順時針,固然這裏A轉向B的角老是考慮一個小於180度之內的角。code
三.算法的Java實現blog
看完這段代碼,直接把POJ1113AC了吧。排序
import java.util.*; class Point implements Comparable { double x; double y; public int compareTo(Object o) {// 按x升序排列,x相同按y升序 Point b = (Point) o; if (this.x > b.x) return 1; else if (this.x == b.x) { if (this.y > b.y) return 1; else if (this.y == b.y) return 0; else return -1; } else return -1; } } public class GiftWrap { Point[] point;// 已知的平面上的點集 boolean[] vis;// 標pointA[i]是否已在凸包中 Queue<Integer> queue = new LinkedList<Integer>();// 點集的凸包, int n; int l; public GiftWrap() { } // 向量ca與ba的叉積 double cross(Point c, Point a, Point b) { return (c.x - a.x) * (a.y - b.y) - (c.y - a.y) * (a.x - b.x); } // 求距離,主要是爲了求極點 public double distance(Point p1, Point p2) { return (Math.hypot((p1.x - p2.x), (p1.y - p2.y))); } public void go() { Scanner sc = new Scanner(System.in); n = sc.nextInt(); point = new Point[n + 1]; vis = new boolean[n + 1]; for (int i = 1; i <= n; i++) {// 輸入從1開始 point[i] = new Point(); point[i].x = sc.nextDouble(); point[i].y = sc.nextDouble(); } Arrays.sort(point, 1, point.length - 1);// 注意這個排序從1開始 // 肯定一個確定在凸包上的點 vis[1] = true;// 注意這裏將point[1]標記爲放進凸包,不過並無真的放入隊列 int in = 1;//在凸包上的點 while (true) { int not = -1; for (int i = 1; i <= n; i++) { if (!vis[i]) {// 找一個不在凸包上的點 not = i; break; } } if (not == -1) break;// 找不到,結束 for (int i = 1; i <= n; i++) { /* * 遍歷全部點, 每一個點都和現有最外側的點比較,獲得新的最外側的點 * 第二個條件是找到極點,不包括共線點 */ if ((cross(point[in], point[i], point[not]) > 0) || (cross(point[in], point[i], point[not]) == 0) && (distance(point[in], point[i]) > distance( point[in], point[not]))) not = i; } if (vis[not]) break;// 找不到最外側的點了 queue.offer(not);// 最外側的點進凸包 vis[not] = true;// 標記這點已放進凸包了 in = not;//in始終表示一個一定在凸包裏的點 } double ans = 0; in = 1; /* 最後將point[1]的下標放進凸包 不會重複放入,由於最開始只是把標記下標1在凸包 */ queue.offer(1); /* * 這裏有個問題須要弄明白 * 算凸多邊形周長,須要連續的兩個點,下面的是連續的不? * 是的。 * 編號1插入到隊尾,temp先取出的是隊頭,便是最後一個進入凸多邊形的點,顯然和編號1是鏈接的 * 那會不會重複呢? * 固然不會,由於while循環最後一次是編號1和最早插入的點的距離,此時編號1已經沒了,隊列空了 */ while (!queue.isEmpty()) { int temp = queue.poll();//獲取並移除隊列的頭 ans += Math.hypot((point[in].x - point[temp].x), (point[in].y - point[temp].y)); in = temp; } System.out.println(ans); } public static void main(String args[]) { GiftWrap m = new GiftWrap(); m.go(); } }遺留問題:作完POJ1113時,想起了怎麼求所得凸多邊形的外接圓,也就是最小覆蓋圓問題,那麼又怎麼求內切圓呢?