旋轉卡殼初步

轉載於 http://www.cnblogs.com/Booble/archive/2011/04/03/2004865.html

一.簡單枚舉算法的不足

上一次介紹了一個基本的求平面最遠點對的算法

即先求點集的凸包 然後枚舉凸包上的點來求最遠點集

這是利用了凸包上的點相比 點集中的點 一般是很少的 平均情況很好 並且我們也能AC這個問題

但是這是有侷限性的 當凸包上的點達到O(N)的級別時 凸包的優化作用就不存在了

不過我們還要考慮到 凸包還起了對凸包上點集排序的作用

凸包有很多的優美的性質 我們可以加以利用 以得到更加高效的算法

旋轉卡殼算法就是利用凸包特性的一類解決問題的方法

==============================

二.旋轉卡殼算法

旋轉卡(qiǎ)殼算法(Rotating Calipers Algorithm):

是解決一些與凸包有關問題的有效算法 就像一對卡殼卡住凸包旋轉而得名

Every time one blade of the caliper lies flat against an edge of the polygon, it forms an antipodal pair with the point or edge touching the opposite blade. It turns out that the complete "rotation" of the caliper around the polygon detects all antipodal pairs and may be carried out in O(n) time.

http://en.wikipedia.org/wiki/Rotating_calipers

