计算几何学习笔记

前情提要

此内容大部分来自我的 PPT,如有不对还请指出。
因为是 PPT 盒来的,所以就懒得串联组织语言了,本来语文就差,QwQ。


目录

  1. 凸包
  2. 向量
  3. 旋转卡壳
  4. 闵可夫斯基和

凸包

  • 凸包:在平面上能包含所有给定点的最小凸多边形叫凸包。

  • 定义:对于给定集合 \(X\),所有包含 \(X\) 的集合的交集 \(S\) 被称为 \(X\)凸包

  • 性质:凸多边形在周长上一定是最优的。如下图,由三边关系可以得出凸多边形的周长是最小的。


  • 举一个凸包的例子:

  • 这个点集中凸包的点集就为\(\{1,6,8,9,5,2\}\),注意 \(3\) 并不在点集中,因为它和\(1,6\)组成的线段共线。

  • 算法:Graham 算法和 Andrew 算法,复杂度均为 \(O(n log n)\)

  • Andrew 算法:

  1. 将所有点按 \(x\) 为第一关键字,\(y\) 为第二关键字排序。且第 \(1\)\(n\) 号点一定在凸包上,因为他们为左下角和右上角的点。
  2. 顺序排序所有点求下凸包,用栈维护当前在凸包上的所有点,新点入栈前需弹出的旧点,如果新点在栈顶两点构成的有向直线的右侧或共线,则弹出旧点,直至无法弹出,加入新点。
  3. 逆序枚举所有点求上凸包,过程同上。
    注意:因为每个点入栈两次,出栈两次,所以总次数不超过 \(4n\)

向量

  • 可以理解为一条有方向的线段。
  • 给定三个点 \(A(x_1,y_1),B(x_2,y_2),C(x_3,y_3)\),向量 \(\overrightarrow{AB} = (x_2 - x_ 1,y_2 - y_1)\)\(\overrightarrow{BC} = (x_3 - x_2,y_3 - y_2)\)。定义他们的二维叉积为:

\[Cross = \overrightarrow{AB} \times \overrightarrow{BC} = (x_2 - x_1)(y_3 - y_2) - (y_2 - y_1)(x_3 - x_2) \]

\[或 Cross = |\overrightarrow{AB}||\overrightarrow{BC}|\sin(\theta) \]

如果 \(cross > 0\),逆时针方向旋转;
如果 \(cross < 0\),顺时针方向旋转;
如果 \(cross = 0\),三点共线。
在求下凸包时就是判断向量旋转方向,若是顺时针或共线就弹出,即 \(cross \le 0\),求上凸包同理。
这个东西的判断有个右手定则,可以自己去了解一下,我不会好吧。

再补充一些知识:


向量的模:

模也就是模长,为向量的长度,表示为 \(|\vec{a}|\)\(|\vec{a}| = \sqrt{x^2 + y^2}\)

相反向量:

两个向量大小相等,方向相反。\(\vec{a}\) 的相反向量为 \(-\vec{a}\)\(|\vec{a}| = |-\vec{a}|\)\(-\vec{a} = (-x,-y)\)

向量的加减:

\(\vec{a} = (x_1,y_1)\)\(\vec{b} = (x_2,y_2)\)\(\vec{a} + \vec{b} = (x_1 + x_2,y_1 + y_2)\)
\(\vec{a} - \vec{b} = \vec{a} + (-\vec{b}) = (x_1 + x_2,y_1 - y_2)\)

向量的数量积

也叫点积或内积,几何意义为一个向量在另一个向量上的投影再乘上第二个向量的模长。\(\vec{a}\)\(\vec{b}\) 的点积表示为 \(\vec{a} \times \vec{b}\)

\(\vec{a} \times \vec{b} = |\vec{a}||\vec{b}| \cos{\theta}\) 或者 \(\vec{a} \times \vec{b} = x_1x_2 + y_1y_2\)

如果两个向量同向(共线),那么它们的数量积为它们的模长之积。
如果两个向量夹角 \(< 90°\),那么它们的数量积为正。
如果两个向量夹角 \(= 90°\),那么它们的数量积为 \(0\),因为 \(\cos{90°} = 0\)
如果两个向量夹角 \(> 90°\),那么它们的数量积为负。
如果两个向量反向(共线),那么它们的数量积为它们的模长之积的相反数。

向量求点到直线距离:


叉积:


这里就还要用到叉积的一个意义,它代表的是两个向量由平行四边形法则围成的四边形面积,为我们看成算一半求围成的三角形面积是一样的,对于不同的旋转方向,也就是 corss 的正负,cross 的绝对值越大,面积也就也大。这样就可以求出点到直线的距离。

点积求点到点的距离:


点积:


