C语言实现凸包Graham_scan算法

算法就不介绍了,其他地方应该也搜得到。

如何判断三点A, B, C连线是逆时针?

(1) 通过判断C在AB连线上方还是下方判断,但需要根据斜率的正负,A, B位置关系分类讨论,比较麻烦。
(2) 通过计算向量叉积的方法。
在一般的常识或者教科书中规定叉乘只有3维才拥有,其实2维也可以拓展出来一个叉乘形式。
拓展方式:假设有两个2维向量a,b,我们直接把他们视为3维向量,z轴补0,那么这个时候的a,b向量的叉乘结果c,c.x=0,c.y=0,c.z=a.x* b.y-b.x *a.y。
有这样的性质:设k是a,b向量的叉积,如果k>0时,那么a正旋转到b的角度为<180°,如果k<0,那么a正旋转到b的角度为>180°,如果k=0 那么a,b向量平行。

代码

#include<stdio.h>
#include<math.h>
#include<time.h>
#include<stdlib.h>
#define PI 3.14159
typedef struct Point {
	int x, y;
	double angle;
}Point;
int less(Point a, Point b) {		//定义点a小于点b法则(排序用)
	if (a.angle != b.angle) return a.angle < b.angle;
	else return a.x < b.x;
}
int ifAnticlockwise(Point a, Point b, Point c) {	//是否为逆时针
	int crossProduct = (b.x - a.x)*(c.y - b.y) - (b.y - a.y)*(c.x - b.x);	//求向量积
	return crossProduct >= 0;	//向量积为正表示逆时针
}
int main() {
	srand((unsigned)time(NULL));
	int n, minx, maxx, miny, maxy;
	scanf("%d%d%d%d%d", &n, &minx, &maxx, &miny, &maxy);
	Point point[105];
	for (int i = 0; i < n; i++) {	//生成随机坐标
		point[i].x = rand() % (maxx - minx + 1) + minx;
		point[i].y = rand() % (maxy - miny + 1) + miny;
	}
	double sx = 1000, sy = 1000;
	for (int i = 0; i < n; i++) {		//寻找起始点
		if (point[i].y < sy) sx = point[i].x, sy = point[i].y;
		else if (point[i].y == sy && point[i].x < sx) sx = point[i].x;
	}
	for (int i = 0; i < n; i++) {		//求夹角
		if (point[i].x == sx && point[i].y == sy) point[i].angle = 0;
		else if (point[i].x == sx) point[i].angle = 90;
		else {
			point[i].angle = atan((point[i].y - sy)*1.0 / (point[i].x - sx));
			if (point[i].angle < 0) point[i].angle = PI + point[i].angle;	
//转换为与x轴正方向夹角
		}
	}
	for (int i = 0; i < n - 1; i++) {
		for (int j = i + 1; j < n; j++) {	//冒泡排序
			if (less(point[j], point[i])) {
				Point temp = point[i];
				point[i] = point[j];
				point[j] = temp;
			}
		}
	}
	int cnt = 3;	//栈顶指针
	for (int i = 3; i < n; i++) {

		if (ifAnticlockwise(point[cnt - 2], point[cnt - 1], point[i])) {
			point[cnt++] = point[i];	//模拟入栈
		}
		else point[cnt - 1] = point[i];	//模拟出栈+入栈
	}
	for (int i = cnt - 1; i >= 0; i--) {	//逆序输出
		printf("%4d %4d    ", point[i].x, point[i].y);
	}
	return 0;
}

程序测试

为了验证程序的正确性,先缩小了样本,随机生成横纵坐标都在-10~10范围内的6个点,输出第一行为随机生成的6个点的横纵坐标,接着输出结果。将点在matlab中绘制,可以验证程序正确。

在这里插入图片描述在这里插入图片描述

  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
好的,以下是 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 类型。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值