分治法解決最近點對問題

問題

        給定平面上n個點,找其中的一對點,使得在n個點的所有點對中,該點對的距離最小。嚴格地說,最接近點對可能多於1對。爲了簡單起見,這裏只限於找其中的一對。

原理(這段爲抄襲https://blog.csdn.net/liufeng_king/article/details/8484284

        設S中的點爲平面上的點,它們都有2個座標值x和y。爲了將平面上點集S線性分割爲大小大致相等的2個子集S1和S2,我們選取一垂直線l:x=m來作爲分割直線。其中m爲S中各點x座標的中位數。由此將S分割爲S1={p∈S|px≤m}和S2={p∈S|px>m}。從而使S1和S2分別位於直線l的左側和右側,且S=S1∪S2 。由於m是S中各點x座標值的中位數,因此S1和S2中的點數大致相等。遞歸地在S1和S2上解最接近點對問題,我們分別得到S1和S2中的最小距離d1和d2。現設d=min(d1,d2)。若S的最接近點對(p,q)之間的距離d(p,q)<d則p和q必分屬於S1和S2。不妨設p∈S1,q∈S2。那麼p和q距直線l的距離均小於d。因此,我們若用P1和P2分別表示直線l的左邊和右邊的寬爲d的2個垂直長條,則p∈S1,q∈S2,如圖所示:


     距直線l的距離小於d的所有點

       在一維的情形,距分割點距離爲d的2個區間(m-d,m](m,m+d]中最多各有S中一個點。因而這2點成爲唯一的末檢查過的最接近點對候選者。二維的情形則要複雜些,此時,P1中所有點與P2中所有點構成的點對均爲最接近點對的候選者。在最壞情況下有n2/4對這樣的候選者。但是P1和P2中的點具有以下的稀疏性質,它使我們不必檢查所有這n^2/4對候選者。考慮P1中任意一點p,它若與P2中的點q構成最接近點對的候選者,則必有d(p,q)<d。滿足這個條件的P2中的點有多少個呢?容易看出這樣的點一定落在一個d×2d的矩形R中,如下圖所示:


包含點q的dX2d矩形R

     由d的意義可知P2中任何2個S中的點的距離都不小於d。由此可以推出矩形R中最多隻有6個S中的點。事實上,我們可以將矩形R的長爲2d的邊3等分,將它的長爲d的邊2等分,由此導出6個(d/2)×(2d/3)的矩形。如左圖所示:

矩陣R中點的稀疏性

     若矩形R中有多於6個S中的點,則由鴿舍原理易知至少有一個δ×2δ的小矩形中有2個以上S中的點。設u,v是這樣2個點,它們位於同一小矩形中,則:

因此d(u,v)≤5d/6<d 。這與d的意義相矛盾。也就是說矩形R中最多隻有6個S中的點。圖4(b)是矩形R中含有S中的6個點的極端情形。由於這種稀疏性質,對於P1中任一點p,P2中最多隻有6個點與它構成最接近點對的候選者。因此,在分治法的合併步驟中,我們最多只需要檢查6×n/2=3n對候選者,而不是n^2/4對候選者。這是否就意味着我們可以在O(n)時間內完成分治法的合併步驟呢?現在還不能作出這個結論,因爲我們只知道對於P1中每個S1中的點p最多只需要檢查P2中的6個點,但是我們並不確切地知道要檢查哪6個點。爲了解決這個問題,我們可以將p和P2中所有S2的點投影到垂直線l上。由於能與p點一起構成最接近點對候選者的S2中點一定在矩形R中,所以它們在直線l上的投影點距p在l上投影點的距離小於d。由上面的分析可知,這種投影點最多隻有6個。因此,若將P1和P2中所有S的點按其y座標排好序,則對P1中所有點p,對排好序的點列作一次掃描,就可以找出所有最接近點對的候選者,對P1中每一點最多隻要檢查P2中排好序的相繼6個點。(抄襲結束)

僞代碼

輸入:按x座標排列的n(n>=2)個點的集合S={(x1,y1),(x2,y2),...,(xn,yn)}

輸出:最近點的距離

1.如果n==2,則返回(x1,y1)和(x2,y2)之間的距離,算法結束;

2.如果n==3,則返回(x1,y1)、(x2,y2)和(x3,y3)之間的最小距離,算法結束;(此步必要在於,若n==3劃分之後必然有一半爲n==1,導致無法正確執行遞歸)

3.劃分:m==S中各點x座標的中位數;

4.d1 = 計算{(x1,y1),...,(xm,ym)}的最近對距離;

5.d2 = 計算{(xm,ym),...,(xn,yn)}的最近對距離;

6.d = min(d1,d2);

7.依次考察集合S中的點p(x,y),如果(x<=xm 並且x>=xm-d),則將點p放入集合P1中;如果(x>xm 並且x<=xm+d),則將點p放入集合P2中;

8.將集合P1和P2按y座標升序排列;

9.對集合P1和P2中的每個點p(x,y),在y座標區間[y,y+d]內最對取出6個候選點,計算與點p的最近距離d3;

10.返回min{d,d3};

注:對於第7,8步,由於任何排序算法需要O(nlgn)的複雜度,但這裏需要O(n)的複雜度纔可使整個算法的複雜度爲O(nlgn),因此採用將歸併算法融入求解過程中。對第9步,實際情況是如下圖所示


c++實現
/*
	程序: 最近對的距離
	作者:Moyu 
*/
#include<iostream>
#include<vector>
#include<cmath>
#include<algorithm>
#include<fstream>
#include<sstream>

using namespace std;

struct Point{
	double x;
	double y;
};
inline bool Compx(const Point &p1, const Point &p2)
{
	return p1.x < p2.x;
}
inline bool Compy(const Point &p1, const Point &p2)
{
	return p1.y < p2.y;
}
inline double Distance(const Point &a, const Point &b)
{
	return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}
void Merge(vector<Point> &v, int lo, int m, int hi)
{
	vector<Point> vl(v.begin()+lo,v.begin()+m);
	int i = lo;
	int j = 0;
	int k = m;
	while(i < hi){
		if(j < vl.size() && (k == hi || vl[j].y <= v[k].y))
			v[i++] = vl[j++];
		if(k < hi && (j == vl.size() || vl[j].y > v[k].y))
			v[i++] = v[k++];
	}
}
/*
	函數:點集最近對的距離
	參數:vx:以x排序的點集 
*/
double Closest(vector<Point> &vx, int lo, int hi)
{
	if(hi - lo == 2){
		if(vx[lo].y > vx[hi-1].y){
			swap(vx[lo],vx[hi-1]);
		}
		return Distance(vx[lo],vx[hi-1]);
	}
	if(hi - lo == 3){
		sort(vx.begin()+lo,vx.begin()+hi,Compy); 
		double d1 = Distance(vx[lo],vx[lo+1]);
		double d2 = Distance(vx[lo],vx[hi-1]);
		double d3 = Distance(vx[lo+1],vx[hi-1]);
		return min({d1,d2,d3});
	}
	int m = (lo + hi) / 2; 
	double mx = vx[m].x;
	double dl = Closest(vx,lo,m);
	double dr = Closest(vx,m,hi);
	double d = min(dl,dr);
	Merge(vx,lo,m,hi);
	vector<Point> vp;
	for(int i = lo; i < hi; ++i){
		if(abs(vx[i].x - mx) < d)
			vp.push_back(vx[i]);
	}
	for(int i = 0; i < vp.size(); ++i){
		for(int j = i + 1; j < vp.size(); ++j){
			if(vp[j].y - vp[i].y >= d)
				break;
			else{
				double dm = Distance(vp[i],vp[j]);
				if(dm < d)
					d = dm;
			}
		}
	}
	return d;
}
int main()
{
	vector<Point> v;
	ifstream file("point.txt",ifstream::in);
	string line;
	while(getline(file,line)){
		Point p;
		stringstream liness(line);
		liness >> p.x >> p.y;
		v.push_back(p);
	}
	sort(v.begin(),v.end(),Compx);
	cout << Closest(v,0,v.size()) << endl;
	return 0;
}


箴言錄:

        堂堂正正做人,踏踏實實做事。


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