1. 程式人生 > >凸包問題的快包演算法程式碼(C語言)

凸包問題的快包演算法程式碼(C語言)

二維凸包可以用來解決圍欄問題、城市規劃問題、聚類分析等等。

分治法

時間複雜度:O(n㏒n)。
思路:應用分治法思想,把一個大問題分成幾個結構相同的子問題,把子問題再分成幾個更小的子問題……。然後我們就能用遞迴的方法,分別求這些子問題的解。最後把每個子問題的解“組裝”成原來大問題的解。
步驟:

  1. 把所有的點都放在二維座標系裡面。那麼橫座標最小和最大的兩個點 P1 和 Pn 一定是凸包上的點(為什麼呢?用反證法很容易證明,這裡不詳講)。直線 P1Pn 把點集分成了兩部分,即 X 軸上面和下面兩部分,分別叫做上包和下包。
  2. 對上包:求距離直線 P1Pn 最遠的點,即下圖中的點 Pmax 。
  3. 作直線 P1Pmax 、PnPmax,把直線 P1Pmax 左側的點當成是上包,把直線 PnPmax 右側的點也當成是上包。
  4. 重複步驟 2、3。
  5. 對下包也作類似操作。

這裡寫圖片描述


然而怎麼求距離某直線最遠的點呢?我們用到公式:
這裡寫圖片描述
設有一個點 P3 和直線 P1P2 。(座標:p1(x1,y1),p2(x2,y2),p3(x3,y3))

當上式結果為正時,p3在直線 p1p2 的左側;當結果為負時,p3在直線 p1p2 的右邊

對上式的結果取絕對值,絕對值越大,則距離直線越遠。

注意:在步驟一,如果橫座標最小的點不止一個,那麼這幾個點都是凸包上的點,此時上包和下包的劃分就有點不同了,需要注意。(但在程式碼中無需特殊處理,詳見程式碼註釋)

我對原作者的程式碼增加了註釋,然後更改了一些程式碼,並寫明瞭原因,希望原作者不要介意,如果有人看到我粘的程式碼有問題,希望能不吝賜教,謝謝

#include<stdio.h>
#include<stdlib.h>

int g_result[240][2];

