线段交点计算与凸包的简单实现

计算几何学习笔记(一)

参考文献:计算几何算法与应用(第三版)

1、前言

1.1 点积:

A ⃗ B ⃗ = ∣ A ⃗ ∣ ∣ B ⃗ ∣ c o s < A ⃗ , B ⃗ > = x a y a + x b y b \vec{A}\vec{B}=|\vec{A}||\vec{B}|cos<\vec{A},\vec{B}>=x_ay_a+x_by_b A B =A B cos<A ,B >=xaya+xbyb
几何意义:A在B所在方向的投影的模长和B的模长的乘积,可判断AB是否垂直

1.2 叉积:

A ⃗ × B ⃗ = ∣ A ⃗ ∣ ∣ B ⃗ ∣ s i n < A ⃗ , B ⃗ > = x a y b − x b y a \vec{A}×\vec{B}=|\vec{A}||\vec{B}|sin<\vec{A},\vec{B}>=x_ay_b-x_by_a A ×B =A B sin<A ,B >=xaybxbya
几何意义:AB为边构成的平行四边形的有向面积,判断A、B方向,是否共线
a×b>0——逆时针;
a×b=0——共线;
a×b<0——顺时针。

例如:相对于由p和q两点确定的一条有向直线, 某点r究竟是位于其左侧还是右侧。 令p = (px, py),q = (qx, qy),r = (rx, ry)。可根据叉积 p q ⃗ × p r ⃗ \vec{pq}×\vec{pr} pq ×pr 也即 ( D = ∣ 1 p x p y 1 q x q y 1 r x r y ∣ ) \bigl (D=\begin{vmatrix} 1&px& py\\ 1&qx & qy\\ 1&rx&ry \end{vmatrix}\bigr) (D=111pxqxrxpyqyry)
若D>0,逆时针方向,r点在左侧;
若D=0,三点共线;
若D<0,顺时针方向,r点在右侧;

1.3 线段AB、CD的交点P

以直线AB为准,根据叉积判断,若C、D两点在线段两侧,则存在交点P。之后根据交点P是否在线段上,分情况求出交点坐标。

  1. 判断是否相交:若 A B ⃗ × C D ⃗ = 0 \vec{AB}×\vec{CD}=0 AB ×CD =0则线段不相交
  2. 交点是否在线段上
    (1)若 ( A B ⃗ × A C ⃗ ) ⋅ ( A B ⃗ × A D ⃗ ) > 0 (\vec{AB}×\vec{AC})\cdot(\vec{AB}×\vec{AD})>0 (AB ×AC )(AB ×AD )>0则交点不在线段上;
    (2)若 ( A B ⃗ × A C ⃗ ) ⋅ ( A B ⃗ × A D ⃗ ) ≤ 0 (\vec{AB}×\vec{AC})\cdot(\vec{AB}×\vec{AD})\leq0 (AB ×AC )(AB ×AD )0则交点在线段上(为0说明交点为某端点)。
  3. 求交点坐标
    (1)交点不在线段上:
    P ⃗ = A ⃗ + 1 ( 1 − α ) A ⃗ B ⃗ \vec{P} =\vec{A}+\frac{1}{(1- \alpha) }\vec{A}\vec{B} P =A +(1α)1A B
    其中 α = A B ⃗ × A D ⃗ A B ⃗ × A C ⃗ \alpha=\frac{\vec{AB}×\vec{AD}}{\vec{AB}×\vec{AC}} α=AB ×AC AB ×AD
    (2)交点在线段上:
    P ⃗ = A ⃗ + 1 ( 1 + α ) A B ⃗ \vec{P} =\vec{A}+\frac{1}{(1+ \alpha) }\vec{AB} P =A +(1+α)1AB
    其中 α = C D ⃗ × C B ⃗ C D ⃗ × C A ⃗ \alpha=\frac{\vec{CD}\times\vec{CB}}{\vec{CD}\times\vec{CA}} α=CD ×CA CD ×CB

2、凸包

2.1 问题分析