(圖片來自:http://cgm.cs.mcgill.ca/~orm/rotcal.html)

被一對卡殼正好卡住的對應點對稱爲對踵點(Antipodal point)

http://en.wikipedia.org/wiki/Antipodal_point

可以證明對踵點的個數不超過3N/2個 也就是說對踵點的個數是O(N)

對踵點的個數也是我們下面解決問題時間複雜度的保證

上第一個圖是卡殼的一般情況 卡住兩點 圖二是卡住一條邊和一個點

由於實現中 卡住兩點的情況不好處理 我們通常關注第二種情況

在第二種情況中 我們可以看到 一個對踵點和對應邊之間的距離比其他點要大

也就是一個對踵點和對應邊所形成的三角形是最大的 下面我們會據此得到對踵點的簡化求法

看一下官方的僞代碼:

當時我看完了 就一個字 ... 我最討厭冗長的程序了...

複製代碼
begin p0:=pn; q:=NEXT[p]; while (Area(p,NEXT[p],NEXT[q]) > Area(p,NEXT[p],q)) do q:=NEXT[q]; q0:=q; while (q != p0) do begin p:=NEXT[p]; Print(p,q); while (Area(p,NEXT[p],NEXT[q]) > Area(p,NEXT[p],q) do begin q:=NEXT[q]; if ((p,q) != (q0,p0)) then Print(p,q) else return end; if (Area(p,NEXT[p],NEXT[q]) = Area(p,NEXT[p],q)) then if ((p,q) != (q0,p0)) then Print(p,NEXT[q]) else Print(NEXT[p],q) end end.
複製代碼

幾經折騰 終於找到了一個不錯的實現:http://www.cnblogs.com/DreamUp/archive/2010/09/16/1828131.html

不過不是很好理解 這裏作一下說明

複製代碼
1 ch[m+1]:=ch[1]; j:=2; 2  for i:=1 to m do 3 begin 4 while cross(ch[i],ch[j],ch[i+1])<cross(ch[i],ch[j+1],ch[i+1]) do 5 begin inc(j); if j>m then j:=1; end; 6 writeln(ch[i].x,' ',ch[i].y,' ',ch[j].x,' ',ch[j].y); 7 end;
複製代碼

上面就是旋轉卡殼尋找對踵點的過程

其中叉積函數Cross(A,B,C:Point):Real 返回AB到AC的二維定義下的叉積

這裏主要用到了叉積求三角形面積的功能

我們對於一條對應邊<CH i,CH Next[i]>求出距離這條邊最遠的點CHj

則由上面第二種情況可知 CH i 和 CH j 爲一對對踵點 這樣讓 CH i 繞行凸包一週即可得到所有的對踵點

下面面這個圖 由於本人的gif圖製作水平拙劣 所以不好看

需要的可以下載幾何畫板察看原版GSP文件 點擊這裏下載GSP文件

接下來考慮 如何得到距離每條對應邊的的最遠點呢?

稍加分析 我們可以發現 凸包上的點依次與對應邊產生的距離成單峯函數

具體證明可以從凸包定義入手 用反證法解決

這樣我們再找到一個點 使下一個點的距離小於當前的點時就可以停止了

而且隨着對應邊的旋轉 最遠點也只會順着這個方向旋轉 我們可以從上一次的對踵點開始繼續尋找這一次的

由於內層while循環的執行次數取決於j增加次數 j最多增加O(N)

所以求出所有對踵點的時間複雜度爲O(N)

還有有兩點需要注意:

1.上面這段代碼及代碼的分析都是需要凸包上沒有三點共線的

2.Next[i] 不需要手動求 在原代碼中有很好的處理

最後指出網上很多文章的一個錯誤 一個點的對踵點並不是離這個點最遠的點!

這樣子的點對是根本不滿足對踵點的性質的 即最爲重要的單峯分佈性質

下圖是一個反例:

==============================

三.旋轉卡殼算法的簡單應用

至此我們終於可以更高效的解決平面最遠點對問題了

有一個很重要的結論是 最遠點對必然屬於對踵點對集合

那麼我們先求出凸包 然後求出對踵點對集合 然後選出距離最大的即可

用這個算法可以47ms AC這個問題 算上凸包的時間 總複雜度爲O(Nlog2N)

[Poj 2187]代碼如下:

const    maxn=50000;
type point=record x,y:longint;end;
var    n,i,x,m,ans,j:longint;
    ch,p:
array[1..maxn+1]of point;
    s:
array[1..maxn]of longint;
function cross(a,b,c:point):longint; inline;begin
cross:
=(b.x-a.x)*(c.y-a.y)-(b.y-a.y)*(c.x-a.x);
end;
function dist(a,b:point):longint; inline;
begin
dist:
=sqr(a.x-b.x)+sqr(a.y-b.y);
end;
function cmp(a,b:point):boolean; inline;
begin
cmp:
=(a.x<b.x)or(a.x=b.x)and(a.y<b.y);
end;
function max(a,b:longint):longint;
begin
if a>bthen max:=aelse max:=b;
end;
procedure swap(a,b:longint); inline;
var    x:point;
begin
x:
=p[a]; p[a]:=p[b]; p[b]:=x;
end;
procedure hull(l,r:longint; a,b:point);
var    x,i,j,k:longint;
    y:point;
begin
x:
=l; y:=p[l];
for k:=lto rdo
   
if (s[x]<s[k])or(s[x]=s[k])and(cmp(y,p[k]))
       
thenbegin x:=k; y:=p[k];end;
i:
=l-1; j:=r+1;
for k:=lto rdo
   
begin
    inc(i); s[i]:
=cross(p[k],a,y);
   
if s[i]>0then swap(i,k)else dec(i);
   
end;
for k:=rdownto ldo
   
begin
    dec(j); s[j]:
=cross(p[k],y,b);
   
if s[j]>0then swap(j,k)else inc(j);
   
end;
if l<=ithen hull(l,i,a,y);
inc(m); ch[m]:
=y;
if j<=rthen hull(j,r,y,b);
end;
begin
assign(input,
'Maxd.in'); reset(input);
assign(output,
'Maxd.out'); rewrite(output);
readln(n);
for i:=1to ndo
   
begin
    readln(p[i].x,p[i].y);
   
if (x=0)or cmp(p[i],p[x]) then x:=i;
   
end;
swap(
1,x);
m:
=1; ch[1]:=p[1]; hull(2,n,p[1],p[1]);
ch[m
+1]:=ch[1]; j:=2; ans:=0;
for i:=1to mdo
   
begin
   
while cross(ch[i],ch[j],ch[i+1])<cross(ch[i],ch[j+1],ch[i+1])do
       
begin inc(j);if j>mthen j:=1;end;
    ans:
=max(ans,dist(ch[i],ch[j]));
   
end;
writeln(ans);
close(input); close(output);
end.
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#define _DEBUG 1
#define MAX_N 50010

typedef struct{
	int x,y;
}Point;
Point pts[MAX_N];//postion of farms
Point chs[MAX_N];//凸包點
int n;//個數

long dist(Point p0,Point p1){
	return ((p1.x-p0.x)*(p1.x-p0.x)+(p1.y-p0.y)*(p1.y-p0.y));
}

long crossMultiple(Point p0,Point p1,Point p2){//計算(p1-p0)×(p2-p0)
	return (p1.x-p0.x)*(p2.y-p0.y)-(p2.x-p0.x)*(p1.y-p0.y);
}

void exchange(Point &p1,Point &p2){
	Point tmp = p1;
	p1 = p2;
	p2 = tmp;
}

int compare(const void *p1,const void *p2){//可能次序反了
	Point p3 = *(Point *)p1;
	Point p4 = *(Point *)p2;

	long cm = crossMultiple(pts[0],p3,p4);
	if(cm < 0)//方向注意
		return 1;
	else if(cm == 0){
		if(dist(p3,pts[0]) > dist(p4,pts[0]) )
			return 1;
	}
	return -1;
}
int convexHull(){
	int i;
	int temp = 0;
	for(i=1;i<n;++i){//找到最下,最左邊的點
		if(pts[i].y < pts[temp].y ||
			(pts[i].y == pts[temp].y && pts[i].x < pts[temp].x))
			temp = i;		
	}
	exchange(pts[0],pts[temp]);//交換temp 與第0個點
	qsort(pts+1,n-1,sizeof(Point),compare);//點按逆時針排序

	chs[0] = pts[0];
	chs[1] = pts[1];

	int top = 1;
	for(i=2;i<n;++i){		
		while(crossMultiple(chs[top-1],chs[top],pts[i]) <= 0 ){//出棧,這邊可能有問題,邊界判斷
			if(top <1)break;
			top--;	
		}
		chs[++top] = pts[i];
	}
	return top+1;
}

int main(){
	int i,j;
#if _DEBUG == 1
	freopen("POJ2187.in","r",stdin);
#endif
	scanf("%d",&n);
	for(i=0;i<n;++i){
		scanf("%d %d",&pts[i].x,&pts[i].y);
	}

	int m=convexHull();//計算凸包,並返回凸包中點個數
	j = 1;
	long distance = 0; 
	long d;
	chs[m] = chs[0];//注意	
	for(i=0;i<m;++i){//通過旋轉卡殼算法得到最大距離,要考慮的問題:假如凸包上的點在一條直線上
		while(crossMultiple(chs[(j+1) % m],chs[i],chs[i+1]) > crossMultiple(chs[j],chs[i],chs[i+1])){
			j = (j+1) % m;			
		}
		if((d=dist(chs[j],chs[i])) > distance)//可能有問題			
			distance = d;
		if((d=dist(chs[j],chs[i+1])) > distance)
			distance = d;
	}	
	printf("%ld\n",distance);
	return 0;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章