凸包算法
凸包类型的题算法主要有三种: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 //#include17 #include 18 #include 19 #include 20 #include 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 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 //#include24 #include 25 #include 26 #include 27 #include 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 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 //#include21 #include 22 #include 23 #include 24 #include 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 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 }