凸包

凸包算法

凸包类型的题算法主要有三种:JarvisMarch算法、Graham算法和Andrew算法,这三种算法时间性能上递增。

JarvisMarch算法

  1 /******************************************************************
  2                     Jarvis March的步进算法
  3 算法复杂度:O(nH)。(其中 n 是点的总个数,H 是凸包上的点的个数)
  4 ******************************************************************/
  5 
  6 /*
  7 思想:
  8 纵坐标最小然后横坐标最小的点一定是凸包上的点, 我们将其记为p0,从p0
  9 开始,按逆时针的方向,逐个找凸包上的点,每前进一步找到一个点,所以叫
 10 作步进法。
 11 选取下一个点的方法:
 12 假设已找到p0、p1,则利用跟p0p1向量夹角最小的点作为p2。(p1则利用p0p1
 13 向量和水平线的夹角)
 14 */
 15 
 16 //#include <bits/stdc++.h>
 17 #include <queue>
 18 #include <cstdio>
 19 #include <cmath>
 20 #include <algorithm>
 21 
 22 using namespace std;
 23 
 24 const int MAXN = 10005;
 25 const double MAXD = 1e9;
 26 const double ACCUR = 1e-9;
 27 
 28 struct node
 29 {
 30     double x, y;        //点的坐标
 31     bool operator < (const node n) const
 32     {
 33         if (abs(y-n.y) < ACCUR) return x < n.x;
 34         else    return y < n.y;
 35     }
 36     bool operator == (const node n) const
 37     {
 38         return (abs(x-n.x) < ACCUR) && (abs(y-n.y) < ACCUR);
 39     }
 40     void operator = (const node n)
 41     {
 42         x = n.x;
 43         y = n.y;
 44     }
 45 };
 46 
 47 struct vect
 48 {
 49     double x, y;
 50     void operator = (const vect v)
 51     {
 52         x = v.x;
 53         y = v.y;
 54     }
 55     double operator *(const vect v) const
 56     {
 57         return x*v.x + y*v.y;
 58     }
 59 };
 60 
 61 bool equal (const double d1, const double d2)
 62 {
 63     return abs(d1-d2) < ACCUR;
 64 }
 65 vect vform(const node n1, const node n2)
 66 {
 67     vect tmpv;
 68     tmpv.x = n2.x - n1.x;
 69     tmpv.y = n2.y - n1.y;
 70     return tmpv;
 71 }
 72 double vlen(const vect v)
 73 {
 74     return sqrt(v.x*v.x+v.y*v.y);
 75 }
 76 double vcos(const vect v1, const vect v2)
 77 {
 78     return (v1*v2)/(vlen(v1)*vlen(v2));
 79 }
 80 double area (const node n1, const node n2, const node n3)
 81 {
 82     double b1, b2, b3;
 83     b1 = vlen(vform(n1,n2));
 84     b2 = vlen(vform(n2,n3));
 85     b3 = vlen(vform(n3,n1));
 86     double b = (b1+b2+b3)/2;
 87     return sqrt(b*(b-b1)*(b-b2)*(b-b3));
 88 }
 89 
 90 node p[MAXN];           //点集
 91 queue <node> bq;        //凸包顶点集
 92 
 93 int main()
 94 {
 95     int n;
 96     while(scanf("%d", &n) == 1)
 97     {
 98         if(n == 0)
 99         {
100             break;
101         }
102         /*【注意】第一个点先不标记,作为循环结束条件(即最后找到第一个点)*/
103         int f[MAXN] = {0};      //点集标记数组;
104         vect v;                 //v表示上两个点形成的向量。
105         node p0, p1;             //p0表示第一个点,p1表示上一个点。
106         p0.x = p0.y = MAXD;     //初始化
107 
108         for (int i = 0; i < n; ++i)
109         {
110             scanf("%lf%lf", &(p[i].x), &(p[i].y));
111             if (p[i] < p0)
112             {
113                 p0 = p[i];
114             }
115         }
116 
117         p1 = p0;                //初始化上一个点
118         //【注意】初始化向量的选取跟第一个点的选取关。
119         //如果第一个点是横坐标最小然后纵坐标最小则初始向量为竖直单位向量
120         v.x = 1; v.y = 0;       //初始向量为水平单位向量。
121         do
122         {
123             node p2;            //待判定的点。
124             vect v1;            //待判定的向量
125             int j;              //带判定的点的下标
126             double minvcos = -1, minvlen = MAXD;    //初始最大夹角和最小向量长度。
127             for (int i = 0; i < n; ++i)
128             {
129                 if (!f[i])      //判断该点是否已经在凸包上
130                 {
131                     vect tmpv;
132                     tmpv.x = p[i].x-p1.x;
133                     tmpv.y = p[i].y-p1.y;
134                     if (vcos(v,tmpv) > minvcos)
135                     {
136                         p2 = p[i];
137                         v1 = tmpv;
138                         j = i;
139                         minvcos = vcos(v,tmpv);
140                         minvlen = vlen(tmpv);
141                     }
142                     else if (equal(vcos(v,tmpv),minvcos) && vlen(tmpv) < minvlen)
143                     {
144                         p2 = p[i];
145                         v1 = tmpv;
146                         j = i;
147                         minvcos = vcos(v,tmpv);
148                         minvlen = vlen(tmpv);
149                     }
150                 }
151             }
152             bq.push(p2);
153             p1 = p2;
154             v = v1;
155             f[j] = 1;
156             //printf("minvcos=%f,minvlen=%f\n", minvcos, minvlen);
157         }while(!(p1==p0));
158 
159         /*
160         while(!bq.empty())
161         {
162             node tmpp = bq.front();
163             printf("(%f,%f)\n", tmpp.x, tmpp.y);
164             bq.pop();
165         }
166         */
167         
168         //凸包周长
169         double ans = 0;
170         node fp, ep;
171         fp = p0;
172         while(!bq.empty())
173         {
174             ep = bq.front();
175             bq.pop();
176             ans += vlen(vform(fp, ep));
177             fp = ep;
178         }
179         printf("%.2f\n", ans);
180      
181         /*
182         //凸包面积
183         double ans = 0;
184         node fp = bq.front();
185         bq.pop();
186         node np = bq.front();
187         bq.pop();
188         while(!bq.empty())
189         {
190             node ep = bq.front();
191             bq.pop();
192             ans += area(fp,np,ep);
193             np = ep;
194             //printf("(%f,%f)\n", tmpp.x, tmpp.y);
195         }
196         printf("%d\n", (int)ans/50);
197         */
198     }
199     return 0;
200 }

 

