算法導論-分治法-最近點對-HDOJ1007

HDOJ1007的原題目是求出在不同時套中兩個玩具的前提下,圓圈的最大半徑。問題翻譯過來就是求解最近點對的問題,這個問題是經典的分治法問題。

參考博客:http://www.cnblogs.com/peng-come-on/archive/2012/01/18/2325163.html

毫無疑問,通過暴力手段列舉所有的點對並計算這些點對的距離,找出最小的一組,可以得到最後的結果。但是,這道題的數據規模非常大,所以,這種傳統的方法肯定行不通。我試過,hdoj是超時的。

我對原博客的第3步和第4步有不同的看法。

原博客步驟:

1.讀取數據,並將點按橫座標升序排列。
2.以最中間的那個點爲基準,將平面內的點分爲左右兩個部分。遞歸調用mindis(int,int)函數,分別求出左右兩個部分的點集的最短距離,並取兩者中的較小值,即爲min.
3.顯然,min並不一定是最短距離,因爲還可能存在一種情況,即點對中的一個點位於左區域,另一個點位於右區域。所以,我們取點集中橫座標與分界線的距離小於min的點,存入p2[N]數組中。
4.對p2[N]數組中的點按縱座標進行排序,計算p2[N]數組中的點對的距離,如果存在小於min 的情況,就取代min作爲最近距離。
最後,min即爲平面內點集的最近距離。

我的步驟是:

1.讀取數據,並將點按橫座標升序排列。
2.以最中間的那個點爲基準,將平面內的點分爲左右兩個部分。遞歸調用mindis(int,int)函數,分別求出左右兩個部分的點集的最短距離,並取兩者中的較小值,即爲min.
3.顯然,min並不一定是最短距離,因爲還可能存在一種情況,即點對中的一個點位於左區域,另一個點位於右區域。所以,我們取點集中橫座標與分界線的距離小於min的點,根據在分界線的左右,分別存入pxSmall[N]數組和pxLarge[N]數組中。

4.因爲唯一可能的情況是一個點在pxSmall[N],另一個點在pxLarge[N]中。只要遍歷這個兩個數組便可以了。

下面是在原博客代碼上的修改:

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>

using namespace std;

#define N 1000010

struct point 
{
 double x;
 double y;
}p1[N],pxSmall[N],pxLarge[N];

double dis ( point a , point b )
{
	return sqrt( pow (a.x-b.x,2) + pow ( a.y-b.y,2 ) );
}

double min ( double a , double b )
{
	return a<b?a:b;
}

bool cpx ( point a , point b )
{
	return a.x < b.x ;
}
bool cpy ( point a , point b )
{
	return a.y < b.y ;
}

double mindis (int l, int r)
{
	if( l + 1 == r )
		return dis ( p1[l] ,p1[r] );
	if( l + 2 == r )
		return min ( dis ( p1[l] , p1[l+1] ) , min ( dis ( p1[l+1] , p1[r] ) , dis ( p1[l] , p1[r] ) ) );
	else
	{
		int mid ,count1=0, count2=0;
		double mini;
		mid = ( l + r) >> 1 ;
		mini = min ( mindis ( l , mid ) , mindis ( mid+1 , r ) );
		for( int i = l ; i <= r ; i++ )
		{
			if ( fabs ( p1[i].x - p1[mid].x ) <= mini )
			{
				if (p1[i].x-p1[mid].x < 0)
					pxSmall[count1++]=p1[i];
				else
					pxLarge[count2++]=p1[i];
			}
				
		}
		//直接遍歷兩個數組
		for(int i=0;i<count1;i++)
		{
			for(int j=0;j<count2;j++)
			{
				double temp = dis(pxSmall[i], pxLarge[j]); 
				if(temp<mini)
					mini=temp;
			}
		}
		/*
		sort ( p2 , p2+count , cpy );
		for ( int i=0 ; i < count ; i++ )
		{
			for ( int j = i+1; j < count ;j++)
			{
				if ( p2[j].y-p2[i].y>=mini)
					break;
				else if(dis (p2[j],p2[i])<mini)
					mini=dis(p2[j],p2[i]);
			}
		}
		*/
		return mini;
	}
}
  
  

int main()
{
 //freopen("input.txt","r",stdin);
	int n ;
	double dia ;
	while(scanf("%d",&n)==1&&n)
	{
		for(int i=0;i<n;i++)
		scanf("%lf%lf",&p1[i].x,&p1[i].y);
		sort ( p1 , p1 + n-1 , cpx );
		dia = mindis ( 0 , n-1 );
		printf("%.2f\n", dia / 2 );
	}
	return 0;
}

我在HDOJ上分別跑這兩組代碼,發現原博客runtime爲1875ms,我的代碼runtime爲1093ms,比原博客的運行時間少了很多。因爲少了排序這個步驟。當然,可能不同的數據集得到的運行時間不同,如果換一個數據集或數據集規模很大的時候原博客的方法可能更有優勢。但是有一點,我的方法更好地符合分治法的要求,正確性是可以保證的。最近一直在學習《算法導論》,這本書真是神書,是算法中的算法,不僅僅告訴你算法,還會告訴你算法的證明。以後如果遇到分治法的問題,我會繼續補充到這篇博客。

補充:參考博客:http://blog.csdn.net/junerfsoft/article/details/2975495

博客中提到了飛機調度的問題,說的很好。可惜因爲每個點的座標類型都是double類型的,不能直接找到那6個點,可以使用二分查找,但是也不是特別方便。我隨便寫了代碼,時間上反而更長了。

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