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

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

 

凸包的概念

首先,引入凸包的概念:

 

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

 大概长这个样子:

 

 

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

Andrew算法流程和思想

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

 

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

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

 第一次,把12加入答案中

 

 

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

 

 

 

 

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

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

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

 

 

 

 

 

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

 

 

 加入53>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 @   Starlight_Glimmer  阅读(701)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
浏览器标题切换
浏览器标题切换end
点击右上角即可分享
微信分享提示