Graham算法

  1 /************************************************************************
  2                         Graham Scan算法
  3 时间复杂度:O(nlogn)。Scan过程为O(n),预处理排序为O(nlogn)。
  4 预处理排序:极角排序。
  5 ************************************************************************/
  6 
  7 
  8 /*
  9 思想:
 10 把所有点放在二维坐标系中,则纵坐标最小的点一定是凸包上的点,记为p0。计算
 11 各个点相对p0的幅角,按从小到大的顺序对各个点排序。(当幅角相同是,距离p0
 12 比较近的排在前面)则幅角最小的点和最大的点一定在凸包上。取幅角最小的点记
 13 为p1,将p0、p1入栈。连接栈顶的点和次栈顶的点,得到直线l,看当前点是在直线
 14 的右边还是左边,在右边则栈顶元素不是凸包上的点,将其弹出,返回继续执行。如
 15 果在左边,则当前点是凸包上的点。一直到幅角最大的那个点为之。
 16 分析:
 17 两个向量的叉积P1xP2 = x1*y2 - x2*y1,其中用结果的正负代表叉乘结果的方向。
 18 该公式本质是两个三维向量(z轴分量为0)叉乘的结果(原来结果为(x1*y2 - x2*y1)
 19 *k,其中k是z轴单位向量)。
 20 */
 21 
 22 
 23 //#include <bits/stdc++.h>
 24 #include <stack>
 25 #include <cstdio>
 26 #include <cmath>
 27 #include <algorithm>
 28 
 29 using namespace std;
 30 
 31 const int MAXN = 10005;
 32 const double MAXD = 1e9;
 33 const double ACCUR = 1e-9;
 34 
 35 struct node
 36 {
 37     double x, y;        //点的坐标
 38     bool operator < (const node n) const
 39     {
 40         if (abs(y-n.y) < ACCUR) return x < n.x;
 41         else    return y < n.y;
 42     }
 43     bool operator == (const node n) const
 44     {
 45         return (abs(x-n.x) < ACCUR) && (abs(y-n.y) < ACCUR);
 46     }
 47     void operator = (const node n)
 48     {
 49         x = n.x;
 50         y = n.y;
 51     }
 52 };
 53 
 54 struct vect
 55 {
 56     double x, y;
 57     void operator = (const vect v)
 58     {
 59         x = v.x;
 60         y = v.y;
 61     }
 62     //叉积
 63     double operator *(const vect v) const
 64     {
 65         return x*v.y - y*v.x;
 66     }
 67 };
 68 
 69 node p0;            //纵坐标最小的点
 70 
 71 bool equal (const double d1, const double d2)
 72 {
 73     return abs(d1-d2) < ACCUR;
 74 }
 75 vect vform(const node n1, const node n2)
 76 {
 77     vect tmpv;
 78     tmpv.x = n2.x - n1.x;
 79     tmpv.y = n2.y - n1.y;
 80     return tmpv;
 81 }
 82 double vlen(const vect v)
 83 {
 84     return sqrt(v.x*v.x+v.y*v.y);
 85 }
 86 double vcos(const vect v1, const vect v2)
 87 {
 88     return (v1*v2)/(vlen(v1)*vlen(v2));
 89 }
 90 //极角排序
 91 bool cmpp (const node p1, const node p2)
 92 {
 93     vect v1, v2;
 94     v1 = vform(p0, p1);
 95     v2 = vform(p0, p2);
 96     if (equal(v1*v2,0))
 97     {
 98         return vlen(v1) < vlen(v2);
 99     }
100     else
101     {
102         return v1*v2 > 0;
103     }
104 }
105 //叉积判断点(v2的终点)在v1左边还是右边
106 bool cmpv (const vect v1, const vect v2)
107 {
108     return (v1*v2 > 0) || equal(v1*v2,0);
109 }
110 double area (const node n1, const node n2, const node n3)
111 {
112     /*
113     //海伦公式
114     double b1, b2, b3;
115     b1 = vlen(vform(n1,n2));
116     b2 = vlen(vform(n2,n3));
117     b3 = vlen(vform(n3,n1));
118     double b = (b1+b2+b3)/2;
119     return sqrt(b*(b-b1)*(b-b2)*(b-b3));
120     */
121 
122     //叉积公式(叉积为平行四边形面积)
123     vect v1, v2;
124     v1 = vform(n1, n2);
125     v2 = vform(n1, n3);
126     return abs(v1*v2)/2;
127 }
128 
129 node p[MAXN];           //点集
130 stack <node> bs;        //凸包顶点集
131 
132 int main()
133 {
134     int n;
135     p0.x = p0.y = MAXD; //初始化第一个点
136     scanf("%d", &n);
137     for(int i = 0; i < n; ++i)
138     {
139         scanf("%lf%lf", &(p[i].x), &(p[i].y));
140         if(p[i] < p0)
141         {
142             p0 = p[i];
143         }
144     }
145     sort(p,p+n,cmpp);
146     bs.push(p[0]);
147     bs.push(p[1]);
148     int j = 2;
149     while(j < n)
150     {
151         //取出栈顶和次栈顶
152         node p1, p2;
153         p2 = bs.top();
154         bs.pop();
155         p1 = bs.top();
156         //构造叉乘向量
157         vect v1, v2;
158         v1 = vform(p1,p2);
159         v2 = vform(p2,p[j]);
160         if(cmpv(v1,v2))
161         {
162             bs.push(p2);
163             bs.push(p[j]);
164             ++j;
165         }
166     }
167     /*
168     while(!bs.empty())
169     {
170         node tmpp = bs.top();
171         printf("(%f,%f)\n", tmpp.x, tmpp.y);
172         bs.pop();
173     }
174     */
175     /*
176     //计算周长
177     double ans = 0;
178     node fp, ep;
179     fp = p[0];
180     while(!bs.empty())
181     {
182         ep = bs.top();
183         bs.pop();
184         ans += vlen(vform(fp, ep));
185         fp = ep;
186     }
187     printf("%.2f\n", ans);
188     */
189 
190     //计算面积
191     double ans = 0;
192     node fp, np, ep;
193     fp = bs.top();
194     bs.pop();
195     np = bs.top();
196     bs.pop();
197     while(!bs.empty())
198     {
199         ep = bs.top();
200         bs.pop();
201         ans += area(fp,np,ep);
202         np = ep;
203     }
204     printf("%d\n", (int)ans/50);
205     return 0;
206 }

 

