Graham求凸包。

       网上已经有很多关于Graham-scan的资料了。

       Graham扫描法的时间复杂度为O(nlogn),是通过维持一个关于候选点的栈来解决凸包问题。输入的每个点都被压入栈一次,其中不在凸包上的点被弹出。当算法终止时,栈中仅包含凸包中的点,并且从栈底到栈顶按逆时针顺序排列。(摘自算法导论)

       首先要对输入的点进行排序。排序有两种,一种是极角序,一种是水平序。极角序容易理解但是不容易实现,水平序容易实现但是不容易理解。排序调用algorithm里的sort()函数即可,关键的是写好cmp函数。

       首先说极角序。选择最“左下”的点为基点,即选择纵坐标最小的点,有多个时选择其中横坐标最小的点,因为这个点一定在凸包上。设基点为K,然后对比排序剩下的点A、B,向量KA和向量KB与x轴的角(以逆时针为正)的大小。排序完后,建立一个栈,将基点与前两个点压入,然后扫描到第n个点结束。由于凸包一定是凸多边形,所以比较方式就是,取当前点X,栈顶点Y,次栈顶点Z,假设新加入的点在凸包上,那么需要考虑栈顶点是否也在凸包上,如果在,那么向量YX一定在向量ZY的逆时针方向,使用向量叉积的正负就可以判断。

       然后说水平序。水平序直接按坐标排序即可,实现非常方便。在扫描的时候和极角序方法一样。但是水平序需要注意右链和左链问题。因为水平序的排序方式,第一个点一定在最下方,第n个点一定在最上方,这样从1扫描到n的时候由于扫描顺序的问题,只有右边在凸包上的点被保留,所以是完整凸包被1、n两点的线段分开后的右半部分(自己模拟一下便可理解),所以需要再扫描左链。。然后从n到1扫描左链即可。值得注意的是,右链扫描完后,栈顶元素就是n,所以开始时为了避免重复只将n-1点压入栈,从n-2循环到1.


极角序:


#include<iostream>
#include<fstream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<vector>
#include<stack>
#define pow2(a) a*a
#define max(a,b) ((a>b)? a:b)
using namespace std;
long n;
struct dian
{char a;
 long x,y;} d[1000];
stack<long> zhan;
dian dd;

long chaji(dian a,dian b,dian c)
{return ((a.x-c.x)*(b.y-c.y)-(b.x-c.x)*(a.y-c.y));}

double juli(dian a,dian b)
{return sqrt(pow2(a.x-b.x)+pow2(a.y-b.y));}

bool cmp(dian a,dian b)
{long s=chaji(a,b,d[0]);
 if(s>0||((s==0)&&(juli(d[0],a)<juli(d[0],b)))) return true;
 else return false;}

void graham()
{sort(d+1,d+n,cmp);
 zhan.push(0);
 zhan.push(1);
 zhan.push(2);
 dian a,b;
 long c;
 for(long i=3;i<n;i++){while(1){a=d[zhan.top()];
                                c=zhan.top();
                                zhan.pop();
					            b=d[zhan.top()];
					            if(chaji(d[i],b,a)>=0){zhan.push(c);//包括共线点,若为>,则是不包括共线点。
								                       break;}}
					   zhan.push(i);}}

int main()
{cin>>n;
 long h;
 for(long i=0;i<n;i++){cin>>d[i].a>>d[i].x>>d[i].y;
                       if(i==0){dd=d[i];
					            h=0;
								continue;}
					   else{if(d[i].x==dd.x){if(d[i].y<dd.y){dd=d[i];
					                                         h=i;
															 continue;}}
							if(d[i].x<dd.x){dd=d[i];
											h=i;}}}
 dd=d[h];
 d[h]=d[0];
 d[0]=dd;
 graham();
 while(!zhan.empty()){cout<<d[zhan.top()].a<<' ';
                      zhan.pop();}
 return 0;}


水平序:

#include<iostream>
#include<fstream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<stack>
#define max(a,b) ((a>b)? a:b)
using namespace std;
long n,m;
struct dian
{long x,y;
 char a;} d[100050];
stack<long> zhan;
dian p1,p2;

bool cmp(dian a,dian b)
{if(a.y==b.y) return a.x<b.x;
 return a.y<b.y;}

long chaji(dian a,dian b)
{return (a.x*b.y-b.x*a.y);}

void graham()
{sort(d+1,d+1+n,cmp);
 zhan.push(1);
 zhan.push(2);
 long a,b;
 for(long i=3;i<=n;i++){while(1){if(zhan.size()<2) break;
                                 a=zhan.top();
                                 zhan.pop();
								 b=zhan.top();
								 p1.x=d[a].x-d[b].x;
								 p1.y=d[a].y-d[b].y;
								 p2.x=d[i].x-d[a].x;
								 p2.y=d[i].y-d[a].y;
								 if(chaji(p1,p2)<=0){zhan.push(a);//包括共线点,若为“<”,则不包括。 
								                     break;}}
						zhan.push(i);}
 long w=zhan.size();
 zhan.push(n-1);
 for(long i=n-2;i>=1;i--){while(1){if(zhan.size()==w) break;
                                   a=zhan.top();
                                   zhan.pop();
								   b=zhan.top();
								   p1.x=d[a].x-d[b].x;
							       p1.y=d[a].y-d[b].y;
								   p2.x=d[i].x-d[a].x;
								   p2.y=d[i].y-d[a].y;
								   if(chaji(p1,p2)<=0){zhan.push(a);//包括共线点,若为“<”,则不包括 
								                       break;}}
						  zhan.push(i);}}

int main()
{cin>>n;
 for(long i=1;i<=n;i++) cin>>d[i].a>>d[i].x>>d[i].y;
 graham();
 while(!zhan.empty()){cout<<d[zhan.top()].a<<endl;
                      zhan.pop();}
 return 0;}


最后关于共线点,一般是不包括的,这样可以减少凸包里点的个数,也算一个小优化。



posted on 2015-09-08 13:11  lcyzgdy  阅读(213)  评论(0编辑  收藏  举报

导航