必威电竞|足球世界杯竞猜平台

凸包
來源:互聯網

凸包(Convex Hull)是一個計算幾何(圖形學)中的概念。在一個實數向量空間V中,對于給定集合X,所有包含X的凸集交集S被稱為X的凸包。X的凸包可以用X內所有點(X1,...Xn)的線性組合來構造。在二維歐幾里得空間中,凸包可想象為一條剛好包著所有點的橡皮圈。用不嚴謹的話來講,給定二維平面上的點集,凸包就是將最散逸層的點連接起來構成的凸多邊型,它能包含點集中所有的點。

定義

⒈對于一個集合D,D中任意有限個點的線性組合的全體稱為D的凸包。

⒉對于一個集合D,所有包含D的凸集之交稱為D的凸包。

可以證明,上述兩種定義是等價的概念

1 點集Q的凸包(convex hull)是指一個最小凸多邊形,滿足Q中的點或者在多邊形邊上或者在其內。右圖中由紅色線段表示的多邊形就是點集Q={p0,p1,...p12}的凸包。

2 一組平面上的點,求一個包含所有點的最小的凸多邊形,這就是凸包問題了。這可以形象地想成這樣:在地上放置一些不可移動的木樁,用一根繩子把他們盡量緊地圈起來,并且為凸邊形,這就是凸包了。

平面凸包求法

常見求法

2.0 Graham's Scan法求解凸包問題  概念

凸包(Convex Hull)是一個計算幾何(圖形學)中的概念。用不嚴謹的話來講,給定二維平面上的點集,凸包就是將最散逸層的點連接起來構成的凸多邊型,它能包含點集中所有點的。嚴謹的定義和相關概念參見維基百科:凸包。

這個算法是由數學大師羅納德·葛立恒(Graham)發明的,他曾經是美國數學學會美國數學學會)主席、AT&T首席科學家以及國際雜技師協會(IJA)主席。(太汗了,這位大牛還會玩雜技~)

問題

給定平面上的二維點集,求解其凸包。

過程

⒈ 在所有點中選取y坐標最小的一點H,當作基點。如果存在多個點的y坐標都為最小值,則選取x坐標最小的一點。坐標相同的點應排除。然后按照其它各點p和基點構成的向量;與x軸的夾角進行排序,夾角由大至小進行順時針掃描,反之則進行逆時針掃描。實現中無需求得夾角,只需根據向量的點積公式求出向量的模即可。以下圖為例,基點為H,根據夾角由小至大排序后依次為H,K,C,D,L,F,G,E,I,B,A,J。下面進行逆時針掃描。

線段;一定在凸包上,接著加入C。假設線段;也在凸包上,因為就H,K,C三點而言,它們的凸包就是由此三點所組成。但是接下來加入D時會發現,線段;才會在凸包上,所以將線段;排除,C點不可能是凸包。

⒊ 即當加入一點時,必須考慮到前面的線段是否會出現在凸包上。從基點開始,凸包上每條相臨的線段的旋轉方向應該一致,并與掃描的方向相反。如果發現新加的點使得新線段與上線段的旋轉方向發生變化,則可判定上一點必然不在凸包上。實現時可用向量叉積進行判斷,設新加入的點為pn + 1,上一點為pn,再上一點為pn - 1。順時針掃描時,如果向量;與;的叉積為正(逆時針掃描判斷是否為負),則將上一點刪除。刪除過程需要回溯,將之前所有叉積符號相反的點都刪除,然后將新點加入凸包。

在上圖中,加入K點時,由于線段;相對于;為順時針旋轉,所以C點不在凸包上,應該刪除,保留K點。接著加入D點,由于線段;相對;為逆時針旋轉,故D點保留。按照上述步驟進行掃描,直到點集中所有的點都遍例完成,即得到凸包。

復雜度