/*getResult()實現功能:以座標P0(x1,y1)和Pn(x2,y2)為直線,找出pack裡面離這條直線最遠的點Pmax
*並找出直線P0Pmax和PmaxPn的上包,進行遞迴。
*注:Pack[0][0]存放點的個數,pack[1]開始存放點的座標。
*全域性變數g_result[][]用來存放凸包上的點,即最終所要的答案。同樣g_result[0][0]存放的是已找到的點的個數。
**/
void getResult(int Pack[240][2], int x1, int y1, int x2, int y2)
{
    int i,t,x3,y3,R,Rmax,tmax;
    int ResultPack[240][2];
    ResultPack[0][0] = 0;
    if(Pack[0][0] <= 2)
    //if(Pack[0][0] <= 1)//從主函式遞迴呼叫來看,Pack用的是含有線上兩點的上包總點數,所以如果是0或1或2,就可以直接返回
    //這裡沒用<=2是因為後面的遞迴演算法中的ResultPack沒有記錄Pack[1][0](這好像產生一個大問題),所以為了解決這裡,下面的for仍應該從1開始吧?
    //就算x3暫時不是凸包點,但如果計算所得的R大於0就很可能成為凸包點,所以我確切認為是從1開始,然後這裡也應該相應改成<=2,不然多跑了一趟浪費時間
        return; 
    x3 = Pack[1][0];//這裡的Pack[1][0]不一定是x1的
    y3 = Pack[1][1];
    R = x1*y2 + x3*y1 + x2*y3 - x3*y2 - x2*y1 - x1*y3;
    Rmax = R;
    tmax = 1;
    //for(i=2;i<=Pack[0][0];i++)//我把CSDN搞下來上的程式碼改了
    for(i=1;i<=Pack[0][0];i++)
    {
        x3 = Pack[i][0];
        y3 = Pack[i][1];
        R = x1*y2 + x3*y1 + x2*y3 - x3*y2 - x2*y1 - x1*y3;//R的絕對值越大,那麼x3這個點距離x1,x2所連成的線越遠(其實不知道公式哪來的,但目前暫時不重要,就是矩陣如下)
        /*
        |x1 y1 1|
        |x2 y2 1|
        |x3 y3 1|
        */
        if(R >= 0)//R>=0在左邊,也就是隻處理上包
        {
            t = ++ResultPack[0][0];
            ResultPack[t][0] = x3;
            ResultPack[t][1] = y3;
        }
        if(R > Rmax)
        {
            Rmax = R;
            tmax = i;
        }
    }
    if(Rmax <= 0)//這裡就是處理凸包一邊上有三點的情況
    {
        //for(i=1;i<ResultPack[0][0];i++)//這裡少掃到一個點,萬一少的那點正好是第三點,那就有bug了,所以我又改了他的程式碼
        for(i=1;i<=ResultPack[0][0];i++)
        {
            x3 = ResultPack[i][0];
            y3 = ResultPack[i][1];
            R = x1*y2 + x3*y1 + x2*y3 - x3*y2 - x2*y1 - x1*y3;
            if(R == 0 && !((x3==x2&&y3==y2)||(x3==x1&&y3==y1)))
            {
                t = ++g_result[0][0];
                g_result[t][0] = ResultPack[i][0];
                g_result[t][1] = ResultPack[i][1];
            }
        }
        return;
    }
    else
    {
        t = ++g_result[0][0];
        g_result[t][0] = Pack[tmax][0];
        g_result[t][1] = Pack[tmax][1];
        if(ResultPack[0][0] == 0)
            return;
    }
    getResult(ResultPack,x1,y1,Pack[tmax][0],Pack[tmax][1]);//新的上包遞迴
    getResult(ResultPack,Pack[tmax][0],Pack[tmax][1],x2,y2);//新的下包遞迴
}

int main()
{
    int Point[240][2];//Point存所有點。
    int i=1;
    int x1,y1,x2,y2,x3,y3;
    g_result[0][0]=0;Point[0][0]=0;//Point的第一行第一列元素存放包裡面有幾個點。初始化為0。
    printf("請輸入所有點的座標:\n");
    while(scanf("%d,%d",&Point[i][0],&Point[i][1]) != EOF)
        i++;
    Point[0][0] = i-1;//前面i=1開始加的
    x1 = Point[1][0];
    y1 = Point[1][1];
    x2 = x1;
    y2 = y1;
    for(i=2;i<=Point[0][0];i++)
    {
        x3 = Point[i][0];
        y3 = Point[i][1];
        if(x3 < x1)//用列舉找最左邊的點,用x1儲存,最右邊用x2儲存

        //其實這裡沒有考慮多個最左邊點和最右邊點的情況
        //不不不,其實考慮了的,因為一開始隨便選一條線就行了,多個最右邊點可以通過處理上包的時候解決掉
        {
            x1 = x3;
            y1 = y3;
        }
        else if(x3 > x2)
        {
            x2 = x3;
            y2 = y3;
        }
    }
    g_result[1][0] = x1;
    g_result[1][1] = y1;
    g_result[2][0] = x2;
    g_result[2][1] = y2;
    g_result[0][0] += 2;
    getResult(Point, x1, y1, x2, y2);//上包遞迴
    getResult(Point, x2, y2, x1, y1);//下包遞迴

    printf("\n\n構成凸包的點有:\n");
    for(i=1;i<=g_result[0][0];i++)
        printf("(%d,%d)\n",g_result[i][0],g_result[i][1]);
    system("pause");//提交記得去掉這一行
    return 0;//HDOJ選手程式碼一般沒有這一行
}

2018年8月8日18:02:51