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++写的具体代码:
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;
}