這個算法可以直接在原數據上進行運算,因此空間復雜度為O⑴。但如果將凸包的結果存儲到另一數組中,則可能在代碼級別進行優化。由于在掃描凸包前要進行排序,因此時間復雜度至少為快速排序的O(nlgn)。后面的掃描過程復雜度為O(n),因此整個算法的復雜度為O(nlgn)。

⒉1 凸包最常用的凸包算法是Graham掃描法和Jarvis步進法。

對于一個有三個或以上點的點集Q,過程如下:

計算點集最右邊的點為凸包的頂點的起點,如上圖的P3點。

Do

For i = 0 To 總頂點數

計算有向向量P3->Pi

If 其余頂點全部在有向向量P3->Pi的左側或右側,則Pi點為凸包的下一頂點

Pi點加入凸包列表

goto 1

End If

樂華七子NEXT

Exit Do

1:

loop

此過程執行后,點按極角自動順時針或逆時針排序,只需要按任意兩點的次序就可以了。而左側或右側的判斷可以用前述的矢量點積性質實現。

特殊算法

⒉2 求凸包有很多方法,不過最適合OIer和ACMer的估計還是Graham's Scan這個方法了。它的大致方法是這樣的:首先,找到所有點中最左邊的(y坐標最小的),如果y坐標相同,找x坐標最小的;以這個點為基準求所有點的極角(atan2(y-y0,x-x0)),并按照極角對這些點排序,前述基準點在最前面,設這些點為P..P[n-1];建立一個棧,初始時P、P、P進棧,對于P[3..n-1]的每個點,若棧頂的兩個點與它不構成“向左轉”的關系,則將棧頂的點出棧,直至沒有點需要出棧以后將當前點進棧;所有點處理完之后棧中保存的點就是凸包了。

如何判斷A、B、C構成的關系不是向左轉呢?如果b-a與c-a的向量積小于0就不是。a與b的叉乘就是日本電視臺*b.y-a.y*b.x。

上面的這個Graham的實現比我原來按照USACO里的課文寫得簡單多了,主要是它通過簡單的預處理保證了P、P以及P[n-1]肯定是凸包里的點,這樣就可以避免在凸包“繞回來”的時候繁雜的處理。

中心法

先構造一個中心點,然后將它與各點連接起來,按斜率遞增的方法,求出凸包上部;再按斜率遞減的方法,求出凸包下部。

水平法

從最左邊的點開始,按斜率遞增的方法,求出凸包上部;再按斜率遞減的方法,求出凸包下部。水平法較中心法減少了斜率無限大的可能,減少了代碼的復雜度。

代碼例

代碼一

(在編輯器中將"_ "(下劃線+空格)替換成兩個空格即可編譯;注意要去掉開通的雙字節中文空格,蛋疼的百科。)

#特庫摩lude

#include

using namespace std;

struct 小數點

{

_ _ int x;

_ _ int y;

} p[30005],res[30005];//p標記圖中所有的點,res標記凸包上的點

int cmp(point p1,point p2)

{

_ _ 回車鍵 p1.y < p2.y || (p1.y == p2.y && p1.x < p2.x);

}

bool ral(point p1,point p2,point p3) //用向量積判斷點的位置

{

_ _ return (p2.x - p1.x)*(p3.y - p1.y) > (p3.x - p1.x)*(p2.y - p1.y);

}

int main()

