ToLeftTest
这是一个判断一个点在向量的左边还是右边的算法。
如上图,我们有三个点,假定分别有坐标。
- (a) ((xa, ya))
- (b) ((xb, yb))
- (c) ((xc, yc))
则有向量
- (vec A = b - a = (xb - xa, yb - ya))
- (vec B = c - a = (xc - xa, yc - ya))
叉乘 (vec A imes B) 也就是 (mid A mid mid B mid sin alpha),我们可以得到:
如果 (vec A 和 vec B) 夹角是在 ((0, 2pi)) 之间的时候叉乘是正数,否者就是负数,这也就正好对应了 c 点是在 ab 向量的左侧还是右侧。
graham scan
graham scan算法的思想
- 首先我们得找到一个基准点,一般取最下面而且最左边的,也就是 (lowest than leftmost)
- 以这个点为基准点,在逆时针方向上找到一条最近的极边,通过逆时针扫描,对点进行排序,如果有两个点在同一条直线上的话,取离基准点最近的点序号更前。
如下图是一个完整的点排序过程
- 从这里我们不难发现前两个点一定是极点,其连成的边也一定是级边。
- 接下来的算法就是通过最后两个找到的点取找下一个极点。也就是对这三个点做ToLeftTest。如果满足下一个点在这最后找到的两个点的左边,则暂时先判定这个点是极点。重复操作。
下面是整个算法的演示过程
3显然在12的左边,建立级边。
对2,3,4分析发现,4在2,3的右侧,把已经找好的最后的一个点删去(也就是3),再通过对1, 2, 4检测,ToLeftTest成立,4符合要求。
后面的点,没有歧义了,最后就会变成这样。
完美的凸包构造出来了
回溯选点
我们可以看出这里已经回溯选点了一次,但是在下一次选点的时候还是不能满足,因此我们再不满足要求的时候需要不断的回溯选点
代码
这里选了一道板子题
P2742 [USACO5.1]圈奶牛Fencing the Cows /【模板】二维凸包
//Powered by CK
#include<bits/stdc++.h>
using namespace std;
const double INF = 1e100;
const double pi = acos(-1.0);
const double eps = 1e-8;
const int N = 1e5 + 10;
int n, cnt;
struct point {
double x, y;
point(double a = 0.0, double b = 0.0) : x(a), y(b) {}
}p[N], ans[N], fir;
int sgn(double x) {
if(fabs(x) < eps) return 0;
if(x > 0) return 1;
return -1;
}
point operator -(point a, point b) {
return point(a.x - b.x, a.y - b.y);
}
double cross(point a, point b) {
return a.x * b.y - a.y * b.x;
}
double dis(point a, point b) {
point temp = a - b;
return sqrt(temp.x * temp.x + temp.y * temp.y);
}
bool cmp(point a, point b) {
int flag = sgn(cross(a - fir, b - fir));
if(flag == 1) return true;
if(flag == 0 && dis(fir, a) < dis(fir, b)) return true;
return false;
}
void graham() {
sort(p, p + n, cmp);
ans[0] = p[0], ans[1] = p[1], cnt = 2;
for(int i = 2; i < n; i++) {
while(sgn(cross(ans[cnt - 1] - ans[cnt - 2], p[i] - ans[cnt - 2])) == -1)
cnt--;
ans[cnt++] = p[i];
}
ans[cnt] = ans[0];
double target = 0;
for(int i = 0; i < cnt; i++)
target += dis(ans[i], ans[i + 1]);
printf("%.2f
", target);
}
int main() {
// freopen("in.txt", "r", stdin);
fir.x = fir.y = INF;
scanf("%d", &n);
for(int i = 0; i < n; i++) {
scanf("%lf %lf", &p[i].x, &p[i].y);
if(p[i].y < fir.y) fir = p[i];
else if(p[i].y == fir.y && p[i].x < fir.x) fir = p[i];
}
graham();
return 0;
}