您的位置:首页 > 其它

凸包的几种算法 主要Graham-Scan算法的水平序法 另加poj113 wall的解题

2012-09-07 13:33 176 查看
在说这个题目之前,我想给大家介绍一些这几天我了解到的有关凸包的知识:

1、Gift-Wrapping(卷包裹算法)

这个算法在《算法艺术》上说的很清楚了(p391-393),如果理解的还不是很清楚,在这里讲解的特别好,特别清楚,由于这个很简单,所以就不谈论它了。我对这个算法的理解是:时间复杂度是O(N*H),N是点的个数,H是在凸包上点的个数。所以卷包裹算法很适合凸包上的点很少的时候,通常情况下,随机数据会很快。但是如果构造出的凸包上的点很多的时候,它就会很慢了,不如说,所有点都在一个园上的时候。

2、Graham-Scan算法

《算法艺术》(p393-396)。我觉得上面讲的很详细很详细了。同样,如果理解的不好可以看看这里

这个算法是基于一个有序的点,所以必须要排序的。关键就在这里,排序分两种:极角排序和水品排序。估计大多数人都会第一种。

先说第一种。首先要找到一个一定在凸包上的点p0,这个点怎么找?自己想想吧。然后就以这个点为基点,对所有点按照极角大小排序。这里注意一下,如果极角相等,那么就按距离从小到大排序。这个是防止共线问题,关于这个下面好好讨论。然后把排序后的p0,p1,p2放入栈中,接着就是遍历每个点了,始终保证非“右手”方向就好了,具体实现去网上搜,很多。

然后就是第二种的水平序。排序准则是先按y大小排序,如果y相等,就按x排序。相对于第一种,这个排序简单的多。然后和上面的思想一样,只不过要分两步,右链和左链,先做右链,从0到排序最后点,然后再反过来,进行左链。具体的细节可以参考下面的代码。可能你在想这样有什么好处呢,我要说的是,这样可以很完美的解决共线问题。

我对这个算法的理解是:对于随机数据,可能没有卷包裹算法快。但是也是可以接受的。之所以喜欢的原因是,它可以完美的解决共线这个一直让人头大的问题。

3、Melkman算法

首先要说的是:很多人都认为这个是最好的算法。这个算法可以在个点有序的前提下,每次获得一个点就可以将先前的凸包改造成新的凸包,因此,这个是一个在线算法,它有着其他算法无法比拟的优势。1987年Melkman提出的的凸包算法,它不再使用堆栈了,转而使用双向表,这为凸包算法的历史掀开了崭新的一夜。

具体实现我就不说了,相信看过上面几句话的,现在都已经忍不住要学习了。

今天看了一下午《算法艺术》和网上的一些资料,终于又有了一些理解,那就补充一下吧。之前都是模模糊糊的,现在明白了,可能我现在理解的还是错误的,但是我还是要说出来:Melkman算法的前提是“各个点有序”。所以melkman是用来求简单多边形的凸包算法,可以在线性时间求出最小凸包。而其他两种则可以求点集的凸包的算法。如果要用Melkman算法来求点集的凸包,那么首先也是要排序的,通过排序可以形成一个简单的多边形,然后才可以在线性的时间求出最小凸包。

所以最终得到的结论是:求点集的凸包,时间复杂度的底线是O(nlogn)。

最后讨论一下共线的问题:

假设有这么几个点

0 0

0 1

0 2

2 2

1 1

和这几个点

1 1

2 2

3 3

4 4

对于上面的两组数据,如果用卷包裹算法和Graham-Scan的极角排序法做,要求只输出凸包上的定点,会出现什么样的问题??

如果用Graham-Scan的水平序来写,会不会出现同样的问题??

大家可以好好想想!!!!

还有很多凸包方面的算法,比如Jarvis步进法、增量、溶解、QuickHull等,这些多用于数学中,实践意义不大,所以就不说了。有兴趣了解凸包算法的发展史,可以看看蓝点大神的《漫话二维凸包》。。

现在说poj1113 wall:

题目:poj1113 wall

意思就是给一个城堡的墙角的坐标,让你用最短的围墙围起来,并且围墙离城堡的距离不能少于L。

说白了就是一个很裸的凸包问题。对于那个不能小于L,等作出了凸包,然后把边向外移动L,自己画画就看出来了,每个角处的弧,加起来刚好是一个半径为L的圆,所以最终结果就是圆的周长加上凸包的周长。

值得注意的是:由于精度问题,对于那些共线的点,应该取最远处那个计算长度,不然可能会一直WA,这个就体现了Graham-Scan算法的水平序的优势了。大家好好品味。。

