Graham算法——凸包问题

Graham算法的思路,大概如下:对平面上的点的集合,从中找到有最小的y坐标值的点p,然后根据其它点和p的连线与正x轴所成的角度将平面上的点进行排序,排序后,扫描从p开始的有序列表,如果所有的这些点都在凸包上,那么每三个相继的点,会组成一个左旋,从另一方面说,如果相继的三个点,p1,p2,p3,组成了一个右旋,则可以立即去除p2,因为它不可能在凸包上!如此扫描到p3等于p时,扫描就结束,剩下的点,就全都是凸包上的点了!

这里面有几个难点:

1、如何根据他们的夹角,来对双链表进行排序

2、如何判断左右旋

3、如何扫描

针对第一个问题,在下面的程序中,采用的是简单的选择排序,即每次在无序区内找到最大的元素,将其放到有序区内,因为这是对双链表进行排序,就有双链表的排序特点,但我认为采用插入,即断开结点之间的联系始终不是好的办法,所以采用了这种简单的交换的方法,虽然效率慢了点,但是重在说明问题!

针对第二个问题,判断左右旋,其实可以用一个行列式的值来判定

|x1   x2  x3|

|y2   y2  y3|

|1    1     1 |

若该行列式为正,则为左旋,反之,则为右旋。其实这个行列式是根据点是在直线的上方还是下方而推断出来的,用直线方程也可以判断左右旋。

针对第三个问题看下面的代码中的scan()函数就很清楚了!

 

下面是用c++写的具体代码:

#include <iostream>
#include <cmath>
using namespace std;
struct point
{
	float x,y;//坐标
	float cos;//该点和x轴的正向所形成的角的余弦
	struct point *prev,*next;
};
typedef struct point Type;
/*创建一个双向链表*/
void creatLinklist_Du(Type *L,int n)
{
       Type *p;
       Type *s;
       p=L;
	   cin>>p->x>>p->y;
	   p->prev=NULL;
	   p->next=NULL;//初始化第一个结点
       int i;
       for(i=0;i<n-1;i++)
       {
              s=new Type;//这里忘记给S分配空间了,所以出错了。
			  cin>>s->x>>s->y;
              s->next=NULL;
              s->prev=p;
              p->next=s;
              p=s;
       }
}
void calculateCos(Type *L)//找到y坐标最小的元素,让它是第一个点,并计算每个点的cos值
{
	Type *p=L,*pMin=L;
	float min=L->y;
	while(p)
	{//找到y坐标最小的结点
		if(min>p->y)
		{
			pMin=p;
			min=p->y;
		}
		p=p->next;
	}
	float x0=pMin->x;
	float y0=pMin->y;
	p=L;
	while(p)
	{
		float x=p->x;
		float y=p->y;
		if(x==x0 && y==y0)
			p->cos=1;
		else
			p->cos=(x-x0)/sqrt((x-x0)*(x-x0)+(y-y0)*(y-y0));
		p=p->next;
	}
}
/*简单选择排序*/
void simpleSlectSort(Type *L)//按cos值进行降序排序
{
	
	Type *p,*pMax,*q,*temp=new Type;
	for(p=L;p->next;p=p->next)
	{
		pMax=p;
		for(q=p->next;q;q=q->next)//找到无序区的最大的cos
		{
			if(q->cos > pMax->cos)
				pMax=q;
		}
		if(p!=pMax)//将此最大值放进有序区,这里只需要交换两个结点的内容就可以了
		{
			temp->cos=pMax->cos;
			temp->x=pMax->x;
			temp->y=pMax->y;
			pMax->cos=p->cos;
			pMax->x=p->x;
			pMax->y=p->y;
			p->cos=temp->cos;
			p->x=temp->x;
			p->y=temp->y;
		}
	}
}
float area(float x1,float y1,float x2,float y2,float x,float y)//计算三个点的左旋还是右旋
{
	return (x1*y2+x2*y+x*y1)-(x*y2+x1*y+x2*y1);
}
void scan(Type *L)//顺序扫描双链表,判断相继的三个点是否组成左旋或是右旋
{
	Type *p=L,*p1=L,*p2,*p3;
	float temp;
	do{
		p2=p1->next;
		if(p2->next)
			p3=p2->next;
		else
			p3=p;
		temp=area(p1->x,p1->y,p2->x,p2->y,p3->x,p3->y);
		if(temp>=0.0)//如果temp为正,则为左旋
			p1=p1->next;
		else
		{
			p1->next=p3;
			p3->prev=p1;
			delete p2;
			p1=p1->prev;
		}
	}while(!(p3==p && temp>=0.0));
}
 /*输出双向链表中的元素*/
void outputLinklist_Du(Type *L)
{
       Type *p;
       p=L;
 
       while(p)
       {
		   cout<<p->x<<" "<<p->y<<"/t"<<p->cos<<endl;
              p=p->next;
       }
}
 
int main()
{
	//freopen("in.txt","r",stdin);
	Type *L=new Type;
	creatLinklist_Du(L,4);
	calculateCos(L);
	simpleSlectSort(L);
	scan(L);
	outputLinklist_Du(L);
	return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值