因为它相当于是一条向量在另一条向量上的投影距离,所以都在以 \(\overrightarrow{P_iP_{i+1}}\) 的基础上的话,投影距离越大,两点间距离也就越远。这样就求出了点与点之间的距离。

Andrew 算法求解示意图


画的太丑了,别喷我,qwq。
实线表示栈中点连成的边,虚线表示被删的点连成的边。(只画了下凸包求解过程)

模板代码


struct point{
	double x,y;
}p[N],s[N];//p是读入的点,s是栈中的点

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

double dis(point a,point b){//距离 
	return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}

bool cmp(point a,point b){
	return (a.x!=b.x)?a.x<b.x:a.y<b.y;
}

double Andrew(){
	sort(p+1,p+n+1,cmp);
	for (int i=1;i<=n;i++){//下凸包 
		while (top>1&&cross(s[top-1],s[top],p[i])<=0)
			top--;
		s[++top]=p[i];
	}
	int t=top;
	for (int i=n-1;i>=1;i--){//上凸包 
		while (top>t&&cross(s[top-1],s[top],p[i])<=0)
			top--;
		s[++top]=p[i];
	}
	double res=0;//周长 
	for (int i=1;i<top;i++)
		res+=dis(s[i],s[i+1]);
	return res;
}

旋转卡壳:

对踵点对:

如果凸多边形上的两个点 \(p\)\(q\) 在两条平行切线上,那么它们就构成一对对踵点对。根据切线与凸多边形的相交方式分为一下三种:

  1. “点 - 点”对踵点对;
  2. “点 - 边”对踵点对;
  3. “边 - 边”对踵点对。

一个凸 \(n\) 边形的对踵点对最多有 \(\frac{3n}{2}\) 对。

其能解决的问题

旋转卡壳就是绕着凸多边形旋转一对平行切线,主要思想是用的“边 - 点”对踵点对。

下面这些可以不用看,没啥用。
它主要解决一下问题:

  • 计算距离
    1. 凸多边形直径
    2. 凸多边形宽度
    3. 凸多边形间最大距离
    4. 凸多边形间最小距离
  • 外接矩形
    1. 最小面积外接矩形
    2. 最小周长外接矩形
  • 三角剖分
    1. 洋葱三角剖分
    2. 螺旋三角剖分
    3. 四边形剖分
  • 凸多边形属性
    1. 合并凸包
    2. 找共切线
    3. 凸多边形交
    4. 临界切线

模板

void Andrew(){
	sort(p+1,p+n+1,cmp);
	for (int i=1;i<=n;i++){//下凸包 
		while (top>1&&cross(s[top-1],s[top],p[i])<=0)
			top--;
		s[++top]=p[i];
	}
	int t=top;
	for (int i=n-1;i>=1;i--){//上凸包 
		while (top>t&&cross(s[top-1],s[top],p[i]<=0))
			top--;
		s[++top]=p[i];
	}
	n=top-1;//凸包上的点数 
}

int rot_cal(){
	int res=0;
	for (int i=1,j=2;i<=n;i++){
		while (cross(s[i],s[i+1],s[j])<cross(s[i],s[i+1],s[j+1]))
			j=j%n+1;
		res=max(res,max(dis(s[i],s[j]),dis(s[i+1],s[j])));
	}
	return res;
}

闵可夫斯基和

这里我只讲凸包的闵可夫斯基和。
就是给你两个图形 \(A\)\(B\),定义它们的闵可夫斯基和 \(C = \{a + b | a \in A, b \in B\}\)
说通俗一点就是从原点向图形 \(A\) 内部的每一个点做向量,再将图形 \(B\) 沿每个向量移动,所有的最终位置的并便是闵可夫斯基和,这个具有交换律。

模板

void minkowski(point *p1,int n,point *p2,int m,point *s,int &tot){
	for (int i=1;i<=n;i++)//提取边向量 
		v1[i]=sub(p1[i+1],p1[i]);
	for (int i=1;i<=m;i++)
		v2[i]=sub(p2[i+1],p2[i]);
	tot=1;
	s[tot]=add(p1[1],p2[1]);
	int i=1,j=1;
	while (i<=n&&j<=m){
		tot++;
		if (cross(v1[i],v2[j])>=0){//v1转向v2为逆时针,v1极角更小 
			s[tot]=add(s[tot-1],v1[i]);
			i++;
		}
		else{
			s[tot]=add(s[tot-1],v2[j]);
			j++;
		}
	}
	for (;i<=n;i++){
		tot++;
		s[tot]=add(s[tot-1],v1[i]);
	}
	for (;j<=m;j++){
		tot++;
		s[tot]=add(s[tot-1],v2[j]);
	}
}
posted @ 2026-05-04 21:22  Morvian  阅读(31)  评论(1)    收藏  举报