五千年(敝帚自珍)

主题:【翻译】计算机几何基础算法(一) -- 东方射日

共:💬11 🌺53
全看分页树展 · 主题 跟帖
家园 【翻译】计算机几何基础算法(三)

东方射日:【翻译】计算机几何基础算法(二)

凸包

对于一组点,能将所有点包含在内的最小的凸多边形就是所谓的凸包。这个凸包是有该组点中的若干点组成的。可以想象这些点是一块板上钉的钉子,用一个弹性很好的橡皮筋箍住所有的点,那么这条橡皮筋所形成的多边形就是凸包。有很多种不同的算法可以计算凸包,本文中,我们将讨论其中一种算法,该算法在大多数情况下的性能足够好,但是比起其他一些算法还是相当慢的。

点看全图

外链图片需谨慎,可能会被源头改

首先,我们可以遍历所有点,找到最左边的点(x最小),如果有两个以上的点的x相同,那么我们取最高的那个点(y最大)。显然该点必然是凸包上的一个点,我们就设该点P为起点。现在我们以顺时针方向遍历所有的点。这里我们要使用叉乘算子。如何确定哪一个点是下一个点呢?我们可以任选一个未用的点N作为下一个点,然后遍历所有其他未用的点,假设是X。如果(X-P) x (N-P) 是负的,我们将X设为新的下一个点N。在我们遍历所有点后,得到的点N就是凸包上的下一个点。下面的图示表明了该算法的步骤:

点看全图

外链图片需谨慎,可能会被源头改

接着同样的方法我们可以找到凸包的下一个点,直到我们返回起点。

这里我们是使用叉乘寻找顺时针的下一个点是很容易理解的。不过在有几点共线的时候,事情稍稍有点复杂。假设不考虑共线的情况,代码很简单:

convexHull(point[] X){

int N = lengthof(X);

int p = 0;

//First find the leftmost point

for(int i = 1; i<N; i++){

if(X[i] < X[p])

p = i;

}

int start = p;

do{

int n = -1;

for(int i = 0; i<N; i++){

//Don't go back to the same point you came from

if(i == p)continue;

//If there is no N yet, set it to i

if(n == -1)n = i;

int cross = (X[i] - X[p]) x (X[n] - X[p]);

if(cross < 0){

//As described above, set N=X

n = i;

}

}

p = n;

}while(start!=p);

}

如果考虑共线的情况,代码稍微复杂一些。首先我们添加一个bool型的参数,该参数指定线上的点是否记为凸包上的点。

//If onEdge is true, use as many points as possible for

//the convex hull, otherwise as few as possible.

convexHull(point[] X, boolean onEdge){

int N = lengthof(X);

int p = 0;

boolean[] used = new boolean[N];

//First find the leftmost point

for(int i = 1; i<N; i++){

if(X[i] < X[p])

p = i;

}

int start = p;

do{

int n = -1;

int dist = onEdge?INF:0;

for(int i = 0; i<N; i++){

//X[i] is the X in the discussion

//Don't go back to the same point you came from

if(i==p)continue;

//Don't go to a visited point

if(used[i])continue;

//If there is no N yet, set it to X

if(n == -1)n = i;

int cross = (X[i] - X[p]) x (X[n] - X[p]);

//d is the distance from P to X

int d = (X[i] - X[p]) (X[i] - X[p]);

if(cross < 0){

//As described above, set N=X

n = i;

dist = d;

}else if(cross == 0){

//In this case, both N and X are in the

//same direction. If onEdge is true, pick the

//closest one, otherwise pick the farthest one.

if(onEdge && d < dist){

dist = d;

n = i;

}else if(!onEdge && d > dist){

dist = d;

n = i;

}

}

}

p = n;

used[p] = true;

}while(start!=p);

}

(译注:该算法复杂度为O(N^2),有更好的算法,复杂度为O(NLogN),参见维基:

http://zh.wikipedia.org/w/index.php?title=%E5%87%B8%E5%8C%85&variant=zh-cn

http://en.wikipedia.org/wiki/Convex_hull_algorithms

多边形内点

给出一个任意多边形的顶点和一个点,测试该点是否在多边形内、外或边上。

这个问题可以简单使用线-线交点和点-线距离的算法。

首先我们可以使用点-线距离来测试该点是否在边上。如果该点到任意一条边的距离为0,那么显然是在边上。

然后,我们要检测该点是否在多边形内部。想象一下以该点为起点,向任意方向画一条射线,每一次该射线和边相交,该射线就由多边形内部穿越到外部或相反。这样,如果该点在多变形内部,该射线必然和边相交奇数次。技术上我们不需要真的画一条无限长的射线,我们只要有一足够长的线段即可。如果另一端点没有选好,你有时会陷入困境,例如线段两点均在多边形内部或和某一条边重合。简单但是龌龊的做法是用一个足够大随机数作为线段的另一个端点,这个方法虽然不优美且不安全,但是在实践中它管用。这线段和某一条边重合的概率是如此之小以至于你可以拍胸脯说可以得到正确的解答。如果你还是不放心,你可以选几个随机点,然后返回最普遍的答案。

String testPoint(verts, x, y){

int N = lengthof(verts);

int cnt = 0;

double x2 = random()*1000+1000;

double y2 = random()*1000+1000;

for(int i = 0; i<N; i++){

if(distPointToSegment(verts[i],verts[(i+1)%N],x,y) == 0)

return "BOUNDARY";

if(segmentsIntersect((verts[i],verts[(i+1)%N],{x,y},{x2,y2}))

cnt++;

}

if(cnt%2 == 0)return "EXTERIOR";

else return "INTERIOR";

}

关键词(Tags): #几何#编程

本帖一共被 1 帖 引用 (帖内工具实现)
全看分页树展 · 主题 跟帖


有趣有益,互惠互利;开阔视野,博采众长。
虚拟的网络,真实的人。天南地北客,相逢皆朋友

Copyright © cchere 西西河