【计算几何】 向量叉积&&Andrew算法求凸包 详解

一.预备知识

1.向量

向量是一类既有大小又有方向的量,如加速度,速度,位移等等
向量的编程表示:

typedef struct{
  double x;
  double y;
}Vector;

注:平面中的点也是用一对x,y来表示的,和向量是一样的,所以常常如下操作:

typedef struct{
  int x;
  int y;
}Point;

typedef Point Vector;

既避免了混淆两种变量,又使程序看起来非常简洁

重载运算符:

Vector operator - (Point a,Point b){
  return Vector {a.x-b.x,a.y-b.y};
}

↑两个点相减得到向量,便于书写

2.向量的叉积

叉积又称叉乘,符号为×。
两个向量叉乘的结果还是一个向量
其运算规则如下:
①所得向量的模为:
∣ a × b ∣ = ∣ a ∣ ⋅ ∣ b ∣ ⋅ s i n θ |a×b|=|a|\cdot|b|\cdot sinθ a×b=absinθ
其中 θ θ θ为向量a与向量b的夹角
②所得向量的方向为:
与a,b所在平面垂直,且满足右手定则(由a转向b角度小于180,大拇指所指方向即为叉乘所得向量的方向)

3.平面坐标中叉乘的计算方法

向量a: ( a x , a y ) (a_x,a_y) (ax,ay)
向量b: ( b x , b y ) (b_x,b_y) (bx,by)
a × b = a x ⋅ b y − a y ⋅ b x a×b=a_x\cdot b_y-a_y\cdot b_x a×b=axbyaybx

证明:
①叉乘满足分配律:
a × ( b + c ) = a × b + a × c a×(b+c)=a×b+a×c a×b+c=a×b+a×c

②令 e 1 e_1 e1表示x轴方向的单位向量, e 2 e_2 e2表示y轴方向的单位向量,则向量 a = a x ⋅ e 1 + a y ⋅ e 2 a=a_x\cdot e_1+a_y\cdot e_2 a=axe1+aye2,向量 b = b x ⋅ e 1 + b y ⋅ e 2 b=b_x\cdot e_1+b_y\cdot e_2 b=bxe1+bye2

a × b a×b a×b
= ( a x ⋅ e 1 + a y ⋅ e 2 ) ⋅ ( b x ⋅ e 1 + b y ⋅ e 2 ) =(a_x\cdot e_1+a_y\cdot e_2)\cdot(b_x\cdot e_1+b_y\cdot e_2) =(axe1+aye2)(bxe1+bye2)
= ( a x ⋅ b x ⋅ e 1 ⋅ e 1 ) + ( a x ⋅ b y ⋅ e 1 ⋅ e 2 ) + ( a y ⋅ b x ⋅ e 2 ⋅ e 1 ) + ( a y ⋅ b y ⋅ e 2 ⋅ e 2 ) =(a_x\cdot b_x\cdot e_1\cdot e_1)+(a_x\cdot b_y\cdot e_1\cdot e_2)+(a_y\cdot b_x\cdot e_2\cdot e_1)+(a_y\cdot b_y\cdot e_2\cdot e_2) =(axbxe1e1)+(axbye1e2)+(aybxe2e1)+(aybye2e2)

∣ a × b ∣ = ∣ a ∣ ⋅ ∣ b ∣ ⋅ s i n θ |a×b|=|a|\cdot|b|\cdot sinθ a×b=absinθ可得:

e 1 ⋅ e 1 = 0 ; e 2 ⋅ e 2 = 0 e_1\cdot e_1=0;e_2\cdot e_2=0 e1e1=0;e2e2=0(夹角为0)

e 1 ⋅ e 2 = 1 ; e 2 ⋅ e 1 = − 1 e_1\cdot e_2=1;e_2\cdot e_1=-1 e1e2=1;e2e1=1(夹角90°)

由右手定则可得,前者叉乘方向指向z轴正方向,所以值为正。后者反之,值为负。
综上,即得 a × b = a x ⋅ b y − a y ⋅ b x a×b=a_x\cdot b_y-a_y\cdot b_x a×b=axbyaybx

4.叉积的应用

①由向量叉积的方向可得:

如果向量b在向量a的左边/上边(逆时针方向小于180°),则它们的叉乘结果方向垂直纸面向外(从坐标系的角度考虑,即z轴正方向);

