计算几何学(3)

点的内包:判断点是否在多边形内。

输入
    一个多边形点的序列
    问题数
    各个问题的点的信息
输出
    2代表在圈内
    1代表在圈上
    0代表在圈外

只要检查一p为端点且平行于x的射线与多边形g的边的相交次数,我们就能判断给定的点p是否内包与多边形g。对于构成多边形各边的线段g_{i}g_{i+1}

g_{i}-pg_{i+1}-p分别为向量a和向量b。点p是否位于g_{i}g_{i+1}上可通过ccw的方法检验,即检验a和b是否在同一直线上且方向相反。如果a和b外积大小为0且内积小于等于0,则点p位于g_{i}g_{i+1}上。

接着我们调整a,b的值使y小的为a,大的为b。在这个状态下,如果a和b外积的大小为正且a,b位于射线两侧,则可确定相交。

这里要注意设置边界条件,避免射线与g_{i}g_{i+1}端点相交时对结果造成影响。

//点的内包
int contains(Polygon g,Point p){
	int n=g.size();
	bool x=false;
	for(int i=0;i<n;i++){
		Vector a=g[i]-p,b=g[(i+1)%n]-p;
		if(abs(cross(a,b))<EPS&&dot(a,b)<EPS)	return 1;
		if(a.y>b.y)	swap(a,b);
		if(a.y<EPS&&b.y>EPS&&cross(a,b)>EPS)	x=!x; 
	}
	return (x?2:0);
} 

凸包:包含点集合P中所有点的最小凸多边形。

输入
    一系列的点序列
输出
    逆时针输出凸包

人们已经研究出了数个求凸包的算法。这里我们将要学习的是相对容易掌握的安德鲁算法(Andrew's Algorithm)。

  • 将给定集合的点按照x坐标升序排列。x相同时按照y坐标升序排列。
  • 按照下述流程创建凸包的上部
    • 将顺序后的点按照x坐标从的顺序加入凸包U。
    • 如果新加入的点使得U不再是多边形,则逆序删除之前加入的U的点,直到重新成为凸多边形。
    • 我们利用准备加入的点S[i]与在U中的倒数两个点来判断。如图所示过程。
  • 按照下述流程创建凸包的下部
    • 将顺序后的点按照x坐标从的顺序加入凸包U。
    • 如果新加入的点使得L不再是多边形,则逆序删除之前加入的L的点,直到重新成为凸多边形。
    • 我们利用准备加入的点S[i]与在L中的倒数两个点来判断。如图所示过程。
Polygon andrewScan(Polygon s){
	Polygon u,l;
	if(s.size()<3)
		return s;
	//以x,y为基准升序排序 
	sort(s.begin(),s.end());
	//将x的最小两个点放入
	u.push_back(s[0]);
	u.push_back(s[1]);
	//将最大的两个点放入
	l.push_back(s[s.size()-1]);
	l.push_back(s[s.size()-2]);
	
	//构建凸包上部
	for(int i=2;i<s.size();i++){
		for(int n=u.size();n>=2&&ccw(s[i],u[n-1],u[n-2])==CLOCKWISE;n--){
			u.pop_back();
		}
		u.push_back(s[i]);
	} 
	//构建凸包下部
	for(int i=s.size()-3;i>=0;i--){
		for(int n=l.size();n>=2&&ccw(s[i],l[n-1],l[n-2])==CLOCKWISE;n--){
			l.pop_back();
		}
		l.push_back(s[i]);
	}
	//顺时针方向生成凸包的点的序列 
	reverse(l.begin(),l.end());
	for(int i=u.size()-2;i>=1;i--)
		l.push_back(u[i]);	
	return l;
}

线段相交问题

给出n条平行于x轴或者y轴的线段,请输出其交点数

基本思路:

与轴平行的线段相交问题(曼哈顿几何)可以通过平面扫描(sweep)高效求解。平面扫描算法的思路是将一条与x轴(或者y轴)平行的直线向上(向右)平行移动,在移动过程中寻找交点。这条直线称为扫描线。

扫描线并不是按照固定的间隔逐行扫描,而是在每次遇到线段上的端点时停止移动,然后检查该位置上的线段交点数。因此我们需要先将输入的线段端点按照y值排序,让扫描线向y轴正方向移动。

再扫描过程中,算法会将扫描线穿过的垂直线段(与y轴平行)临时记录下来,等到扫描线与水平线段(平行于x轴)重叠时,检查在水平线段范围内的交点数。

为提高效率,我们可以应用二叉搜索树来保存扫描线穿过的垂直线段。

#define BOTTOM 0
#define LEFT 1
#define RIGHT 2
#define TOP 3

class EndPoint{
	public:
		Point p;
		int seg,st;//输入线段的ID和端点种类
		EndPoint(){
		} 
		EndPoint(Point p,int seg,int st):p(p),seg(seg),st(st){
		}
		
		bool operator < (const EndPoint &ep) const{
			//按y坐标升序排列
			if(p.y==ep.p.y){
				return st<ep.st;
			} 
			else{
				return p.y<ep.p.y;
			}
		}
};

EndPoint EP[2*100000];//端点列表

//直线相交问题
int manhattanIntersection(vector<Segment> S){
	int n=S.size();
	
	for(int i=0,k=0;i<n;i++){
		//调整端点p1,p2,保证左小右大
		if(S[i].p1.y==S[i].p2.y){
			if(S[i].p1.x>S[i].p2.x)
				swap(S[i].p1,S[i].p2);
		}else if(S[i].p1.y>S[i].p2.y)
			swap(S[i].p1,S[i].p2);
	
		if(S[i].p1.y==S[i].p2.y){
 			EP[k++]=EndPoint(S[i].p1,i,LEFT);
 			EP[k++]=EndPoint(S[i].p2,i,RIGHT);
	 	} 
	 	else{
	 		EP[k++]=EndPoint(S[i].p1,i,BOTTOM);
	 		EP[k++]=EndPoint(S[i].p2,i,TOP);
		 }
	}
	
	sort(EP,EP+(2*n));
	
	set<int>BT;
	BT.insert(1000000001);
	int cnt=0;
	
	for(int i=0;i<2*n;i++){
		if(EP[i].st==TOP)
			BT.erase(EP[i].p.x);//删除上端点 
		else if(EP[i].st==BOTTOM)
			BT.insert(EP[i].p.x);
		else if(EP[i].st==LEFT){
			set<int>::iterator b = BT.lower_bound(S[EP[i].seg].p1.x);
			set<int>::iterator e = BT.upper_bound(S[EP[i].seg].p2.x);
			cnt+=distance(b,e);
		} 
	}
	return cnt;
} 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值