把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

Andrew算法求二维凸包-学习笔记

 

凸包的概念

首先,引入凸包的概念:

 

 (有点窄的时候...图片右边可能会被吞,拉开图片看就可以了)

 大概长这个样子:

 

 

 那么,给定一些散点,如何快速地求出凸包呢(用在凸包上的点来表示凸包)

Andrew算法流程和思想

常见的求凸包的算法有$Graham$和$Andrew$,$Andrew$是$Graham$扫描算法的变种,和$Graham$相比,$Andrew$更快,且更稳定,所以主要讲一下$Andrew$。

 

首先把所有点以$x$坐标为第一关键字,$y$坐标为第二关键字从小到大进行排序,可以肯定第一个点和最后一个点在答案中。

接下来用以下的例子来帮助理解算法流程:

 第一次,把$1$和$2$加入答案中

 

 

尝试把$3$加进去,发现凹进去了,所以把$2$丢掉,把$3$放进去

 

 

 

 

 我们来看看$2$被丢掉,$3$成功上位的原因(凹进去的原因):
发现是斜率(或者...可以总结成叉积?

在下图中$1->2$的斜率大于$1->3$的斜率,又因为之前按$x$递增排序,所以可以说明$2$在$3$的左上,所以是凹进去的。

换句话说,如果加进去这个点(即当前点,记为$i$)和$i-2$号点的斜率小于$i$号点和$i-1$号点的斜率,那么就要把$i-1$号点去掉并加入$i$号点来维护凸包的性质(不让它凹进去)

 

 

 

 

 

接下来加入$4$,$1->4$斜率大于$1->3$斜率,所以$3$不用被丢掉。

 

 

 加入$5$,$3->5$斜率小于$3->4$斜率,所以丢掉$4$,加入$5$

 

 

 

 

 

 

 

加入$6$,一样的理由,一样的操作。($3->6$斜率小于$3->5$斜率,丢掉$5$,加入$6$)

 

 

 

 

 

 然后发现$3$那个地方也凹进去了($1->6$的斜率小于$1->3$的斜率)

所以$3$也要被丢掉,然后只剩下两个点:

(所以写代码的时候要用$while$)

 

 

 接着来,加入$7$:

 

 

 然后是$8$,发现...斜率只小一点点(图没画好,这***钻的角度,将就看一下吧...),所以$7$要删掉

不过也顺便解决一个共线的问题,共线嘛,很好解决,反正两个点都在凸包上,都不丢就可以了,后面如果那一条线不属于凸包的话,用$while$丢点的时候两个点斜率是一样的,总会被丢出去的。

 

 

 

 

 

 然后是$9$,发现斜率小,所以丢掉$8$:

 

 

 啊哈,然后发现所有点都已经遍历完了,成功达到了$9$,可是凸包还有一半呢。

倒着再来一次就可以求出上面那个盖盖了:

(下面放流程图,不一一解说了(好累),操作是一样的

 

 (把$7$悄悄地挪了一下位置) 

 

 

 

 (丢掉$6$,发现斜率的关系和正着的那一次都一样,都是小于) 

 

 

 

 

 

 

 

 

 

 

 

 

 这样, 凸包就求出来啦!

按照以上的思路写代码就可以啦。

例题& 板子

 例题: 求凸包的周长:

 1 /*
 2 ID: Starry21
 3 LANG: C++
 4 TASK: fc          
 5 */ 
 6 #include<cstdio>
 7 #include<algorithm>
 8 #include<vector>
 9 #include<cstring>
10 #include<queue>
11 #include<map>
12 #include<iostream>
13 #include<cmath>
14 using namespace std;
15 #define ll long long
16 #define INF 0x3f3f3f3f
17 #define N 10005
18 struct node{
19     double x,y;
20 };
21 node p[N],s[N]/*凸包上的点*/;
22 int n;
23 double dis(node a,node b)
24 {
25     return sqrt(((a.x-b.x)*(a.x-b.x))+((a.y-b.y)*(a.y-b.y)));
26 }
27 bool cmp(node a,node b)
28 {
29     if(a.x==b.x) return a.y<b.y;
30     return a.x<b.x;
31 }
32 double getk(node a,node b)
33 {
34     if(a.x==b.x) return INF;//在一条竖线上 斜率看成无限大 
35     return (b.y-a.y)/(b.x-a.x);
36 }
37 double Andrew()
38 {
39     sort(p+1,p+n+1,cmp);
40     int cnt=0,tot=0;
41     double sum=0.0;
42     for(int i=1;i<=n;i++)
43     {
44         s[++cnt]=p[i];
45         while(cnt>=3&&getk(s[cnt-2],s[cnt])<getk(s[cnt-2],s[cnt-1]))
46             s[cnt-1]=s[cnt],cnt--;
47     }
48     for(int i=1;i<=cnt-1;i++)
49         sum+=dis(s[i],s[i+1]);
50     tot=cnt;
51     cnt=0;
52     for(int i=n;i>=1;i--)
53     {
54         s[++cnt]=p[i];
55         while(cnt>=3&&getk(s[cnt-2],s[cnt])<getk(s[cnt-2],s[cnt-1]))
56             s[cnt-1]=s[cnt],cnt--;
57     }
58     for(int i=1;i<=cnt-1;i++)
59         sum+=dis(s[i],s[i+1]);
60     tot+=cnt;
61     tot-=2;//tot是凸包上点的个数 
62     //printf("%d\n",tot); 
63     return sum;
64 }
65 int main() 
66 {
67     //freopen("fc.in","r",stdin);
68     //freopen("fc.out","w",stdout);
69     scanf("%d",&n);
70     for(int i=1;i<=n;i++)
71         scanf("%lf %lf",&p[i].x,&p[i].y);
72     printf("%.2lf\n",Andrew());
73     return 0;
74 }
Code

 

还有一个用叉积写的,原理都是一样的, 不过我自己不是很喜欢这种写法:

 1 /*
 2 ID: Starry21
 3 LANG: C++
 4 TASK: shuttle           
 5 */ 
 6 #include<cstdio>
 7 #include<algorithm>
 8 #include<vector>
 9 #include<cstring>
10 #include<queue>
11 #include<map>
12 #include<iostream>
13 #include<cmath>
14 using namespace std;
15 #define ll long long
16 #define INF 0x3f3f3f3f
17 #define N 10005
18 struct node{
19     double x,y;
20 };
21 node p[N],s[N]/*凸包上的点*/;
22 int n;
23 double dis(node a,node b)
24 {
25     return sqrt(((a.x-b.x)*(a.x-b.x))+((a.y-b.y)*(a.y-b.y)));
26 }
27 bool cmp(node a,node b)
28 {
29     if(a.x==b.x) return a.y<b.y;
30     return a.x<b.x;
31 }
32 bool Cross(node a,node b,node c)
33 {
34     double x1=a.x-b.x,y1=a.y-b.y;
35     double x2=c.x-b.x,y2=c.y-b.y;
36     if((x1*y2-x2*y1)<=0) return 0;
37     //如果不希望在凸包的边上有输入点。把<=改成<
38     return 1;
39 }
40 int Andrew()
41 {
42     sort(p+1,p+n+1,cmp);
43     int num=0;
44     for(int i=1;i<=n;i++)
45     {
46         while(num>1&&!Cross(s[num-1],s[num-2],p[i]))
47             num--;
48         s[num++]=p[i];
49     }
50     int tmp=num;
51     for(int i=n-1;i>=1;i--)
52     {
53         while(num>tmp&&!Cross(s[num-1],s[num-2],p[i]))
54             num--;
55         s[num++]=p[i];
56     }
57     if(n>1) num--;
58     return num; 
59 }
60 int main() 
61 {
62     //freopen("shuttle.in","r",stdin);
63     //freopen("shuttle.out","w",stdout);
64     scanf("%d",&n);
65     for(int i=1;i<=n;i++)
66         scanf("%lf %lf",&p[i].x,&p[i].y);
67     int num=Andrew();
68     double sum=0;
69     for(int i=1;i<=num-1;i++)
70         sum+=dis(s[i],s[i+1]);
71     sum+=dis(s[num],s[1]);//还有第n个点到第1个点的距离
72     printf("%.2lf",sum);
73     return 0;
74 }
Code

 

 

posted @ 2019-11-09 15:48  Starlight_Glimmer  阅读(587)  评论(0编辑  收藏  举报
浏览器标题切换
浏览器标题切换end