凸包算法求解(C++)

老师刚刚布置了个作业,让用quicksort实现凸包。但是我突然有一丝灵感,所以就写下我的想法,希望大家可以提出问题和改进,感谢。

我们知道Graham扫描法实现凸包是先找到纵坐标最小的点,基于这个想法我做了一些改进,也就是说Graham扫描法只是从一个方向发散出去,我们为何不从四个方向同时发散。也即找到x,y轴坐标最大或最小的四个点(也有可能是两个或者三个,这个先不讨论),以这四个点为基点,平面内的所有点必然都在由这四个点延申构成的矩形内

如左图,红色点就是我们所选择的基点,右图就是做完延伸线之后所形成的矩形,可以很简单的证明所有的点都在该矩形内。

接下来,连接这4个基点,可以形成四条线,如下图(为了方便讲述,我把D点平移了一下)

接下来我们就可以向外拓展了,以向右上方扫描为例,以AB为基线向右上方扫描,找到距离AB最远的一个点D,连接AD,DB,再以这两条线为基线,继续向外扫描,直到直线的外侧没有点为止,此时即构成凸包的一侧的边。

第一次:

第二次:

如图,凸包的右上边已经找出来了 :)

其他三个方向也是如上步骤。

接下来就是具体的实现

struct coordinate{
	float x;
	float y;
}; 

首先定义结构体coordinate存放点的坐标

float equation(coordinate s1,coordinate s2,coordinate s3){
	float k=(s1.y-s2.y)/(s1.x-s2.x);
	float c=(s1.y*s2.x-s2.y*s1.x)/(s2.x-s1.x);
	float result=abs(k*s3.x-s3.y);
	return result;
}

计算点s3到由s1,s2组成的直线的距离(  上面的result返回的不是真正的距离,因为+c和除以根号下k^2+1都是常数运算,对大小没有影响)

我这里对扫描的实现是将所有的点遍历一遍,然后得到一个小范围内的点,再计算这个范围内点的叉乘,以此来判断这个点是不是我们需要的最远点,代码如下(这里我只能想出这一个办法,如果大家有其他想法的话欢迎在评论下分享)

void findNE(coordinate s1,coordinate s2){
	float x_min,x_max,y_min,y_max;
	if(s1.x<s2.x){
		x_min=s1.x;
		y_max=s1.y;
		x_max=s2.x;
		y_min=s2.y;
	}else{
		x_min=s2.x;
		y_max=s2.y;
		x_max=s1.x;
		y_min=s1.y;
	}
	int count=0;
	for(int i=0;i<MAX;i++){
		if(point[i].x>x_min&&point[i].x<x_max&&point[i].y>y_min&&point[i].y<y_max){
			if(s1.x*s2.y+point[i].x*s1.y+s2.x*point[i].y-point[i].x*s2.y-s2.x*s1.y-s1.x*point[i].y<0){
			//计算叉乘,>0在左边,<0在右边 
				dataNE[count]=point[i];
				count++;
			} 
			
		}
	}
	if(count==0){
		cout<<"右上边已完成"<<endl;
	}else{
		int max_d=0;
		for(int i=0;i<count;i++){
       //dataNE是coordinate类型的数组,dis_data是用来存放距离的float型数组
			dis_data[i]=equation(s1,s2,dataNE[i]);
			if(dis_data[i]>max_d){
				max_d=dis_data[i];
				numb_NE=i;
			}
				
		}
		finalData[index]=dataNE[numb_NE];
//finalData是coordinate型的数组,用来存放最终的凸包边界点的坐标
		index++;
		findNE(s1,dataNE[numb_NE]);
		findNE(dataNE[numb_NE],s2);
	}

}

-----------------------------------

我突然发现这个跟分治法有点像,但是还是有点参考价值吧 T_T

  • 7
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
好的,以下是 Graham-Scan 算法的 C++ 实现,用于求解问题: ```cpp #include <bits/stdc++.h> using namespace std; struct Point { int x, y; }; // 按照 x 坐标从小到大排序,若 x 坐标相等,则按照 y 坐标从小到大排序。 bool cmp(Point a, Point b) { if (a.x == b.x) return a.y < b.y; return a.x < b.x; } // 计算叉积。 int cross(Point a, Point b, Point c) { return (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x); } // Graham-Scan 算法求解。 vector<Point> grahamScan(vector<Point> &points) { int n = points.size(); if (n <= 1) return points; sort(points.begin(), points.end(), cmp); vector<Point> hull(2 * n); int k = 0; // 构建下壳。 for (int i = 0; i < n; ++i) { while (k >= 2 && cross(hull[k - 2], hull[k - 1], points[i]) <= 0) k--; hull[k++] = points[i]; } // 构建上壳。 for (int i = n - 2, t = k + 1; i >= 0; --i) { while (k >= t && cross(hull[k - 2], hull[k - 1], points[i]) <= 0) k--; hull[k++] = points[i]; } // 去除重复点。 hull.resize(k - 1); return hull; } int main() { // 测试数据。 vector<Point> points = {{0, 3}, {1, 1}, {2, 2}, {4, 4}, {0, 0}, {1, 2}, {3, 1}, {3, 3}}; vector<Point> hull = grahamScan(points); // 输出的顶点。 for (int i = 0; i < hull.size(); ++i) { cout << "(" << hull[i].x << ", " << hull[i].y << ")" << endl; } return 0; } ``` 注意点: 1. 为了方便起见,我直接使用了 C++11 的新特性,使用 vector 存储点集,如果你使用的是较老的编译器,可以使用数组代替 vector。 2. 实现中为了方便起见,我使用了三个点 $A(a_x,a_y)$、$B(b_x,b_y)$、$C(c_x,c_y)$ 的叉积 $cross(A,B,C)$ 表示向量 $\vec{AB}$ 和 $\vec{AC}$ 的叉积。当叉积 $cross(A,B,C)>0$ 时,表示 $\vec{AB}$ 在 $\vec{AC}$ 的逆时针方向;当叉积 $cross(A,B,C)<0$ 时,表示 $\vec{AB}$ 在 $\vec{AC}$ 的顺时针方向;当叉积 $cross(A,B,C)=0$ 时,表示 $\vec{AB}$ 和 $\vec{AC}$ 共线。 3. 为了避免精度误差,最好使用整数类型存储坐标,如 int 类型。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值