Graham扫描法求解凸包问题(C++)

目录

凸包概念

算法思路

算法过程

1、准备数据

2、数据处理

2.1 获取左下角的点

2.2 坐标点移动至左下角点坐标系中

2.3 坐标点排序 

3、Graham算法

3.1 判断点与线(栈顶点和原点连线)的关系

扫描二维码关注公众号,回复: 16143399 查看本文章

3.2 计算夹角(顺逆时针旋转)

 3.3.算法主体

执行结果


凸包概念

1 点集Q的凸包(convex hull)是指一个最小凸多边形,满足Q中的点或者在多边形边上或者在其内。图1中由红色表示线段的多边形就是点集Q={p0,p1,...p12}的凸包。

2 一组平面上的点,求一个包含所有点的最小的凸多边形,这就是凸包问题了。这可以形象地想成这样:在地上放置一些不可移动的木桩,用一根绳子把他们尽量紧地圈起来,并且为凸边形,这就是凸包了。

算法思路

首先找到数组中位于左下角的点,并将其移动至数组首位;然后将所有点移动至以左下角点为坐标原点的坐标轴上;再对数组进行排序(按照与x轴夹角大小排序);对排序完成的数组进行计算,求解凸包。

(1)创建凸包节点栈,并将排序后数组中的第二个值入栈(第一个值为坐标原点,无需入栈),开始凸包计算。

(2)判断数组中下一个点是否在前一点与坐标原点连线的左侧或连线上。若不在,转至步骤(3)若在转至步骤(4)

(3)栈顶点不符合要求,需要移除,并重新执行步骤(2)

(4)判断当前点p与栈中顶部两个点p1(top点)p2(顶部第二个点)的关系。★(较难)

如果向量p2p1与向量pp1是逆时针旋转(即∠p2p1p为负角,以向量p2p1为正方向),或者点p、p1、p2三点连线;点入栈。

否则(顺时针旋转,正角),栈顶点p1出栈,p入栈。

(5)当遍历完数组所有值时,程序结束。栈中存储着凸包图形结点。

