Graham scanning method to solve convex hull problem (C++)

Table of contents

Convex hull concept

Algorithm idea

Algorithm process

1. Prepare data

2. Data processing

2.1 Get the point in the lower left corner

2.2 Move the coordinate point to the lower left point coordinate system

2.3 Sorting of coordinate points 

3. Graham algorithm

3.1 Determine the relationship between the point and the line (connecting the stack vertex and the origin)

3.2 Calculate the included angle (rotate clockwise)

 3.3. Algorithm subject

Results of the


Convex hull concept

1 The convex hull of a point set Q refers to a minimum convex polygon that satisfies the points in Q either on the side of the polygon or within it. The polygon represented by the line segment in red in Figure 1 is the convex hull of the point set Q={p0,p1,...p12}.

2 A group of points on the plane, find a minimum convex polygon containing all points, this is the convex hull problem. This can be visualized like this: place some immovable wooden stakes on the ground, use a rope to circle them as tightly as possible, and have a convex shape, which is the convex hull.

Algorithm idea

First find the point in the lower left corner of the array and move it to the first position of the array; then move all the points to the coordinate axis with the lower left corner point as the coordinate origin; then sort the array (according to the size of the angle with the x-axis ); Calculate the sorted array and solve the convex hull.

(1) Create a convex hull node stack, and push the second value in the sorted array into the stack (the first value is the origin of the coordinates, no need to push it into the stack), and start the convex hull calculation.

(2) Determine whether the next point in the array is on the left or on the line connecting the previous point and the coordinate origin. If not, go to step (3) If yes, go to step (4)

(3) The stack vertex does not meet the requirements and needs to be removed, and step (2) should be performed again

(4) Determine the relationship between the current point p and the top two points p1 (top point) p2 (top second point) in the stack. ★(difficult)

If the vector p2p1 and the vector pp1 rotate counterclockwise (that is, ∠p2p1p is a negative angle, and the vector p2p1 is a positive direction), or the point p, p1, and p2 are connected by a three-point line; click on the stack.

Otherwise (clockwise rotation, positive angle), the stack vertex p1 is popped out, and p is pushed into the stack.

(5) When all values ​​in the array are traversed, the program ends. The convex hull graph nodes are stored in the stack.

Dynamic process (from Mathematics: Detailed Explanation of Convex Hull Algorithm - Patriotic Na - Blog Park )

Write picture description here

Remarks: The following structures and functions are custom structures and functions.

Algorithm process

1. Prepare data

//默认的输入列表
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. Data processing

2.1 Get the point in the lower left corner

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 Move the coordinate point to the lower left point coordinate system

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 Sorting of coordinate points 

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 algorithm

3.1 Determine the relationship between the point and the line (connecting the stack vertex and the origin)

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

3.2 Calculate the included angle (rotate clockwise)

Update content: The included angle is calculated using the double type

//向量叉乘
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. Algorithm subject

Update: Algorithm minor bug has been fixed

Circularly compare the 1st and 2nd elements on the top of the stack with the elements to be pushed into the stack until the angle between the three points meets the conditions, and then push the point into the stack

	//逆时针找数据
	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]);
			}
		
		}		
	}

The complete code is as follows:

#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;
}

Results of the

 Algorithm reference:

Convex hull method (Graham's drawing method)_Thank you-CSDN 博客_graham's drawing method

​​​​​​Convex Hull Algorithm (Graham Scanning Method) Detailed Explanation - Stepping on the Waves - 博客园

Guess you like

Origin blog.csdn.net/qq_45929977/article/details/122136533