核心:叉积运算
意义:平行四边形的面积
问题:
1.判断Op2在Op1的顺时针方向还是逆时针方向
计算p1×p2
如果p1×p2>0,那么Op2在Op1的逆时针方向
如果p1×p2<0,那么Op2在Op1的顺时针方向
如果p1×p2==0,那么O、p1、p2共线
2.判断左转还是右转
计算(p1-p0)×(p2-p0)=(x1-x0)(y2-y0)-(y1-y0)(x2-x0)
如果为正,那么p0p2在p0p1的逆时针方向,左转
如果为负,那么p0p2在p0p1的顺时针方向,右转
如果为0,那么p0、p1、p2共线,没有转向
3.判断两线段是否相交
相交的情况有五种,相互横跨,或者某一条线段的端点在另一条线段上(四个端点,四种情况)
横跨:
int Intersect(node p1,node p2,node p3,node p4)
{
int d1,d2,d3,d4;
d1=comp(p3,p4,p1);
d2=comp(p3,p4,p2);
d3=comp(p1,p2,p3);
d4=comp(p1,p2,p4);
if(((d1>0&&d2<0)||(d1<0&&d2>0))&&((d3>0&&d4<0)||(d3<0&&d4>0))) return 1;//交叉异号时为互跨
else if (d1==0 && OnSegment(p3,p4,p1)) return 1;
else if (d2==0 && OnSegment(p3,p4,p2)) return 1;
else if (d3==0 && OnSegment(p1,p2,p3)) return 1;
else if (d4==0 && OnSegment(p1,p2,p4)) return 1;
//已知共线,判断共线点是否在线段中
return -1;
}
int comp(node pi,node pj,node pk)
{
return (pk-pi)*(pj-pi);
//(xk-xi)*(yj-ji)-(xj-xi)*(yk-yi)
}
int OnSegment(node pi,node pj,node pk)
{
if((min(xi,xj)<=xk<=max(xi,xj))&&(min(yi,yj)<=yk<=max(yi,yj))) return 1;
return -1;
}
4.确定任意一对线段是否相交
假设没有一条输入线段是垂直的,且假设没有三条线段交于一点的情况
设定一个垂直的直线,从左向右移动进行“扫除”操作,因为每一条线段都是垂直的,所以任何一条线段与垂直扫除线只有一个交点,故可根据此交点的y坐标对所有线段进行排序。
记a≥rb为在x坐标值为r处,a线段与垂直扫除线的交点在b线段与垂直扫除线的交点的上面。
即ya[r]>yb[r]。按照这个顺序将已扫描到的线段排一个前序关系。
对于两条线s1、s2,如果存在交点,当垂直扫描线扫描到两线段交点时,两线段的前序关系会发生变化。
凸包
1.Graham算法
通过维持一个关于候选点的栈S来解决凸包问题。
输入集合Q中每个点都会被压入栈一次,不是凸包中点的最终被弹出栈。当算法终止时,栈S中仅包含凸包中的点,且这些点以逆时针顺序出现在边界上。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<memory.h>
using namespace std;
struct point
{
double x;
double y;
}num[100010],stack[100010],tmp;
int n;
double CJ(point a0,point a1,point b0,point b1)
{
return (a1.x-a0.x)*(b1.y-b0.y) - (b1.x-b0.x)*(a1.y-a0.y);
}
double dis(point a,point b)
{//计算a、b之间的距离
return sqrt((double)(b.y-a.y)*(double)(b.y-a.y) + (double)(b.x-a.x)*(double)(b.x-a.x));
}
double cross(point a,point b,point c)
{//
return (a.x-c.x)*(b.y-c.y) - (b.x-c.x)*(a.y-c.y);
}
bool cmp(struct point a, struct point b)
{
double tmp = CJ(num[1],a,num[1],b);
if(tmp > 0)
return 1;
if(tmp == 0 && dis(num[0],a) < dis(num[0],b))
return 1;
//dis(num[0],a)是点a和原点(0,0)之间的距离
else
return 0;
}
int main()
{
int i,cnt;
double ans = 0, res = 0;
scanf("%d",&n);
for(i=1;i<=n;i++)
{
scanf("%lf%lf", &num[i].x,&num[i].y);
//寻找y坐标最小的作为p0,如果有多个就选其中x坐标最小的。p0一定是凸包的第一个点
if(i!=1&& num[i].y < num[1].y)
{
tmp = num[i];
num[i] = num[1];
num[1] = tmp;
}
if(i!=1&&num[i].y == num[1].y && num[i].x < num[1].x)
{
tmp = num[i];
num[i] = num[1];
num[1] = tmp;
}
}
sort(num+2,num+n+1,cmp);//按照点与p0点之间的极角排序
//sort函数的第一个参数是排序起始地址,第二次参数是排序结束地址,第三个参数是排序方式
//因为num[1]不参与排序,从num[2]开始,故起始地址为num+2
//num[n]是num+n,再加一是结束地址
stack[1] = num[1];
stack[2] = num[2];
stack[3] = num[3];
//将前三个点入栈
cnt = 3;
//cnt是栈中元素的数量
for(i=4;i<=n;i++)
{
while(cnt > 1 && CJ(stack[cnt],num[i],stack[cnt-1],stack[cnt]) > 0)
cnt--;
//如果CJ(stack[cnt],num[i],stack[cnt-1],stack[cnt])>0,那么由stack[cnt]stack[cnt-1]到stack[cnt]num[i]是右转,故stack[cnt]不是凸包中的点,将其出栈
cnt++;
stack[cnt] = num[i];//将下一个元素入栈,继续判断是不是凸包中的点
}
stack[cnt+1] = num[1];//将num[1]入栈作为凸包结束的点
for(i = 1;i <= cnt;i++)
ans += dis(stack[i],stack[i+1]);//ans为凸包的周长
res = fabs(cross(stack[2],stack[3],stack[1]));
for(i = 3;i < cnt;i++)
res += fabs(cross(stack[i],stack[i+1],stack[1]));
res = fabs(res) / 2.0;
printf("%.2lf %.2lf", ans, res);
return 0;
}
2.Jarvis步进法
Jarvis步进法运用一种打包或包装礼物的技术计算凸包。运行时间为O(nh),其中h是凸包中顶点的个数。当h=o(lgn)时,Jarvis步进法在渐进意义上比Graham快。
寻找最近点对
之前文章中写过