凸包問題的快包演算法程式碼(C語言)
阿新 • • 發佈:2019-01-02
二維凸包可以用來解決圍欄問題、城市規劃問題、聚類分析等等。
分治法
時間複雜度:O(n㏒n)。
思路:應用分治法思想,把一個大問題分成幾個結構相同的子問題,把子問題再分成幾個更小的子問題……。然後我們就能用遞迴的方法,分別求這些子問題的解。最後把每個子問題的解“組裝”成原來大問題的解。
步驟:
- 把所有的點都放在二維座標系裡面。那麼橫座標最小和最大的兩個點 P1 和 Pn 一定是凸包上的點(為什麼呢?用反證法很容易證明,這裡不詳講)。直線 P1Pn 把點集分成了兩部分,即 X 軸上面和下面兩部分,分別叫做上包和下包。
- 對上包:求距離直線 P1Pn 最遠的點,即下圖中的點 Pmax 。
- 作直線 P1Pmax 、PnPmax,把直線 P1Pmax 左側的點當成是上包,把直線 PnPmax 右側的點也當成是上包。
- 重複步驟 2、3。
- 對下包也作類似操作。
然而怎麼求距離某直線最遠的點呢?我們用到公式:
設有一個點 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