面对具有几何本质的算法问题,我们所采用的解决方法大多需要具备两方面要素:一是对该问题的几何特性的深刻理解,二是算法和数据结构的合理应用。
平面凸包的计算是一个比较常见的平面几何问题。若 ∃ S \exists S S ∀ p , q ∈ S \forall p ,q\in S p,qS,有线段 p q ‾ ∈ S \overline{pq}\in S pqS则称S为凸集。
凸集与非图集
问题描述:给定平面点集 P = { p1, …, pn },通过计算从 P 中选出若干点,它们沿顺时针方向依次对应于 CH( P)的各个顶点。
input = 平面上一组点:p1, p2, p3, p4, p5, p6, p7, p8, p9
output = 凸包的表示:p4, p5, p8, p2, p9
计算凸包
对于CH( P)边界上任一边所在的直线,P中所有的点均居于同侧。为降低时间复杂度,我们采取递增式策略,构建一个递增式算法,逐步引入P中各点,每引入一个点都更新目前的解,直至遍历所有点。
具体方案:沿顺时针方向,从左至右找出上方边界各点构成上凸包;然后自右向左进行扫描找出下方边界各点构成下凸包。
计算上下凸包
判断条件:引入新点时保证每次都是右拐,否则剔除中间点,直至满足条件或只剩两个点。

2.2 具体实现

计算凸包算法伪代码:

/************************************
> 算法 CONVEXHULL( P)  
> 输入:平面点集P  
> 输出:由CH(P )的所有顶点沿顺时针方向组成的一个列表 
1. 根据x-坐标,对所有点进行排序,得到序列p1, …, pn 
2. 在Lupper中加入p1和p2(p1在前) 
3.  for (i← 3 to n) 
4.  	do 在Lupper中加入pi 
5.  		while (Lupper中至少还有三个点,而且最末尾的三个点所构成的不是一个右拐) 
6.     			do 将倒数第二个顶点从Lupper中删去 
7.  在Llower中加入pn和pn-1(pn在前) 
8.  for (i← n-2 downto 1) 
9.  	do 在Llower中加入pi 
10. 		while (Llower中至少还有三个点,而且最末尾的三个点所构成的不是一个右拐) 
11.    			do 将倒数第二个顶点从Llower中删去 
12. 将第一个和最后一个点从Llower中删去  (以免在上凸包与下凸包联接之后,出现重复顶点) 
13. 将Llower联接到Lupper后面(将由此得到的列表记为L) 
14. return(L)
***********************/

整个算法的时间复杂度取决于第一步的排序即O(nlogn)。因此,给定包含 n 个点的任意一个平面点集,其凸包都可以在 O(nlogn)时间内构造出来。
退化情况:当出现三点共线时,要保证严格右拐,将共线视为左拐舍弃。

bool Judge_right_turn(POINT &p1, POINT &p2, POINT &p3)
{
	int flag1 = (p2.x - p1.x)*(p3.y - p1.y) - (p3.x - p1.x)*(p2.y - p1.y);		//求向量×积
	if (flag1 >= 0)
		return false;
	
	return true;
}

本文使用C++的vector容器实现如下:

using pt_List = vector < POINT >;

pt_List CONVEXHULL(pt_List &list0)
{
	pt_List upper;
	pt_List lower;
	int list0_num = list0.size();
	//排序
	sort(list0.begin(), list0.end(), Compare_point);
	//先求上凸包	
	upper.push_back(list0.at(0));		//索引添加元素
	upper.push_back(list0.at(1));
	//递增策略,逐个添加点pi	
	for (int i = 2; i < list0_num;i++)
	{
		POINT pi = list0[i];
		while (true)
		{
			auto iter = upper.end();
			--iter;
			POINT p2 = *iter;
			--iter;
			POINT p1 = *iter;
			//判断pi是否构成右拐,是添加pi,否删除中间点,继续判断
			if (Judge_right_turn(p1, p2, pi))
			{
				upper.push_back(pi);				
				break;
			}
			else
			{
				upper.pop_back();						
				if (upper.size() == 1)
				{
					upper.push_back(pi);
					break;
				}
			}
		}
	}

	//求下凸包	
	lower.push_back(list0.at(list0_num-1));
	lower.push_back(list0.at(list0_num - 2));

	for (int i = list0_num - 2; i > 0;i--)
	{
		POINT pi = list0[i-1];		
		while (true)
		{
			auto iter = lower.end();
			--iter;
			POINT p2 = *iter;
			--iter;
			POINT p1 = *iter;
			//判断pi是否构成右拐,是添加pi,否删除中间点,继续判断
			if (Judge_right_turn(p1, p2, pi))
			{
				lower.push_back(pi);
				break;
			}
			else
			{
				lower.pop_back();
				if (lower.size() == 1)
				{
					lower.push_back(pi);
					break;
				}
			}
		}
	}

	//连接上下凸包
	upper.pop_back();
	lower.pop_back();
	upper.insert(end(upper), begin(lower), end(lower));
	return upper;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值