程帅霞

不断受挫,不停起身,不断追寻,不止AC~~

导航

凸包算法详解

Graham扫描法

时间复杂度:O(n㏒n) 
思路:Graham扫描的思想是先找到凸包上的一个点,然后从那个点开始按逆时针方向逐个找凸包上的点,实际上就是进行极角排序,然后对其查询使用。 
这里写图片描述 
步骤:

  1. 把所有点放在二维坐标系中,则纵坐标最小的点一定是凸包上的点,如图中的P0。
  2. 把所有点的坐标平移一下,使 P0 作为原点,如上图。
  3. 计算各个点相对于 P0 的幅角 α ,按从小到大的顺序对各个点排序。当 α 相同时,距离 P0 比较近的排在前面。例如上图得到的结果为 P1,P2,P3,P4,P5,P6,P7,P8。我们由几何知识可以知道,结果中第一个点 P1 和最后一个点 P8 一定是凸包上的点。 
    (以上是准备步骤,以下开始求凸包) 
    以上,我们已经知道了凸包上的第一个点 P0 和第二个点 P1,我们把它们放在栈里面。现在从步骤3求得的那个结果里,把 P1 后面的那个点拿出来做当前点,即 P2 。接下来开始找第三个点:
  4. 连接P0和栈顶的那个点,得到直线 L 。看当前点是在直线 L 的右边还是左边。如果在直线的右边就执行步骤5;如果在直线上,或者在直线的左边就执行步骤6。
  5. 如果在右边,则栈顶的那个元素不是凸包上的点,把栈顶元素出栈。执行步骤4。
  6. 当前点是凸包上的点,把它压入栈,执行步骤7。
  7. 检查当前的点 P2 是不是步骤3那个结果的最后一个元素。是最后一个元素的话就结束。如果不是的话就把 P2 后面那个点做当前点,返回步骤4。

  最后,栈中的元素就是凸包上的点了。 
  以下为用Graham扫描法动态求解的过程: 
这里写图片描述

  下面静态求解过程

 1 #include<iostream>
 2 #include<string.h>
 3 #include<algorithm>
 4 #include<cstdio>
 5 #include<cmath>
 6 using namespace std;
 7 const int maxn=105;
 8 const double PI=acos(-1.0);
 9 struct node{int x,y;};
10 node vex[maxn];//存入所有坐标点
11 node stackk[maxn];//凸包中所有的点
12 bool cmp1(node a,node b){//按点的坐标排序
13     if(a.y==b.y)return a.x<b.x;//如果纵坐标相同,则按横坐标升序排
14     else return a.y<b.y;//否则按纵坐标升序排
15 }
16 bool cmp2(node a,node b){//以基点为坐标原点,极角按升序排,这里可用atan2函数或者叉积来进行极角排序,但是用atan2函数来排序效率高时间快,不过精度比叉积低
17     double A=atan2(a.y-stackk[0].y,a.x-stackk[0].x);//返回的是原点至点(x,y)的方位角,即与x轴的夹角
18     double B=atan2(b.y-stackk[0].y,b.x-stackk[0].x);
19     if(A!=B)return A<B;//逆时针方向为正值,极角小的排在前面
20     else return a.x<b.x;//如果极角相同,则横坐标在前面的靠前排列
21 }
22 int cross(node p0,node p1,node p2){//计算两个向量a、b(a=(x1,y1),b=(x2,y2))的叉积公式:a×b=x1y2-x2y1 ===> p0p1=(x1-x0,y1-y0),p0p2=(x2-x0,y2-y0)
23     return (p1.x-p0.x)*(p2.y-p0.y)-(p2.x-p0.x)*(p1.y-p0.y);
24 }
25 double dis(node a,node b){//计算两点之间的距离
26     return sqrt((a.x-b.x)*(a.x-b.x)*1.0+(a.y-b.y)*(a.y-b.y));
27 }
28 int main(){
29     int t;
30     while(~scanf("%d",&t)&&t){
31         for(int i=0;i<t;++i)//输入t个点
32             scanf("%d%d",&vex[i].x,&vex[i].y);
33         if(t==1)printf("%.2f\n",0.00);//如果只有一个点,则周长为0.00
34         else if(t==2)printf("%.2f\n",dis(vex[0],vex[1]));//如果只有两个点,则周长为两个点的距离
35         else{
36             memset(stackk,0,sizeof(stackk));//清0
37             sort(vex,vex+t,cmp1);//先按坐标点的位置进行排序
38             stackk[0]=vex[0];//取出基点
39             sort(vex+1,vex+t,cmp2);//将剩下的坐标点按极角进行排序,以基点为坐标原点
40             stackk[1]=vex[1];//将凸包中的第二个点存入凸集中
41             int top=1;//当前凸包中拥有点的个数为top+1
42             for(int i=2;i<t;++i){//不断地找外围的坐标点
43                 while(top>0&&cross(stackk[top-1],stackk[top],vex[i])<=0)top--;//如果叉积为负数或0(0表示两向量共线),则弹出栈顶元素
44                 //虽然第2个凸点显然是最外围的一点,但加上top>0保证了栈中至少有2个凸点
45                 stackk[++top]=vex[i];
46             }
47             double s=0;
48             for(int i=1;i<=top;++i)//计算凸包的周长
49                 s+=dis(stackk[i-1],stackk[i]);
50             s+=dis(stackk[top],vex[0]);//最后一个点和第一个点之间的距离
51             printf("%.2f\n",s);
52             /*
53             int s=0;//计算凸包的面积
54             for(int i=1;i<=top;i++)
55                 s+=cross(st[i-1],st[i],e[0])/2;
56             */
57         }
58     }
59     return 0;
60 }

 

posted on 2021-02-15 13:27  程帅霞  阅读(444)  评论(0编辑  收藏  举报