分治法求最近點對問題
這個問題熬了我一個晚上改錯,都是空指針錯誤,從網上看了好多資料都沒什麼用,但就是那一瞬間看到了代碼的某一行發現了錯誤,在數據與數組中某個值比較時,數組範圍弄錯了,導致比較過程中會指向數組外的地方(還是空指針問題)。現寫下分析過程,以便後續查驗
問題描述
補充要求:隨機生成橫縱座標值均在1-10的30個點(浮點數),遞歸求出距離最近的一對點的座標及其該兩點距離值並以顯示。
時間複雜度
如果是用蠻力法,那麼時間複雜度毫無疑問是O(n^2),並不是最優解。現採用分治法遞歸函數,使之時間複雜度爲O(nlogn)。
基本思想
30個點爲0到29,數集爲S。
基本思想:將S分成左右不相交的兩個子集,求每個子集的最近點對,以及分別位於兩個子集中的
所有點對的最近點對(由左邊一個點,右邊一個點構成),最後通過比較得到S中的最近點對。
做法
(1)當n≤2時直接求解。n爲1時則返回一個double型的最大值,n爲2時則記錄兩點並返回兩點間距離。n>3時按如下步驟。
(2)分解:根據x座標,對S中的點升序排序,以經過第n/2個點做中垂線,分爲左右兩側,將S均分成兩個子集:S[1…m] 和[m+1…n] ,其中 (int) m=n/2 。這樣,一部分在中垂線的左邊或在中垂線上,另一部分在中垂線右邊或在中垂線上。
(3)遞歸:用同樣的分治算法分別求中垂線兩邊的最近點對的距離min1和min2。
(4)組合:比較min1和min2,取較小值賦給mint,求中垂線左邊一個點和右邊一個點的最近距離看其是否小於mint,若小於則改變點對,更新mint。
難點:求中垂線左邊一個點和右邊一個點的最近距離
如果還是左邊和右邊一個一個算的話,又回到了O(n^2)。事實上不用遍歷每一個點,有些點沒有必要去算。先給出具體方法:我們現已比較出左側和右側兩方的最短距離mint,那麼以中垂線爲界兩邊擴展mint的距離,可能符合要求的點均在這個區域內。這些點可以比較其與中垂線的距離來篩選,x的差距絕對值小於mint的進入新的數組K[n]。如圖:
那麼K中數據怎麼比較呢,現介紹一個方式。
取一個mint×2*mint方框,若其中不存在小於mint的點對,那麼這個方框中最多存在六個點。
所以我們往K裏放數據時應是按y的值從小到大排好的,這樣我們遍歷K的時候只需往後多找六個點就行
了。這樣使得此過程時間複雜度爲O(n)。但是需提前將30個點分別按x和y排好順序,存到兩個數組中,以便後續使用。
上代碼(如有人查看,請不要直接搬抄代碼,無意義)
import java.util.Arrays;
import java.util.Comparator;
public class MinDistance {
static Points point = new Points();
public static void main(String[] args) {
node[] a = new node[30];
createPoint(a);
node[] b = a.clone();
Arrays.sort( a , new cmp1() ); //按x排序
Arrays.sort( b , new cmp2() ); //按y排序
double dis = minDis(0,29,a,b,point);
System.out.println("最近點對:");
System.out.println("( "+point.x1+" , "+point.y1+" ),"+"( "+point.x2+" , "+point.y2+" )");
System.out.println("最近距離:"+dis);
}
public static void createPoint(node[] a) { //創建隨機點
for(int i=0;i<30;i++)
{
a[i] = new node();
a[i].x = 10*Math.random();
a[i].y = 10*Math.random();
for(int j=0;j<i;j++) {
if( a[j].x==a[i].x && a[j].y==a[i].y ) {
i--;
break;
}
}
}
}
public static double distance(int i,int j,node[] a) { //計算距離
double n = Math.pow(a[i].y-a[j].y, 2);
double m = Math.pow(a[i].x-a[j].x, 2);
double w = Math.sqrt(m+n);
return w;
}
public static double minDis(int low,int high,node[] a,node[] b,Points p) {
if(high-low==0)
return Double.MAX_VALUE;
else if(high-low==1) {
p.x1 = a[low].x;
p.x2 = a[high].x;
p.y1 = a[low].y;
p.y2 = a[high].y;
return distance(low,high,a);
}
else {
int m = (low+high)/2;
Points c = new Points();
Points d = new Points();
double min1 = minDis(low,m,a,b,c);
double min2 = minDis(m+1,high,a,b,d);
double mint;
if(min1 < min2){
p.x1 = c.x1;
p.x2 = c.x2;
p.y1 = c.y1;
p.y2 = c.y2;
mint = min1;
}else {
p.x1 = d.x1;
p.x2 = d.x2;
p.y1 = d.y1;
p.y2 = d.y2;
mint = min2;
}
double x = a[m].x;
int k = 0;
node[] K = new node[30];
for(int i=0;i<30;i++) {
if(mint>=(Math.abs(b[i].x-x))) {
K[k] = new node();
K[k].x = b[i].x;
K[k].y = b[i].y;
k++;
}
}
for(int i=0;i<(k-1);i++) {
int temp = (i+7)<k?(i+7):k;
for(int j=i+1;j<temp;j++) { //記住此地方,空指針,爲了它真是用了好長時間,長記性
if(distance(i,j,K)<mint) {
mint = distance(i,j,K);
p.x1 = K[i].x;
p.x2 = K[j].x;
p.y1 = K[i].y;
p.y2 = K[j].y;
}
}
}
return mint;
}
}
}
class Points{ // 存儲最近點對
public double x1;
public double y1;
public double x2;
public double y2;
}
class node{ //存儲30個點
public double x=0;
public double y=0;
}
class cmp1 implements Comparator<node>{
public int compare(node a,node b){
if( a.x < b.x)
return -1;
else if(a.x >b.x)
return 1;
else return 0;
}
}
class cmp2 implements Comparator<node>{
public int compare(node a,node b){
if( a.y < b.y)
return -1;
else if(a.y >b.y)
return 1;
else return 0;
}
}
個人總結,如有幫助,感謝點贊,如有問題,下方評論