ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

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

2020-11-24 18:05:44  阅读:239  来源: 互联网

标签:Point cdot Andrew 凸包 叉积 e1 向量 e2


文章目录

一.预备知识

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​构成的向量)的关系:
当代毕加索
如果是第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;
}

标签:Point,cdot,Andrew,凸包,叉积,e1,向量,e2
来源: https://blog.csdn.net/Qiuker_jl/article/details/109995504

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有