凸包

凸包是计算几何中的基本问题,在介绍凸包算法之前,先来解决几个计算几何中的几个基础算法。

计算几何基本算法

叉乘

double Cross(Point& p,Point& q,Point&  r)
{
    return (q.x-p.x)*(r.y-p.y) - (r.x-p.x)*(q.y-p.y);
}

在解析几何中我们知道叉乘的绝对值表示所围成的面积的两倍,而叉乘的值可以用来判断点在向量的左侧还是右侧

点乘

double Dot(int p,int q,int r)
{
    return (P[r].x-P[p].x)*(P[r].x-P[q].x)+(P[r].y-P[p].y)*(P[r].y-P[q].y);//在两点中间,等于0是为了应对从点的情况
}

点乘的符号在凸包中可以用来判断共线点在向量的中间还是两侧。

LTL(lowest-than-left)最下边最左端点

这个是比较简单的

int LTL(int n)
{
    int ltl = 0;
    for(int i=1 ; iif(P[ltl].y==P[i].y)
    {
        if(P[ltl].x>P[i].x)
            ltl = i;
    }else if(P[ltl].y>P[i].y)
    {
        ltl = i;
    }
    return ltl;
}

凸包中的几个简单算法

Jevies-March

void  Jevis_March(int n)
{
    ltl = LTL(n);
    int k = ltl;
    do{
        int s = (0==k?1:0);
        for(int i =0 ; iif(i!=k&&i!=s )//不在左边并且不在中间更新s
            {
                double area2 = Cross(P[k],P[s],P[i]);
                if(area2<0 || (area2 == 0 && !between(k,s,i)))s = i;//寻找最右点,如果共线则寻找线端点
            }
        }
        P[k].next = s;
        k = s;
    }while(k!=ltl);
}

算法复杂度 O(nh) 这是一个与凸包极点的个数相关的算法,先找到一个极点(一般就是LTL)然后从这个极点出发每次找出最右边的一个点,当扫完的时候凸包也就构造出来了。

Graham_Scan

这是Graham创造的一个 O(nlgn) 的一个算法。简单的说就是先找到一个极点,然后再把剩下的点把所有的点按照极角的大小排序,这样排出来的

void Graham_Scan(long long n)
{
    ltl = LTL(n);
    swap(P[ltl],P[0]);
    ltl = 0;
    sort(P+1,P+n,cmp);

    vector<int>s;
    s.push_back(ltl);
    //应对重合起始点
    int  next = 1;
    s.push_back(next);
    for(int i=next+1; iif(s.size()<2)
        {
            s.push_back(i);i++;
        }else{
         double area2 = Cross(P[s[s.size()-2]],P[s[s.size()-1]],P[i]);
         if(area2>0)
         {
             s.push_back(i);i++;
         }else 
         {
            s.pop_back();
         }
        }
    }
    //串接凸包上的点
    for(int i=0 ; i1 ; ++i)
        P[s[i]].next = s[i+1];
    P[s[s.size()-1]].next =s[0];
}

这是一个经典的算法,基于这个算法,Andrew提出了一个更快数值稳定性更好的算法(PS:我并不知道快在哪儿,稳定在哪儿。。)

Andrew

int Andrew(int n)
{
    int m = 0;
    sort(P,P+n);
    for(int i=0 ; iwhile(m>1 && Cross(P[ch[m-2]],P[ch[m-1]],P[i])<=0)m--;
        ch[m++] = i;
    }
    int k = m;

    for(int i=n-2 ; i>=0 ;--i )
    {
        while(m>k && Cross(P[ch[m-2]],P[ch[m-1]],P[i])<=0)m--;
        ch[m++] = i;
    }
    if(n>1)m--;
    return m;
}

这个算法其实思想是和Graham_Scan的朴素思想是想通的,唯一的优点,个人认为是实现起来比较简单,比如排序不需要再用极角来排序了,直接用坐标来排序是比较容易,也是比较简单的。

你可能感兴趣的:(算法理论)