1. 程式人生 > 實用技巧 >凸包問題

凸包問題

(1)問題描述

用分分治法編寫一個求解凸包問題的演算法,並測試演算法的正確性。
【注:凸包問題】給定平面上n個點,從中找出一個最小點集,使該點集所組成的凸多邊形包圍所有的n個點。

(2)問題分析

將n個點分為兩部分,則每一部分可以形成一個凸包,重複分下去最後將多個凸包合併即可得到n個點的凸包。

(3)演算法設計

將n個點按橫座標從小到大排列,則x1,xn必定屬於最小點集,x1,xn連線將n個點分成兩部分,下面只需找各自的凸包即可。以找上凸包為例,在上半部分的點中找到距離x1,xn連線最遠的點,將該點與x1,xn相連則可將上半部分分為3塊,則找到上凸包的問題又轉化為找出另外兩個凸包的問題。上凸包和下凸包要分開處理。遞迴結束的條件是連線的上方(上凸包)/下方(下凸包)沒有點。

重複此操作即可得到最後整個的凸包。

(4)演算法實現

#include<iostream>
#include<vector>
#include<algorithm>
#include<cmath>
#include<fstream>
#include<sstream>

using namespace std;

struct Point {
	double x;
	double y;
};
inline bool Compx(const Point &p1, const Point &p2)
{
	return p1.x < p2.x;
}
/*
函式:求點p在以p1和p2決定直線的上側還是下側
返回值:上側:>0    下側:<0    在直線上:=0
*/
inline double Line(const Point &p1, const Point &p2, const Point &p)
{
	return (p1.y - p2.y) * p.x + (p2.x - p1.x) * p.y + (p1.x * p2.y - p1.y * p2.x);
}
/*
函式:求點p到以p1和p2決定直線的距離
*/
double Dist(const Point &p1, const Point &p2, const Point &p)
{
	double A = p1.y - p2.y;
	double B = p2.x - p1.x;
	double C = p1.x * p2.y - p1.y * p2.x;
	return abs(A*p.x + B * p.y + C) / sqrt(A*A + B * B);
}
/*
函式:求解直線p1p2上點集的上包
引數:v:直線p1p2上方的點集 vo:上包點集
*/
void UpHull(const vector<Point> &v, vector<Point> &vo, const Point &p1, const Point &p2)
{
	if (v.size() == 0)
		return;
	if (v.size() == 1) {
		vo.push_back(v[0]);
		return;
	}

	double d = 0;
	int k;
	for (size_t i = 0; i < v.size(); ++i) {
		double t = Dist(p1, p2, v[i]);
		if (t > d) {
			d = t;
			k = i;
		}
	}
	vo.push_back(v[k]);
	vector<Point> vl;
	vector<Point> vr;
	for (size_t i = 0; i < v.size(); ++i) {
		if (Line(p1, v[k], v[i]) > 0)
			vl.push_back(v[i]);
		else if (Line(v[k], p2, v[i]) > 0)
			vr.push_back(v[i]);
	}

	UpHull(vl, vo, p1, v[k]);
	UpHull(vr, vo, v[k], p2);
}
/*
函式:求解直線p1p2下點集的下包
引數:v:直線p1p2下的點集 vo:下包點集
*/
void DownHull(const vector<Point> &v, vector<Point> &vo, const Point &p1, const Point &p2)
{
	if (v.size() == 0)
		return;
	if (v.size() == 1) {
		vo.push_back(v[0]);
		return;
	}

	double d = 0;
	int k;
	for (size_t i = 0; i < v.size(); ++i) {
		double t = Dist(p1, p2, v[i]);
		if (t > d) {
			d = t;
			k = i;
		}
	}
	vo.push_back(v[k]);
	vector<Point> vl;
	vector<Point> vr;
	for (size_t i = 0; i < v.size(); ++i) {
		if (Line(p1, v[k], v[i]) < 0)
			vl.push_back(v[i]);
		else if (Line(v[k], p2, v[i]) < 0)
			vr.push_back(v[i]);
	}

	DownHull(vl, vo, p1, v[k]);
	DownHull(vr, vo, v[k], p2);
}
/*
函式:求解點集v的凸包
*/
void ConvexHull(vector<Point> &v, vector<Point> &vo)
{
	sort(v.begin(), v.end(), Compx);
	vo.push_back(v[0]);
	vo.push_back(v[v.size() - 1]);
	vector<Point> vu;
	vector<Point> vd;
	for (size_t i = 1; i < v.size() - 1; ++i) {
		if (Line(v[0], v[v.size() - 1], v[i]) >= 0)
			vu.push_back(v[i]);
		else if (Line(v[0], v[v.size() - 1], v[i]) < 0)
			vd.push_back(v[i]);
	}
	UpHull(vu, vo, v[0], v[v.size() - 1]);
	DownHull(vd, vo, v[0], v[v.size() - 1]);
}
int main()
{
	vector<Point> v;
	ifstream input("Points.txt", ifstream::in);
	string line;
	Point p;
	while (getline(input, line)) {
		stringstream liness(line);
		liness >> p.x >> p.y;
		v.push_back(p);
	}
	vector<Point> vo;
	ConvexHull(v, vo);
	for (auto p : vo)
		cout << "<" << p.x << "," << p.y << ">" << endl;
	system("pause");
	return 0; 
}

輸入檔案Points.txt:

0 0
1 5
2 2
3 -1
4 5
5 -3
6 0

(5)執行結果