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

文章目录

一.预备知识

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

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

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

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

typedef Point Vector;

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

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

由此可得,如果向量b在向量a的左边(逆时针方向小于180°),则它们的叉乘结果方向垂直纸面向外,反之,如果向量b在向量a的右边(逆时针方向大于180°),则它们的叉乘结果方向垂直纸面向里

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

规定垂直纸面向外为正方向

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

平面坐标下下叉乘的计算方法:
向量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=ax​⋅by​−ay​⋅bx​

证明:
①叉乘满足分配律:
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=ax​⋅e1​+ay​⋅e2​,向量 b = b x ⋅ e 1 + b y ⋅ e 2 b=b_x\cdot e_1+b_y\cdot e_2 b=bx​⋅e1​+by​⋅e2​

③ 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) =(ax​⋅e1​+ay​⋅e2​)⋅(bx​⋅e1​+by​⋅e2​)
= ( 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) =(ax​⋅bx​⋅e1​⋅e1​)+(ax​⋅by​⋅e1​⋅e2​)+(ay​⋅bx​⋅e2​⋅e1​)+(ay​⋅by​⋅e2​⋅e2​)

由 ∣ a × b ∣ = ∣ a ∣ ⋅ ∣ b ∣ ⋅ s i n θ |a×b|=|a|\cdot|b|\cdot sinθ ∣a×b∣=∣a∣⋅∣b∣⋅sinθ可得:
e 1 ⋅ e 1 = 0 ; e 2 ⋅ e 2 = 0 e_1\cdot e_1=0;e_2\cdot e_2=0 e1​⋅e1​=0;e2​⋅e2​=0(夹角为0)
e 1 ⋅ e 2 = 1 ; e 2 ⋅ e 1 = − 1 e_1\cdot e_2=1;e_2\cdot e_1=-1 e1​⋅e2​=1;e2​⋅e1​=−1
因为y轴在x轴左边,所以前者为正后者为负,又因为单位向量所以值为1

综上,即得 a × b = a x ⋅ b y − a y ⋅ b x a×b=a_x\cdot b_y-a_y\cdot b_x a×b=ax​⋅by​−ay​⋅bx​

叉乘计算代码:

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 p1​p2​( p 1 p_1 p1​、 p 2 p_2 p2​构成的向量)的关系:
【凸包】 向量叉积&&Andrew算法求凸包 详解
如果是第1种情况, p 3 p_3 p3​在向量 p 1 p 2 p_1p_2 p1​p2​的下方(即 p 1 p 3 p_1p_3 p1​p3​在 p 1 p 2 p_1p_2 p1​p2​的顺时针小于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​开始,重复操作就能求出凸包的上半部分。合起来就得到了一个完整的凸包

三.模板

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

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

③我写了一个P_to_V函数来描述点与向量的变换,更方便与简洁的写法是重载运算符,但我不会

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

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

typedef Point Vector;

Point P[50005];
Point Ch[50005]; //凸包


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 dis(Point a,Point b){ //两点间距离的平方
    return (a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y);
}


Vector P_to_V(Point a,Point b){  两点
   Vector ret;
   ret.x=b.x-a.x;
   ret.y=b.y-a.y;
   return ret;
}

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(P_to_V(Ch[cnt-1],Ch[cnt-2]),P_to_V(P[i],Ch[cnt-2]))<0)
         cnt--;
     Ch[cnt++]=P[i];
  }
  int vis=cnt;//前vis个点可以保证是凸包的一部分
  for(int i=N-2;i>=0;i--){
     while(cnt>vis&&Cross(P_to_V(Ch[cnt-1],Ch[cnt-2]),P_to_V(P[i],Ch[cnt-2]))<0)
         cnt--;
     Ch[cnt++]=P[i];
  }

	return 0;
}

上一篇:【2020.11.30提高组模拟】柱形图(histogram) 题解翻译


下一篇:#Python&密码学中的简单算法