反之,如果向量b在向量a的右边/下边(逆时针方向大于180°),则它们的叉乘结果方向垂直纸面向里(z轴负方向);

建议画图理解

所以在坐标系中,叉乘结果为正代表向量b在向量a的逆时针小于180°方向;结果为负代表向量b在向量a的逆时针大于180°方向(为0共线/平行)

↑也就是叉乘在求凸包中的应用了,即求得向量的顺逆时针关系

②由向量叉积的模可得:

高中学过的三角形面积公式:
1 2 ∣ a ∣ ⋅ ∣ b ∣ ⋅ s i n θ \frac{1}{2} |a|\cdot|b|\cdot sinθ 21absinθ
(因为 ∣ b ∣ ⋅ s i n θ |b|\cdot sinθ bsinθ即三角形中边a的高)
那么叉积的值也就是向量a,b构成的平行四边形的面积

5.叉乘计算代码:

double Cross(Vector a,Vector b){
  return A.x*B.y-A.y*B.x;
}

tips:叉乘英文为Cross Product

二.Andrew 算法

1.凸包定义

将给定点包围在内部的面积最小的凸多边形
因为要满足面积最小,凸包必然是由这给定点中的若干个组成的

2.Andrew 算法

①将所有点按x坐标从小到大排序(如果x相同,则y从小到大排序),可以通过反证法证明:x坐标最小的那个点必然是凸包上的一个点
②不妨令坐标都被存放在数组p(排过序)中,并用一个Ch数组存放凸包(tips:凸包英文Convexhull),取出数组p中的前两个点 p 1 p_1 p1 p 2 p_2 p2加入凸包
③判断 p 3 p_3 p3 p 1 p 2 p_1p_2 p1p2 p 1 p_1 p1 p 2 p_2 p2构成的向量)的关系:
当代毕加索
如果是第1种情况, p 3 p_3 p3在向量 p 1 p 2 p_1p_2 p1p2的下方(即 p 1 p 3 p_1p_3 p1p3 p 1 p 2 p_1p_2 p1p2的顺时针小于180°的位置),那么我们就可以判断p2必定不是凸包上的点,于是把 p 2 p_2 p2点从凸包中删除

如果是第2种情况,则 p 2 p_2 p2仍可能是凸包上的点,不做处理

然后将 p 3 p_3 p3加入凸包

④不断重复上述操作,判断新的点是否在“前进方向”的上方,(即最近加入凸包的两个点形成的向量),如果不是,则需要删除最近加入凸包的那个点,注意删除完后还需要继续判断

用栈来表示可以更好理解,不断向栈加入点,遇到新点时和栈顶的两个点进行形成的向量比较位置关系,若在下方则删除栈顶的点,然后继续和栈顶的两个点比较,直到新点处于上方为止(或者栈中点不足两个)。

⑤这样不断操作直到点 p n p_n pn,就求出了凸包的下半部分,然后反过来从 p n p_n pn开始,重复操作就能求出凸包的上半部分。合起来就得到了一个完整的凸包

三.模板

①判断新点与“前进方向”的位置关系通过前文所讲的向量叉积实现,根据结果正负性判断方向

②这道题用了栈的思想,但是用数组模拟也可以

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;

const double eps=1e-8;

typedef struct{
 int x,y;
}Point;

typedef Point Vector;

Point P[50005];
Point Ch[50005];

Vector operator - (Point a,Point b){
  return Vector {a.x-b.x,a.y-b.y};
}

int Cross(Vector a,Vector b){
  return a.x*b.y-a.y*b.x;
}

bool cmp(Point a,Point b){
   if(a.x!=b.x) return a.x<b.x;
   return a.y<b.y;
}

int main()
{
    int N;
    scanf("%d",&N);
    for(int i=0;i<N;i++)
    scanf("%d%d",&P[i].x,&P[i].y);
    sort(P,P+N,cmp);

    int cnt=0;
    for(int i=0;i<N;i++){
      while(cnt>1&&Cross(Ch[cnt-2]-Ch[cnt-1],Ch[cnt-2]-P[i])<=0)
          cnt--;
      Ch[cnt++]=P[i];
    }
    int vis=cnt;
    for(int i=N-2;i>=0;i--){
       while(cnt>vis&&Cross(Ch[cnt-2]-Ch[cnt-1],Ch[cnt-2]-P[i])<=0)
          cnt--;
       Ch[cnt++]=P[i];
    }
    return 0;
}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值