译者序
本篇blog实际上是Bob大叔对xreborner的一连串的发贴给于的回复(xreborner在上篇blog中对Bob大叔提出了一系列犀利的维护C++权益的观点)。
正文
我在最近的一篇blog中对比了C++、Java和Ruby的时间消耗,其中一个参与者(xreborner)提交了一个convex hull的凸包算法代码。我花了好久来研究其中的蹊跷,直到把算法绘制于图上,才发现自己是蒙在鼓里了。
xreborner用的算法像是一种Graham Scan算法,它的复杂度呈O(nLogn)级别。实际上,这个算法的时间主要是消耗在其中的快速排序上了。
还有一个算法叫做Jarvis March,也称作giftwrapping算法。它的复杂度呈O(kn)级别,k代表了凸包顶点的数目。
我非常好奇这两个算法哪个会更快些,显然,只有在logn < k的时候,O(nLogn)才会比O(kn)的快。不过也有另一种考量,因为Jarvis March比Graham Scan简单的多,所以我认为在绝大多数情况下,Jarvis March算法都会是个更优的选择,因为只有在凸包顶点数目非常大的情形下Graham算法的效率才会反超。于是我自己编写了Jarvis March算法,并且和xreborner的Graham Scan做了比较,结果如下:
是不是很有趣?我构造了1000个随机点并绘制了时间和凸包顶点数量的曲线。交叉点看起来正好位于这些随机的凸包顶点数量的中间位置,而且大概是在37点位!我猜想这是个巧合,不过也够神奇的啊。
噢,作为一个锻炼,看看你们是不是能读懂Jarvis March,也再看看能不能弄明白Graham Scan。
import java.util.*;
public class JarvisMarch { Points pts; private Points hullPoints = null; private List<Double> hy; private List<Double> hx; private int startingPoint; private double currentAngle; private static final double MAX_ANGLE = 4;
public JarvisMarch(Points pts) { this.pts = pts; }
/** * The Jarvis March, sometimes known as the Gift Wrap Algorithm. * The next point is the point with the next largest angle. * <p/> * Imagine wrapping a string around a set of nails in a board. Tie the string to the leftmost nail * and hold the string vertical. Now move the string clockwise until you hit the next, then the next, then * the next. When the string is vertical again, you will have found the hull. */ public int calculateHull() { initializeHull();
startingPoint = getStartingPoint(); currentAngle = 0;
addToHull(startingPoint); for (int p = getNextPoint(startingPoint); p != startingPoint; p = getNextPoint(p)) addToHull(p);
buildHullPoints(); return hullPoints.x.length; }
public int getStartingPoint() { return pts.startingPoint(); }
private int getNextPoint(int p) { double minAngle = MAX_ANGLE; int minP = startingPoint; for (int i = 0; i < pts.x.length; i++) { if (i != p) { double thisAngle = relativeAngle(i, p); if (thisAngle >= currentAngle && thisAngle <= minAngle) { minP = i; minAngle = thisAngle; } } } currentAngle = minAngle; return minP; }
private double relativeAngle(int i, int p) {return pseudoAngle(pts.x[i] - pts.x[p], pts.y[i] - pts.y[p]);}
private void initializeHull() { hx = new LinkedList<Double>(); hy = new LinkedList<Double>(); }
private void buildHullPoints() { double[] ax = new double[hx.size()]; double[] ay = new double[hy.size()]; int n = 0; for (Iterator<Double> ix = hx.iterator(); ix.hasNext();) ax[n++] = ix.next();
n = 0; for (Iterator<Double> iy = hy.iterator(); iy.hasNext();) ay[n++] = iy.next();
hullPoints = new Points(ax, ay); }
private void addToHull(int p) { hx.add(pts.x[p]); hy.add(pts.y[p]); }
/** * The PseudoAngle is a number that increases as the angle from vertical increases. * The current implementation has the maximum pseudo angle < 4. The pseudo angle for each quadrant is 1. * The algorithm is very simple. It just finds where the angle intesects a square and measures the * perimeter of the square at that point. The math is in my Sept '06 notebook. UncleBob. */ public static double pseudoAngle(double dx, double dy) { if (dx >= 0 && dy >= 0) return quadrantOnePseudoAngle(dx, dy); if (dx >= 0 && dy < 0) return 1 + quadrantOnePseudoAngle(Math.abs(dy), dx); if (dx < 0 && dy < 0) return 2 + quadrantOnePseudoAngle(Math.abs(dx), Math.abs(dy)); if (dx < 0 && dy >= 0) return 3 + quadrantOnePseudoAngle(dy, Math.abs(dx)); throw new Error("Impossible"); }
public static double quadrantOnePseudoAngle(double dx, double dy) { return dx / (dy + dx); }
public Points getHullPoints() { return hullPoints; }
public static class Points { public double x[]; public double y[];
public Points(double[] x, double[] y) { this.x = x; this.y = y; }
// The starting point is the point with the lowest X // With ties going to the lowest Y. This guarantees // that the next point over is clockwise. int startingPoint() { double minY = y[0]; double minX = x[0]; int iMin = 0; for (int i = 1; i < x.length; i++) { if (x[i] < minX) { minX = x[i]; iMin = i; } else if (minX == x[i] && y[i] < minY) { minY = y[i]; iMin = i; } } return iMin; }
} }
(原文链接网址:http://www.butunclebob.com/ArticleS.UncleBob.ConvexHullTiming; Robert C. Martin的英文blog网址:http://www.butunclebob.com/ArticleS.UncleBob)
作者简介:Robert C. Martin是Object Mentor公司总裁,面向对象设计、模式、UML、敏捷方法学和极限编程领域内的资深顾问。他不仅是Jolt获奖图书《敏捷软件开发:原则、模式与实践》(中文版)(《敏捷软件开发》(英文影印版))的作者,还是畅销书Designing Object-Oriented C++ Applications Using the Booch Method的作者。Martin是Pattern Languages of Program Design 3和More C++ Gems的主编,并与James Newkirk合著了XP in Practice。他是国际程序员大会上著名的发言人,并在C++ Report杂志担任过4年的编辑。
|
请发表评论