震惊!叉积竟有如此多用法

叉积的实际运用

数据结构的组织

为了方便接下来更好的组织数据,构建如下数据结构

#define eps 1e-8   //方便后面精度的计算
struct point{
	double x,y;
}
struct segment{
    point seg[2];
}

矢量的概念:

如果一条线段的端点是有次序之分的,我们把这种线段成为有向线段(directed segment)。如果有向线段p1p2的起点p1在坐标原点,我们可以把它称为矢量(vector)p2。

矢量加减法及距离公式:

设二维矢量P(x1,y1),Q(x2,y2),则矢量加分定义为:P+Q=(x1+x2,y1+y2),同样有矢量减法就不赘述了。并且有满足交换律,和结合律,分配律。
距离公式:
假设两点P(x1,y1),Q(x2,y2),其距离为sqrt((x1-x2)(x1-x2)+(y1-y2)(y1-y2)

double dist(point p,point q){
	return sqrt((x1-x2)*(x1-x2)-(y1-y2)*(y1-y2));
}

矢量叉积

计算叉积的作用有非常多,是直线和线段的核心算法。设P(x1,y1),Q(x2,y2),则其叉积为P×Q=x1y2-x2y1。

double cross(point a,point b){
	return a.x*b.y-a.y*b.x;
}

叉积具有一个重要性质是可以通过它的符号判断两个矢量相互之间的顺逆时针关系:
若P×Q>0 则P在Q的顺时针方向
若P×Q<0 则P在Q的逆时针方向
若P×Q=0 则P与Q共线,但有可能为同向或反向

矢量叉积可以看作原点,P点,Q点三点,因此可以推广成任意的3个点来判断它们之间的关系。
假设P(x1,y1),Q(x2,y2),R(x3,y3)三点,
将P和Q连接,对P,Q,R三点进行叉积即:
K=x1y2-x2y1+x2y3-x3y2+x3y1-x1y3
若K>0,其意义为在P点,面向Q点,R在PQ线段的左边
若K<0,其意义为在P点,面向Q点,R在PQ线段的右边
若K=0,其意义为在P点,面向Q点,R在PQ线段上

int mul(point a,point b,point c){
	double k=x1*y2-x2*y1+x2*y3-x3*y2+x3*y1-x1*y3;
	if(k>eps)return 1;
	else if(k<-eps)return -1;
	else return 0;
}

这个知识点在后面有奇用。

判断点是否在矩形中

只要判断该点的横坐标和纵坐标是否夹在矩形的左右边和上下边之间。
但如果点不规则就不好判断,那么我们又可以用到叉积:判断一个点是否在两条线段之间夹着就转化成,判断一个点是否在某条线段的一边上,就可以利用叉乘的方向性,来判断夹角是否超过了180度 在这里插入图片描述此图来自[https://www.cnblogs.com/fangsmile/p/9306510.html]
设E为任意点,ABCD为矩形
那么我们只要判断(AB×AE)(CD×CE)>=0并且(DA×DE)(BC×BE)>=0。

struct Rec{
	point a,b,c,d;
}
bool pointisinRectangle(point e,Rec r){
if(mul(r.a,r.b,r.e)*mul(r.c,r.d,e)>=0&&mul(r.d,r.a,e)*mul(r.b,r.c,e)>=0)return true;
	else return false;
}

判断矩形是否在矩形中

只要判断两个矩形的左右边界以及上下边界即可

判断圆是否在矩形中

充要条件是:圆心在矩形中并且圆的半径小于等于圆心到矩形距离的最小值。
那么点到各线段的距离为:
在这里插入图片描述

struct Rec{
	point a[4];
}
double dist(point R,point a,point b){
	double A=(a.y-b.y)/(a.x-b.x),B=1,C=a.y-(a.y-b.y)/(a.x-b.x);
	double d=fabs((A*R.x+B*R.y+C)/sqrt(A*A+B*B);
}
//其中r为圆的半径,R为圆心点,j为矩形
bool isinRectangle(double r,point R,Rec j){
	if(!pointisinRectangle(R,j))return false;
	double min=1e18;
	for(int i=0;i<4;i++){    //遍历各个点求各线段到圆心的距离
 		min=min>dist(R,j.a[i],j.a[(i+1)%4]);
	}
	if(r<min)return true;
	else return false;
}

判断线段是否与直线相交

假设有四个点P(x1,y1),Q(x2,y2),S(x3,y3),R(x4,y4),要判断PQ与SR是否相交,一般来说有两种可行的方法。
1.快速排斥法:PQ,SR分别作为对角线做 R1,R2两个矩形,如果两个矩形相交那么两线段即相交。
2.叉积判断法:如果两个线段相交,那么S,R点必须分别在PQ线段的两侧或者在PQ上,并且P,Q点也必须满足在SR线段的两侧或者在SR上,那么我们就可以运用上面的叉积知识了。

bool isintersectant(segment a,segment b){
	if(mul(a.seg[0],a.seg[1],b.seg[0])*mul(a.seg[0],a.seg[1],b.seg[1]<=0&&mul(b.seg[0],b.seg[1],a.seg[0])*(b.seg[0],b.seg[1],a.seg[1])<=0)
	return true;
	else return false;
} 

计算任意多边形的面积

众所周知,对于一个三角形的三个顶点,它们的叉积的绝对值/2即为三角形的面积,那么也可以推广到n边形的面积:

double square(int n,point p[]){
	double k=0;
	for(int i=0;i<n;i++){
		k+=p[i].x*p[(i+1)%n].y-p[i].y*p[(i+1)%n].x;
	}
	return k;
}

凸包

点集Q的凸包(convex hull)是指一个最小凸多边形,满足Q中的点或者在多边形边上或者在其内。下图中由红色线段表示的多边形就是点集Q={p0,p1,…p12}的凸包
在这里插入图片描述
凸包的求法:
凸包算法的时间复杂度O(n*logn)
其算法过程如下:
先寻找y坐标最小的点,如果有y坐标相同的,则取x偏小的点作为起始点。
设<p1,p2,…pm>为对其余点按以p0为中心的极角逆时针排序所得的点集(如果有多个点有相同的极角,除了距p0最远的点外全部移除
  压p0进栈S
  压p1进栈S
  压p2进栈S
for i ← 3 to m
do while 由S的栈顶元素的下一个元素、S的栈顶元素以及pi构成的折线段不拐向左侧
对S弹栈
压pi进栈S
return S;

伪代码可能对于一些刚入门的朋友不大友好,附上源代码:

#include <iostream>
#include <algorithm>
#include <math.h> 
#define eps 1e-8
using namespace std;
struct point{
	double x,y;
};
point p[10005];
int ord[10005];
double multi(point p1,point p2,point p0){  //求叉积
	double x1=p1.x-p0.x;
    double y1=p1.y-p0.y; 
    double x2=p2.x-p0.x;
    double y2=p2.y-p0.y;
    return(x1*y2-x2*y1); 
}
double dist(point a,point b){    //求距离
	return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}
int mul(point a,point b,point c){   //这个不用多说了把 前面用了不知道多是次了
	double k=a.x*b.y-a.y*b.x+b.x*c.y-b.y*c.x+c.x*a.y-c.y*a.x;
	if(k>eps)return 1;else
	if(k<-eps)return -1;else return 0; 
}
bool cmp(int a,int b){    //为了避免结构体内部大规模的移动,将各个索引进行移动,用空间换时间
	int i=mul(p[ord[0]],p[a],p[b]);
	if(i==0)return (dist(p[ord[0]],p[a])<dist(p[ord[0]],p[b]));
	else return i>0; 
}
int main(){
	int n,f,j=2;
	double miny=1e18;
	cin>>n;
	for(int i=0;i<n;i++){   //输入并查找起始点
		cin>>p[i].x>>p[i].y;
		ord[i]=i;
		if(p[i].y<miny){
			miny=p[i].y;
		    f=i;	
		}else if(p[i].y==miny&&p[i].x<p[f].x){
		    f=i;
		}
	}
	swap(ord[0],ord[f]);    //将所找到的起始点索引放到数组最前端
	if(n==2){      //当只有两个点的时候形成不了凸包,但可以直接输出
		if(ord[0]==0)cout<<0<<" "<<1;
		else cout<<1<<" "<<0;
	}else{ 
		sort(ord+1,ord+n,cmp);   //将映射点根据以p0为中心的极角逆时针排序
		int *stack=new int[10005];
		int top=0;
		stack[0]=ord[0]; 
		for(int i=1;i<n;i++){         //这边伪代码都有讲到,可以对照看看
			while(top>0&&mul(p[stack[top-1]],p[stack[top]],p[ord[i]])<=0)top--;
			stack[++top]=ord[i];
		}
		for(int i=0;i<=top;i++){
			cout<<stack[i]<<" ";
		}
	}

代码的作用为输出各个凸包最小多边形的顶点的索引。

最后

突发奇想记录下最近学的几何知识,叉积的用法真是玄学,祝大家学业有成,万事如意!!!

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ecnuorg

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值