动态过程(源自数学:凸包算法详解 - 爱国呐 - 博客园

这里写图片描述

备注:以下结构体、函数均为自定义结构体及函数。

算法过程

1、准备数据

//默认的输入列表
Point* init()
{
	Point p1(1, 3);
	Point p2(2, 2);
	Point p3(4, 9);
	Point p4(3, 5);

	Point* plist = new Point[5];
	plist[0] = p1;
	plist[1] = p2;
	plist[2] = p3;
	plist[3] = p4;

	return plist;
}

2、数据处理

2.1 获取左下角的点

void getPole(Point* &plist, int size)
{
	for (int i = 0; i < size; i++)
	{
		if (plist[i].y < plist[0].y || (plist[i].y == plist[0].y && plist[i].x < plist[0].x))
			Swap(plist[i], plist[0]);
	}
}

2.2 坐标点移动至左下角点坐标系中

void DealData(Point* &plist, int size)
{
	Point p = plist[0];
	for (int i = 0; i < size; i++) {
		plist[i].x -= p.x;
		plist[i].y -= p.y;
	}
}

2.3 坐标点排序 

void Sort(Point* &plist, int size)
{
	for (int i = 0; i < size - 1; i++)
		for (int j = 0; j < size -i -1; j++) {
			if (Compare(plist[j], plist[j + 1]))//plist[i]<plist[i+1]
				Swap(plist[j], plist[j + 1]);
		}
}

3、Graham算法

3.1 判断点与线(栈顶点和原点连线)的关系

//点与线的关系 0 共线,负 点在向量(原点与栈顶点连线)的右侧,正 左侧
int PointLine(Point v1, Point v2) {
	return v1.x*v2.y - v1.y*v2.x;
}

3.2 计算夹角(顺逆时针旋转)

更新内容:计算夹角采用double类型计算

//向量叉乘
double cross(Point v1, Point v2) {
	return v1.x*v2.y - v1.y*v2.x;
}
//向量点乘
double multi(Point v1, Point v2) {
	return v1.x*v2.x + v1.y*v2.y;
}
//计算夹角
double getangle(Point p, Point p1, Point p2) {
	Point v1(p2.x - p1.x, p2.y - p1.y);
	Point v2(p.x - p1.x, p.y - p1.y);
	double theta = atan2((double)cross(v1, v2), (double)multi(v1, v2));
	return theta;
}

 3.3.算法主体

更新:算法小bug已修复

对栈顶1、2号元素与待入栈元素循环比较,直到三点夹角满足条件,再将点入栈

	//逆时针找数据
	stack<Point> stack;
	stack.push(pl[0]);
	stack.push(pl[1]);
	for (int i = 2; i < psize; i++) {
		//cout << "第" << i << "个点:" << " " << pl[i].x << " " << pl[i].y << endl;
		if (PointLine(stack.top(), pl[i]) < 0)//在栈顶点与原点连线的右边
		{
			//cout << "出栈:" << stack.top().x << " " << stack.top().y << endl;
			stack.pop();
			
			stack.push(pl[i]);
			//cout << "入栈:" << stack.top().x << " " << stack.top().y << endl;
		}
		else if(PointLine(stack.top(), pl[i]) == 0) {//点在线上
			stack.push(pl[i]);
			//cout << "入栈:" << stack.top().x << " " << stack.top().y << endl;
		}
		else {//点在线的左侧
            Point p1 = stack.top();
			stack.pop();
			Point p2 = stack.top();
			//判断向左拐存在问题
			//cout << "三点夹角:" << getangle(pl[i], p1, p2) << endl;
			if (getangle(pl[i], p1, p2) <= 0) {//负角,保留
				stack.push(p1);
				cout << pl[i].x << " " << pl[i].y << "进栈\n";
				stack.push(pl[i]);
			}
			else {//正角,舍去
				while (getangle(pl[i], p1, p2) > 0)//判断当前三点关系
				{
					//cout << p1.x << " " << p1.y << "出栈\n";
					p1 = p2;
					stack.pop();
					p2 = stack.top();
				}
				stack.push(p1);
				//cout << pl[i].x << " " << pl[i].y << "进栈\n";
				stack.push(pl[i]);
			}
		
		}		
	}

完整代码如下:

#include <vector>
#include <iostream>
using namespace std;
#include <stack>
#include <cmath>
//Graham扫描法求解凸包问题
struct Point {
	int x, y;
	Point(int x, int y) :x(x), y(y) {}
	Point() {}
};
//默认的输入列表
Point* init()
{

	Point p1(1, 3);
	Point p2(2, 2);
	Point p3(4, 9);
	Point p4(3, 5);

	Point* plist = new Point[5];
	plist[0] = p1;
	plist[1] = p2;
	plist[2] = p3;
	plist[3] = p4;

	return plist;
}
void Swap(Point &p1, Point &p2) {
	Point tmp = p2;
	p2 = p1;
	p1 = tmp;
}
//计算左下角点
void getPole(Point* &plist, int size)
{
	for (int i = 0; i < size; i++)
	{
		if (plist[i].y < plist[0].y || (plist[i].y == plist[0].y && plist[i].x < plist[0].x))
			Swap(plist[i], plist[0]);
	}
}
//比较a,b与x轴夹角的大小
bool Compare(Point a, Point b)
{
	double anglea =atan2( (double)a.y , (double)a.x);
	double angleb =atan2( (double)b.y , (double)b.x);
	if (anglea == angleb) {//共线条件比较
		int d1 = a.x*a.x + a.y*a.y;
		int d2 = b.x*b.x + b.y*b.y;
		return d1 > d2;
	}
	else
		return anglea > angleb;
}
//冒泡排序
void Sort(Point* &plist, int size)
{
    for (int i = 0; i < size - 1; i++)
		for (int j = 0; j < size - 1 - i; j++) {
			if (Compare(plist[j], plist[j + 1]))//plist[i]<plist[i+1]
				Swap(plist[j], plist[j + 1]);
		}
}
//坐标变换
void DealData(Point* &plist, int size)
{
	Point p = plist[0];
	for (int i = 0; i < size; i++) {
		plist[i].x -= p.x;
		plist[i].y -= p.y;
	}
}

int PointLine(Point v1, Point v2) {
	return v1.x*v2.y - v1.y*v2.x;
}
//判断是否共线
bool collinear(Point p1, Point p2, Point p3) {
	return (p1.x*p2.y + p2.x*p3.y + p3.x*p1.y - p1.x*p3.y - p2.x*p1.y - p3.x*p2.y) == 0;
}
double cross(Point v1, Point v2) {
	return v1.x*v2.y - v1.y*v2.x;
}
double multi(Point v1, Point v2) {
	return v1.x*v2.x + v1.y*v2.y;
}
//计算夹角
double getangle(Point p, Point p1, Point p2) {
	Point v1(p2.x - p1.x, p2.y - p1.y);
	Point v2(p.x - p1.x, p.y - p1.y);
	double theta = atan2((double)cross(v1, v2), (double)multi(v1, v2));
	return theta;
}
//算法主体
vector<Point> Graham(Point* pl, int psize) {
	getPole(pl, psize);//处理数据,极点至首位
	Point pole = pl[0];//坐标原点

	DealData(pl, psize);//将极点为坐标原点处理各点坐标

	Sort(pl, psize);//按极角排序
    stack<Point> stack;
	//逆时针找凸包结点
    stack.push(pl[0]);
	stack.push(pl[1]);
	for (int i = 2; i < psize; i++) {
		if (PointLine(stack.top(), pl[i]) < 0)//在栈顶点与原点连线的右边
		{
			stack.pop();
			stack.push(pl[i]);
		}
		else if(PointLine(stack.top(), pl[i]) == 0) {//点在线上
			stack.push(pl[i]);
		}
		else {
			Point p1 = stack.top();
			stack.pop();
			Point p2 = stack.top();
			if (getangle(pl[i], p1, p2) <= 0) {//负角,保留
				stack.push(p1);
				stack.push(pl[i]);
			}
			else {//正角,舍去
				while (getangle(pl[i], p1, p2) > 0)
				{
					p1 = p2;
					stack.pop();
					p2 = stack.top();
				}
				stack.push(p1);
				stack.push(pl[i]);
			}
		}		
	}

    int pnum = stack.size();
    //顺时针获取栈内元素
	vector<Point> plist;
	for (int i = 0; i < pnum; i++)
	{
		plist.push_back(Point(stack.top().x + pole.x, stack.top().y + pole.y));
		stack.pop();
	}

	return plist;
}

int main()
{
	Point* plist = init();
    //执行算法
	vector<Point> pl = Graham(plist, 4);//输入点的列表、点的个数

	cout << "最终结果为:\n";
	for (int i = 0; i < pl.size(); i++) {
		cout << "第" << i << "个点:" << pl.at(i).x << " " << pl.at(i).y << endl;
	}

	return 0;
}

执行结果

 算法参考:

凸包算法(Graham扫描法)_よろしくお願いします-CSDN博客_graham扫描法

​​​​​​凸包算法(Graham扫描法)详解 - 踩在浪花上 - 博客园

猜你喜欢

转载自blog.csdn.net/qq_45929977/article/details/122136533