{

_ _ int n,i;

_ _ while(scanf("%d",&n) != EOF) //一共有n個點

_ _ {

_ _ _ _ for(i = 0; i < n; i++)

_ _ _ _ _ _ scanf("%d%d",&p[i].x,&p[i].y);

__ _ _ if(n == 1)

_ _ _ _ {

_ _ _ _ _ _ printf("%d %d\n",p.x,p.y);

_ _ _ _ _ _ continue;

_ _ _ _ }

_ _ _ _ if(n == 2)

_ _ _ _ {

_ _ _ _ _ _ printf("%d %d\n",p.x,p.y);

_ _ _ _ _ _ printf("%d %d\n",p.x,p.y);

_ _ _ _ _ _ continue;

_ _ _ _ }

_ _ _ _ sort(p,p + n,cmp);

_ _ _ _ res = p;

_ _ _ _ res = p;

_ _ _ _ int top = 1;

_ _ _ _ for(i = 2; i < n; i++)

_ _ _ _ {

_ _ _ _ _ _ while(top && !ral(res[top],res[top - 1],p[i]))

_ _ _ _ _ _ top--;

_ _ _ _ _ _ res[++top] = p[i];

_ _ _ _ }

_ _ _ _ int len = top;

_ _ _ _ res[++top] = p[n - 2];

_ _ _ _ for(i = n - 3; i >= 0; i--)

_ _ _ _ {

_ _ _ _ _ _ while(top != len && !ral(res[top],res[top - 1],p[i]))

_ _ _ _ _ _ top--;

_ _ _ _ _ _ res[++top] = p[i];

_ _ _ _ }

_ _ _ _ for(i = 0; i < top; i++)

_ _ _ _ _ _ printf("%d %d\n",res[i].x,res[i].y);//輸出凸包上的點

_ _ }

_ _ 回車鍵 0;

}

代碼二

#特庫摩lude // 求點集合的凸包的gram算法。n是頂點個數,x,y是頂點

坐標。

#include // order 是按照頂點和左下腳的角度的排序后數組。

#include // tu即是逆時針的凸包上的頂點。

#include //

using namespace std; //使用條件:1。點可以任意給,可重復。

// 2。三個以及以上的點。

ifstream fin("input.txt"); // 3。已經考慮了邊上有點的情況。

#define NN 1000

#define pi 3.1415827

typedef struct Cseg{

double x,y,tg;

}Cseg;

int n;

double x[NN],y[NN];

deque order;

deque tu;

Cseg seg1;

deque ::iterator p1;

deque ::iterator p,q;

void in();

void gram();

void makeorder(int s);

double dist(double x1,double yy1,double x2,double yy2);

double cross(double x1,double yy1,double x2,double yy2);

void out();

int main()

{

in();

gram();

李光洙();

回車鍵 0;

}

void out()

{

int i;

for (i=0;i

cout< <<" "< <

}

cout< <<" Edges Polydgon"<

return;

}

void in()