Andrew算法

  1 /************************************************************************
  2                     Andrew算法(Graham Scan算法变种)
  3 时间复杂度:O(nlogn)。Scan过程为O(n),预处理排序为O(nlogn)。
  4 预处理排序:水平排序排序。
  5 ************************************************************************/
  6 
  7 
  8 /*
  9 思想:
 10 预处理排序改为水平排序,按照横坐标从小到大进行排序,横坐标相同则按纵坐标从
 11 小到大排。按照graham算法思想从p0、p1扫描所有点得到下凸包,再从pn-1、pn-2扫
 12 描所有点得到上凸包,二者结合即为整个凸包。(注意:这里的p1不一定在凸包里)
 13 分析:
 14 两个向量的叉积P1xP2 = x1*y2 - x2*y1,其中用结果的正负代表叉乘结果的方向。
 15 该公式本质是两个三维向量(z轴分量为0)叉乘的结果(原来结果为(x1*y2 - x2*y1)
 16 *k,其中k是z轴单位向量)。
 17 */
 18 
 19 
 20 //#include <bits/stdc++.h>
 21 #include <stack>
 22 #include <cstdio>
 23 #include <cmath>
 24 #include <algorithm>
 25 
 26 using namespace std;
 27 
 28 const int MAXN = 10005;
 29 const double MAXD = 1e9;
 30 const double ACCUR = 1e-9;
 31 
 32 struct node
 33 {
 34     double x, y;        //点的坐标
 35     //水平排序(与极角排序不一样,只能确定p0和pn-1在凸包内)
 36     bool operator < (const node n) const
 37     {
 38         if (abs(x-n.x) < ACCUR) return y < n.y;
 39         else    return x < n.x;
 40     }
 41     bool operator == (const node n) const
 42     {
 43         return (abs(x-n.x) < ACCUR) && (abs(y-n.y) < ACCUR);
 44     }
 45     void operator = (const node n)
 46     {
 47         x = n.x;
 48         y = n.y;
 49     }
 50 };
 51 
 52 struct vect
 53 {
 54     double x, y;
 55     void operator = (const vect v)
 56     {
 57         x = v.x;
 58         y = v.y;
 59     }
 60     //叉积
 61     double operator *(const vect v) const
 62     {
 63         return x*v.y - y*v.x;
 64     }
 65 };
 66 
 67 bool equal (const double d1, const double d2)
 68 {
 69     return abs(d1-d2) < ACCUR;
 70 }
 71 vect vform(const node n1, const node n2)
 72 {
 73     vect tmpv;
 74     tmpv.x = n2.x - n1.x;
 75     tmpv.y = n2.y - n1.y;
 76     return tmpv;
 77 }
 78 //计算向量长度
 79 double vlen(const vect v)
 80 {
 81     return sqrt(v.x*v.x+v.y*v.y);
 82 }
 83 //计算向量夹角余弦值
 84 double vcos(const vect v1, const vect v2)
 85 {
 86     return (v1*v2)/(vlen(v1)*vlen(v2));
 87 }
 88 /*
 89 //极角排序
 90 bool cmpp (const node p1, const node p2)
 91 {
 92     vect v1, v2;
 93     v1 = vform(p0, p1);
 94     v2 = vform(p0, p2);
 95     if (equal(v1*v2,0))
 96     {
 97         return vlen(v1) < vlen(v2);
 98     }
 99     else
100     {
101         return v1*v2 > 0;
102     }
103 }
104 */
105 //叉积判断点(v2的终点)在v1左边还是右边
106 bool cmpv (const vect v1, const vect v2)
107 {
108     return (v1*v2 > 0) || equal(v1*v2,0);
109 }
110 double area (const node n1, const node n2, const node n3)
111 {
112     /*
113     //海伦公式
114     double b1, b2, b3;
115     b1 = vlen(vform(n1,n2));
116     b2 = vlen(vform(n2,n3));
117     b3 = vlen(vform(n3,n1));
118     double b = (b1+b2+b3)/2;
119     return sqrt(b*(b-b1)*(b-b2)*(b-b3));
120     */
121 
122     //叉积公式(叉积为平行四边形面积)
123     vect v1, v2;
124     v1 = vform(n1, n2);
125     v2 = vform(n1, n3);
126     return abs(v1*v2)/2;
127 }
128 
129 node p[MAXN];           //点集
130 stack <node> bs;        //凸包顶点集
131 
132 int main()
133 {
134     int n;
135     scanf("%d", &n);
136     for(int i = 0; i < n; ++i)
137     {
138         scanf("%lf%lf", &(p[i].x), &(p[i].y));
139     }
140     sort(p,p+n);
141     //正向扫描(上凸包)
142     bs.push(p[0]);
143     bs.push(p[1]);
144     int j = 2;
145     while(j < n)
146     {
147         //取出栈顶和次栈顶
148         node p1, p2;
149         p2 = bs.top();
150         bs.pop();
151         //p1不一定在凸包中
152         if(bs.empty())
153         {
154             bs.push(p2);
155             bs.push(p[j]);
156             ++j;
157         }
158         else
159         {
160             p1 = bs.top();
161             //构造叉乘向量
162             vect v1, v2;
163             v1 = vform(p1,p2);
164             v2 = vform(p2,p[j]);
165             if(cmpv(v1,v2))
166             {
167                 bs.push(p2);
168                 bs.push(p[j]);
169                 ++j;
170             }
171         }
172     }
173     //反向扫描(下凸包)
174     int k = n-2;
175     while(k >= 0)
176     {
177         //取出栈顶和次栈顶
178         node p1, p2;
179         p2 = bs.top();
180         bs.pop();
181         p1 = bs.top();
182         //构造叉乘向量
183         vect v1, v2;
184         v1 = vform(p1,p2);
185         v2 = vform(p2,p[k]);
186         if(cmpv(v1,v2))
187         {
188             bs.push(p2);
189             bs.push(p[k]);
190             --k;
191         }
192     }
193     bs.pop();   //p0重复进栈一次
194 
195     /*
196     while(!bs.empty())
197     {
198         node tmpp = bs.top();
199         printf("(%f,%f)\n", tmpp.x, tmpp.y);
200         bs.pop();
201     }
202     */
203     /*
204     //计算周长
205     double ans = 0;
206     node fp, ep;
207     fp = p[0];
208     while(!bs.empty())
209     {
210         ep = bs.top();
211         bs.pop();
212         ans += vlen(vform(fp, ep));
213         fp = ep;
214     }
215     printf("%.2f\n", ans);
216     */
217     
218     //计算面积
219     double ans = 0;
220     node fp, np, ep;
221     fp = bs.top();
222     bs.pop();
223     np = bs.top();
224     bs.pop();
225     while(!bs.empty())
226     {
227         ep = bs.top();
228         bs.pop();
229         ans += area(fp,np,ep);
230         np = ep;
231     }
232     printf("%d\n", (int)ans/50);
233 
234     return 0;
235 }

 

posted @ 2020-01-28 15:32  孤独な霊魂  阅读(215)  评论(0编辑  收藏  举报