比如上面提到的第一组数据,计算的顶点应该是0 0,0 2和2 2,而不是所有的点,即使所有的点都在凸包上,如果计算所有的点,误差就会大很多,肯能就是一直WA的。

代码如下:

//突然想用类写个模板,由于c++没学好,所以弄了一上午才弄万,不好的地方欢迎大家指点。。。。。
#include <stdio.h>
#include <iostream>
#include <algorithm>
#include <string.h>
#include <math.h>

using namespace std;
const int N=1100;

struct Node{
int x,y;
bool operator<(Node a)const{
return y<a.y||(y==a.y&&x<a.x);
}
};

class Graham_Scan{
public:

Graham_Scan(int r){
num_node=r;
for(int i=0;i<=num_node;i++)
visit[i]=true;
}
bool judg(){//判断给的点是否符合条件能够成凸包
if(num_node<2)
return 0;
}
void init();
void fun();
void print_node(bool jud);//输出凸包上的点,如果jud真就输出所有点,否则就输出定点
double print_per();//输出凸包的周长
private:
int num_node,stack_all
,stack
,top_all,top;
Node node
;
double dis(Node a,Node b);
void Graham_scan();//Graham_Scan算法水平序的实现
bool visit
;
int turn(int a,int b,int c);
};

void Graham_Scan::init()
{
for(int i=0;i<num_node;i++)
scanf("%d%d",&node[i].x,&node[i].y);
}

double Graham_Scan::dis(Node a,Node b)
{
double c;
c=(b.x-a.x)*(b.x-a.x)+(b.y-a.y)*(b.y-a.y);
return sqrt(c);
}

int Graham_Scan::turn(int a,int b,int c)
{
return (node[b].x-node[a].x)*(node[c].y-node[a].y)-(node[b].y-node[a].y)*(node[c].x-node[a].x);
}

void Graham_Scan::fun()
{
Graham_scan();
}

void Graham_Scan::Graham_scan()
{
int i;
sort(node,node+num_node);
stack_all[0]=stack[0]=0;
stack_all[1]=stack[1]=1;
top=top_all=1;
visit[1]=false;
//执行右链,同时标记已经在右链上的点
for(i=2;i<num_node;i++)
{
while(top>0&&turn(stack[top-1],stack[top],i)<=0)
top--;
stack[++top]=i;

while(top_all>0&&turn(stack_all[top_all-1],stack_all[top_all],i)<0)
{
visit[stack_all[top_all]]=true;
top_all--;
}
stack_all[++top_all]=i;
visit[i]=false;
}
//现在的top,top_all点一定是最上边,最右边的那个点
//执行左链,逃过在右链上已经在凸包上的点,看别人写的时候没有跳过,不过算法书上说是要跳过的,所以还是跳过吧
int top1=top,top1_all=top_all;
stack[++top]=num_node-2;
stack_all[++top_all]=num_node-2;
for(i=num_node-3;i>=0;i--)
{
while(visit[i]&&top>top1&&turn(stack[top-1],stack[top],i)<=0)
top--;
stack[++top]=i;

while(visit[i]&&top_all>top1_all&&turn(stack_all[top_all-1],stack_all[top_all],i)<0)
top_all--;
stack_all[++top_all]=i;
}
//现在的top,top_all点一定是0点
}

void Graham_Scan::print_node(bool jud)
{
if(jud)
{
for(int i=0;i<top_all;i++)
cout<<node[stack_all[i]].x<<" "<<node[stack_all[i]].y<<endl;
}
else
for(int i=0;i<top;i++)
cout<<node[stack[i]].x<<" "<<node[stack[i]].y<<endl;
}

double Graham_Scan::print_per()
{
double ans=0.0;
for(int i=1;i<=top;i++)
{
ans+=dis(node[stack[i]],node[stack[i-1]]);
}
return ans;
}

int main()
{
int n,l;
while(~scanf("%d%d",&n,&l))
{
double ans=acos(-1.0)*2*l;
Graham_Scan solve(n);
if(solve.judg())
{
cout<<"No\n"<<endl;
continue;
}
solve.init();
solve.fun();
ans+=solve.print_per();
printf("%0.lf\n",ans);
}
}


参考资料:

《算法艺术与信息学竞赛》 刘汝佳 黄亮

《算法导论》 Thomas H.Cormen、Charles E.Leiserson、Ronald L.Rivest、Clifford Stein 潘金贵 顾铁成等人译
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: