一、 問題描述
給定平面上的n個點,找其中的一對點,使得在n個點組成的所有點對中該點對間的距離最小。
二、 解題思路及所選算法策略的可行性分析
思路:利用分治法來解決問題。遞歸子結構求最接近點對總體可分為幾個步驟:
1、當問題規模小於20,直接求解最小點對
2、將n個點組成的集合S分成2個子集S1和S2
3、遞歸求出兩個子集中的最接近點對並比較出最小點對,記錄距離dmin
4、以X坐標為基准找到所有點中線,在中線附近找出相距可能小於dmin的點對點,記錄於S3和S4
5、求S3和S4每點對距離,和dmin進行比較,求解最接近點對
策略可行性分析:
設直線l上有2m個點,以m為中點將l分割成兩條線段dl,dr,然后求出dl和dr這兩點條線段中的最小點距d1,d2,此時d=min{d1,d2},再通過計算出dl線段的中最大點與dr線段中的最小點之間的距離D,最小距離則為min{d,D}.
二維情況與此相似,最大的區別只是對於中線位置的點,二維情況只是針對中線旁好多可能點,再在Y軸方向上進行點的篩選,以減少平方計算次數。
三、 偽代碼描述及復雜度分析
Public static double closestPoint(S)
{
1、首先,遞歸結束條件,當數組長度在一定范圍內時直接求出最近點,蠻力求解
2、求所有點在X坐標中的中位數midX
3、以midX為界將所有點分成兩組分別存放在兩個表中
4、將兩張表轉化為數組類型,並分別按X坐標升序排列
5、求S1中的最近距離的兩個點
6、求S2中的最近距離的兩個點
7、求兩最近距離的最小值
8、在S1、S2中收集距離中線小於d1的點,分別存放於兩個表中
9、分別將表T3、T4轉換為數組類型的S3、S4,並將其分別按Y坐標升序排列
10、求解S3、S4兩者之間可能的更近(相比於d1)距離 , 以及構成該距離的點
}
復雜度分析:
設算法耗時T(n)。 算法第1步、第2步、第3步和第8步用了O(n)時間。第7步和第10步用了常數時間。第4步和第9步用了O(nlogn)時間。第5步和第6步分別用了T(n/2)時間。不過第4步和第9步是數組的排序預處理時間,所以不算在算法中。所以經由預處理的算法所需計算時間滿足遞歸方程:
T(n)={ O(1) n<4
2T(n/2)+O(n) n>=4
由此,T(n)=O(nlogn)。
代碼實現
dcPoint.java
package 分治法; public class dcPoint implements Cloneable, Comparable<dcPoint>{ public dcPoint() { x = 0; y = 0; } public dcPoint(int x, int y) { this.x = x; this.y = y; } public void setX(int x) { this.x = x; } public void setY(int y) { this.y = y; } public int getX() { return x; } public int getY() { return y; } private int x; private int y; @Override public int compareTo(dcPoint o) { if(x == o.getX() && y == o.getY()) return 0; else return 1; } }
NPointPair.java
package 分治法; import java.util.ArrayList; import java.util.Random; import java.util.Set; import java.util.TreeSet; public class NPointPair { /** * 最近點問題 * @param S */ public static dcPoint[] closestPoint(dcPoint [] S){ dcPoint[] result = new dcPoint[2]; /** * 0.首先,解決該問題的邊界,當數組長度在一定范圍內時直接求出最近點,蠻力求解 */ double dmin = Double.POSITIVE_INFINITY; double tmpmin = 0; if(S.length <= 20){ for(int i = 0; i < S.length; i ++){ for(int j = i + 1; j < S.length; j ++){ tmpmin = Math.sqrt(Math.pow(S[i].getX() - S[j].getX(), 2)) + Math.pow(S[i].getY() - S[j].getY(), 2); if(tmpmin < dmin){ dmin = tmpmin; result[0] = S[i]; result[1] = S[j]; } } } return result; } /** *1.求所有點在X坐標的中位數 */ int minX = (int) Double.POSITIVE_INFINITY; //保證假設的初始最小值足夠大 int maxX = (int) Double.NEGATIVE_INFINITY; //保證假設的初始最大值足夠小 for(int i = 0; i < S.length; i++){ if(S[i].getX() < minX) minX = S[i].getX(); if(S[i].getX() > maxX) maxX = S[i].getX(); } int midX = (minX + maxX)/2; /** * 2.以midX為界將所有點分成兩組分別存放在兩個表中 */ ArrayList T1 = new ArrayList(); ArrayList T2 = new ArrayList(); for(int i = 0; i < S.length; i++){ if(S[i].getX() <= midX) //是否要=號? T1.add(S[i]); if(S[i].getX() > midX) T2.add(S[i]); } /** * 3.將兩張表轉化為數組類型,並分別按X坐標升序排列 */ dcPoint [] S1 = new dcPoint[T1.size()]; dcPoint [] S2 = new dcPoint[T2.size()]; T1.toArray(S1); T2.toArray(S2); mergeSort(S1, "x"); //按X坐標升序排列 mergeSort(S2, "x"); //按X坐標升序排列 /** * 4.求S1中的最近距離的兩個點 */ dcPoint[] result1 = new dcPoint[2]; result1 = closestPoint(S1); /** * 5.求S2中的最近距離的兩個點 */ dcPoint[] result2 = new dcPoint[2]; result2 = closestPoint(S2); /** * 6.求兩最近距離的最小值 */ double d1 = Math.sqrt(Math.min(Math.pow(result1[0].getX() - result1[1].getX(), 2) + Math.pow(result1[0].getY() - result1[1].getY(), 2), Math.pow(result2[0].getX() - result2[1].getX(), 2) + Math.pow(result2[0].getY() - result2[1].getY(), 2))); if(Math.pow(result1[0].getX() - result1[1].getX(), 2) + Math.pow(result1[0].getY() - result1[1].getY(), 2) < Math.pow(result2[0].getX() - result2[1].getX(), 2) + Math.pow(result2[0].getY() - result2[1].getY(), 2)) result = result1; else result = result2; /** * 7.在S1、S2中收集距離中線小於d1的點,分別存放於兩個表中 */ ArrayList T3 = new ArrayList(); ArrayList T4 = new ArrayList(); for(int i = 0; i < S1.length; i++){ if(midX - S1[i].getX() < d1) T3.add(S1[i]); } for(int i = 0; i < S2.length; i++){ if(S2[i].getX() - midX < d1){ T4.add(S2[i]); } } /** * 8.分別將表T3、T4轉換為數組類型的S3、S4,並將其分別按Y坐標升序排列 */ dcPoint [] S3 = new dcPoint [T3.size()]; dcPoint [] S4 = new dcPoint [T4.size()]; T3.toArray(S3); T4.toArray(S4); mergeSort(S3, "y"); mergeSort(S4, "y"); /** * 求解S3、S4兩者之間可能的更近(相比於d1)距離 , 以及構成該距離的點 */ double d = Double.POSITIVE_INFINITY; for(int i = 0; i < S3.length; i ++){ for(int j = 0; j < S4.length; j ++){ if(Math.abs(S3[i].getY() - S4[j].getY()) < d1){ double tmp = Math.sqrt(Math.pow(S3[i].getX() - S4[j].getX(), 2) + Math.pow(S3[i].getY() - S4[j].getY(), 2)); if(tmp < d){ d = tmp; result[0] = S3[i]; result[1] = S4[j]; } } } } return result; } //歸並排序 private static void mergeSort(dcPoint[] a, String property){ dcPoint[] tempArray = new dcPoint[a.length]; mergeSort(a, tempArray, 0, a.length - 1, property); } private static void mergeSort(dcPoint[] a, dcPoint [] tempArray, int left, int right, String property){ if(left < right){ int center = (left + right) >> 1; //分治 mergeSort(a, tempArray, left, center, property); mergeSort(a, tempArray, center + 1, right, property); //合並 merge(a, tempArray, left, center + 1, right, property); } } private static void merge(dcPoint [] a, dcPoint [] tempArray, int leftPos, int rightPos, int rightEnd, String property){ int leftEnd = rightPos - 1; int numOfElements = rightEnd - leftPos + 1; int tmpPos = leftPos; //游標變量, 另兩個游標變量分別是leftPos 和 rightPos while(leftPos <= leftEnd && rightPos <= rightEnd){ if(property.equals("x")){ if(a[leftPos].getX() <= a[rightPos].getX()) tempArray[tmpPos++] = a[leftPos++]; else tempArray[tmpPos++] = a[rightPos++]; }else if(property.equals("y")){ if(a[leftPos].getY() <= a[rightPos].getY()) tempArray[tmpPos++] = a[leftPos++]; else tempArray[tmpPos++] = a[rightPos++]; }else throw new RuntimeException(); } while(leftPos <= leftEnd) tempArray[tmpPos++] = a[leftPos++]; while(rightPos <= rightEnd) tempArray[tmpPos++] = a[rightPos++]; //將排好序的段落拷貝到原數組中 System.arraycopy(tempArray, rightEnd-numOfElements+1, a, rightEnd-numOfElements+1, numOfElements); } public static void main(String[] args) { Set<dcPoint> testData = new TreeSet<dcPoint>(); Random random = new Random(); int x = 0; int y = 0; for(int i = 0;i < 50;i++){ x = random.nextInt(500); y = random.nextInt(500); System.out.println("x:" + x + " y:" + y); testData.add(new dcPoint(x, y)); } dcPoint [] S = new dcPoint[testData.size()]; S = (dcPoint[]) testData.toArray(S); for(int i = 0; i < S.length; i ++){ System.out.println("(" + S[i].getX() + ", " + S[i].getY() + ")"); } System.out.println(testData.size()); dcPoint [] result = new dcPoint [2]; result = closestPoint(S); System.out.println("最近的兩點分別是(" + result[0].getX() + ", " + result[0].getY() + ") 和 (" + result[1].getX() + ", " + result[1].getY() + "), 最近距離為:" + Math.sqrt(Math.pow(result[0].getX() - result[1].getX(), 2) + Math.pow(result[0].getY() - result[1].getY(), 2))); } }