{

int i;

fin>>n;

for (i=0;i

fin>>x>>y;

return;

}

void gram()

{

int i,mm;

mm=0;

for (i=1;i

if (y[mm]>y+1e-9) mm=i;

else if (fabs(y[mm]-y)<1e-9 && x[mm]>x+1e-9) mm=i;

makeorder(mm);

seg1.x=x[mm];

seg1.y=y[mm];

tuClear();

tu.push_back(0);

tu.push_back⑴;

tu.push_back⑵;

for (i=3;i

p=tu.end();

seg1.x=order.x;

seg1.y=order.y;

p--;

q=p-1;

if

(cross(order[*p].x-order[*q].x,order[*p].y-order[*q].y,order.x-order[*

q].x,order.y-order[*q].y)>1e-9)

tu.push_back(i);

else{

tu.pop_back();

i--;

continue;

//tu.push_back(i);

}

}//for

回車鍵;

}

void makeorder(int s)

{

int i;

double tg;

order.clear();

for (i=0;i

if (i==s) continue;

tg=atan2(y-y[s],x-x[s]);

seg1.x=x;

seg1.y=y;

seg1.tg=tg;

p1=order.Begin();

while (p1!=order.end()){

if (fabs(tg-p1->tg)<1e-9){

if

(dist(x[s],y[s],x,y)>dist(x[s],y[s],p1->x,p1->y)+1e-9) {

p1->x=x;

p1->y=y;

}

break;

}

else

if (tg tg){

order.insert(p1,seg1);

break;

}

p1++;

}//while

if (p1==order.end()) order.insert(p1,seg1);

}//for

seg1.x=x[s];seg1.y=y[s];

order.insert(order.begin(),seg1);

//for (i=0;i

// printf("i=%d %lf %lf

%lf\n",i,order.x,order.y,order.tg*180/pi);

回車鍵;

}

double cross(double x1,double yy1,double x2,double yy2)

{

return (x1*yy2-x2*yy1);

}

double dist(double x1,double yy1,double x2,double yy2)

{

return pow((x1-x2)*(x1-x2)+(yy1-yy2)*(yy1-yy2),0.5);

}

代碼三

P標程{北京大學 1113 }

{$Q-,S-,R-}

const

pi=3.1415926575;

zero=1e-6;

maxn=1000;

maxnum=100000000;

var

ans,temp :X波段;

n,TOT :longint;

x,y :array[0..maxn]of extended;

zz,num :array[0..maxn]of longint;

procedure swap(var ii,jj:extended);

var

t :extended;

begin

t:=ii;ii:=jj;jj:=t;

end;

procedure init;

var

i,j :longint;

begin

readln(n,temp);

for i:=1 to n do readln(x[i],y[i]);

end;

函數 ok(x,midx,y,midy:X波段):longint;

begin

if 丙烯腈-丁二烯-苯乙烯共聚物(x-midx)<=zero then

begin

if abs(midy-y)<=zero then exit(0);

if midy>y then exit⑴

else exit⑵;

end

else

begin

if x

else exit⑵;

end;

end;

procedure qsort(head,tail:longint);

var

i,j :longint;

midx,midy :X波段;

begin

i:=head;

j:=tail;

midx:=x[(head+tail) div 2];

midy:=y[(head+tail) div 2];

repeat

while ok(x[i],midx,y[i],midy)=1 do inc(i);

while ok(x[j],midx,y[j],midy)=2 do dec(j);

if i<=j then

begin

swap(x[i],x[j]);

swap(y[i],y[j]);

inc(i);

韓國鐵道9201系柴油動車組(j);

end;

until i>j;

if i

if j>head then qsort(head,j);

end;

函數 情節(x1,y1,x2,y2:X波段):extended;

begin

Plot:=x1*y2-x2*y1;

end;

function check(first,last,new:longint):boolean;

var

ax,ay,bx,by :extended;

Pt :extended;

begin

ax:=x[last]-x[first];ay:=y[last]-y[first];

bx:=x[new]-x[first];by:=y[new]-y[first];

if 情節(ax,ay,bx,by)<-zero then exit(true)

else exit(false);

end;

procedure Tbao;

var

i,j,tail :longint;

begin

TOT:=0;

zz:=1;tail:=1;

for i:=2 to n do

begin

while (zz[tail]<>1)and check(zz[符尾1],zz[tail],i) do dec(tail);

inc(tail);

zz[tail]:=i;

end;

inc(tot,tail-1);

for i:=1 to tail-1 do

num[i]:=zz[i];

zz:=n;tail:=1;

for i:=n-1 downto 1 do

begin

while (zz[tail]<>n)and check(zz[tail-1],zz[tail],i) do dec(tail);

inc(tail);

zz[tail]:=i;

end;

for i:=1 to 符尾1 do

num[tot+i]:=zz[i];

inc(tot,tail-1);

end;

函數 dist(a,b:longint):X波段;

begin

dist:=sqrt((x[a]-x[b])*(x[a]-x[b])+(y[a]-y[b])*(y[a]-y[b]));

end;

procedure main;

var

i,j :longint;

begin

qsort(1,n);

Tbao;

ans:=0;

for i:=1 to TOT1 do

ans:=ans+dist(num[i],num[i+1]);

ans:=ans+dist(num[TOT],num);

ans:=ans+temp*pi*2;

writeln(ans:0:0);

end;

begin

init;

main;

end.

參考資料 >

生活家百科家居網