G_Weber

Keep Hungry Keep Stupid !

公告

统计

分治法实现凸包算法

学习了分治的思想,并看了凸包问题的理论分析,然后就用代码实现了凸包算法。凸包算法跟快速排序很类似,所以叫快包!

一、几何知识
算法涉及到了一个平面几何的知识。就是三角形p1p2p3的面积等于以下行列式的二分之一:
| x1 y1 1 |
| x2 y2 1 | = x1*y2+x3*y1+x2*y3-x3*y2-x2*y1-x1*y3
| x3 y3 1 |
                = x1*y2-x2*y1+x3*(y1-y2)+y3*(x2-x1)
而且当点P3 在射线P1P2的左侧的时候,表达式为正,右侧表达式为负,三点同线的话表达式为0;算法中就利用该几何特性判断一个点在一条线的左侧还是右侧。

二、代码实现
习惯性把算法封装在一个类里面,只是不喜欢很多全局定义类型而已,留出接口
算法声明文件:ConvexHull.h

#ifndef _CONVEXHULL_HGW_
#define _CONVEXHULL_HGW_
/*
	凸包算法
	使用分治思想解决
	洪庚伟   2009.12.28
	http://www.cnblogs.com/g_weber
*/
#include<vector>
#include<stack>

class CConvexHull
{
public:
	//点
	struct Point
	{
		float x;
		float y;

		Point(float a,float b)
			:x(a),y(b)
		{}
		Point()
			:x(0),y(0)
		{}
	};

	//线
	struct Line
	{
		Point from;
		Point to;

		Line(Point f,Point t)
			:from(f),to(t)
		{}
	};

	//该算法可以用递归实现,我这里是用非递归实现
	//所有一个辅助栈实现
	struct StackElement
	{
		//当前需要处理的点的集合的索引
		std::vector<int> PointsIndex;	

		Point		from;
		Point		to;
		//在划分集合的同时计算出一个最大点
		Point		Pmax;
	};

	typedef std::vector<Point>	PointVector;
	typedef std::vector<Line>	LineVector;
	typedef std::stack<StackElement>	ConvexHullStack;

public:
	//给定点的集合,返回由线组成的凸包
	void go(PointVector & Points,LineVector &Lines);
};

#endif

实现文件ConvexHull.cpp就实现了一个函数:

#include "ConvexHull.h"

void CConvexHull::go(PointVector & Points,LineVector &Lines)
{
	Lines.clear();
	if(Points.size() < 2)
		return;

	//1.找出点中最左和最右的两个点,这两个点一定是凸包顶点
	Point mostLeft = *(Points.begin());
	Point mostRight = *(Points.begin());

	PointVector::iterator it = Points.begin();
	while(it != Points.end())
	{
		if(it->x < mostLeft.x )
			mostLeft = *it;
		if(it->x > mostRight.x)
			mostRight = *it;
		it++;
	}
	
	//2.把集合分治为两个集合,上包和下包

	StackElement UpHull;	//上包
	StackElement DownHull;	//下包,实际处理跟上包一样

	UpHull.from = mostLeft;
	UpHull.to = mostRight;

	DownHull.from = mostRight;
	DownHull.to = mostLeft;

	//		    |x1 y1 1|
	//行列式	|x2 y2 1| = x1*y2+x3*y1+x2*y3-x3*y2-x2*y1-x1*y3
	//		    |x3 y3 1|
	//				      = x1*y2-x2*y1+x3*(y1-y2)+y3*(x2-x1)
	//当P3位于射线P1P2的左侧时,该表达式为正。
	//同时该表达式的值为三角形P1P2P3的面积的一半

	//遍历所有点
	it = Points.begin();
	//减小while循环负担的辅助变量
	//即先求出 x1*y2-x2*y1
	float xy = UpHull.from.x * UpHull.to.y -
		UpHull.to.x * UpHull.from.y;

	float max = 0;
	float min = 0;

	//遍历所有的点,划分为两个集合
	//同时求出两个集合中的最大点
	while(it != Points.end())
	{
		//计算行列式的值
		float express = 
			xy + it->x*(UpHull.from.y - UpHull.to.y)+
			(UpHull.to.x - UpHull.from.x) * it->y;
		if(express > 0.0f)
		{
			//当前点在P1P2的左端,划分到上包
			//记录索引而已
			UpHull.PointsIndex.push_back(it - Points.begin());

			//同时算出面积最大的一个点
			if(express > max)
			{
				max = express;
				UpHull.Pmax = *it;
			}
		}

		//如果不能划分到上包,那么可能划分到下包
		if(express < 0.0f)
		{
			DownHull.PointsIndex.push_back(it - Points.begin());
			if(express < min)
			{
				min = express;
				DownHull.Pmax = *it;
			}
		}
		it++;
	}

	//将上包和下包压入栈,等待进一步处理
	ConvexHullStack chstack;
	chstack.push(UpHull);
	chstack.push(DownHull);

	//3.分治处理上包和下包
	do
	{
		StackElement element = chstack.top();
		chstack.pop();

		//该集合当中不需要再扩展,则两个点就为组成凸包的线段
		if(element.PointsIndex.size() == 0)
		{
			Lines.push_back(Line(element.from,element.to));
		}
		else
		{
			//再次将当前集合划分到不同的集合中去
			StackElement left,right;
			left.from = element.from;
			left.to = element.Pmax;

			right.from = element.Pmax;
			right.to = element.to;

			float express;
			float leftmax = 0;
			float rightmax = 0;

			std::vector<int>::iterator indexit=element.PointsIndex.begin();
			while(indexit != element.PointsIndex.end())
			{
				//检测是否在 left的左侧
				express = left.from.x * left.to.y - left.to.x *left.from.y +
					Points[*indexit].x * (left.from.y - left.to.y) +
					Points[*indexit].y * (left.to.x - left.from.x);
				if(express > 0.0f)
				{
					left.PointsIndex.push_back(*indexit);
					//同时算出面积最大的一个点
					if(express > leftmax)
					{
						leftmax = express;
						left.Pmax = Points[*indexit];
					}
					indexit++;
					continue;
				}

				express = right.from.x * right.to.y - right.to.x *right.from.y +
					Points[*indexit].x * (right.from.y - right.to.y) +
					Points[*indexit].y * (right.to.x - right.from.x);
				if(express > 0.0f)
				{
					right.PointsIndex.push_back(*indexit);
					//同时算出面积最大的一个点
					if(express > rightmax)
					{
						rightmax = express;
						right.Pmax = Points[*indexit];
					}
					indexit++;
					continue;
				}
				indexit++;
			}
			chstack.push(left);
			chstack.push(right);
		}
	}while(!chstack.empty());
}

三、测试程序
为了测试算法,不得不写了一个可视化的程序来测试。总不能在Dos程序下一个个点输入坐标,然后观察输出吧。实在看不出结果,就写个小程序测试,比较直观可以判断算法是否正确
凸包演示程序
测试截图:
CH

 

By:洪庚伟

出处:http://www.cnblogs.com/G_Weber

posted on 2009-12-29 22:59 G_Weber 阅读(1105) 评论(2) 编辑 收藏