分治法-最近距離問題Java實現
分治演算法,有很多典型的問題,如最近點問題、線性選擇問題、整數劃分問題、大整數成績問題、棋盤覆蓋問題、迴圈賽日程表、二分搜尋、Strassen矩陣乘法、漢諾塔等。準備花些時間逐個解決這些問題,並用Java實現,從最近點問題開始。網上找到一些程式碼,標題如“java 用蠻力法和分治法求解最近對有關問題”,雖然體現了分治,但劃分不夠徹底,因此我重新對其進行了實現。
一、基本思想及策略:
首先,說說分治的思想。分治, “分而治之”,就是把一個複雜的問題分成兩個或更多的相同或相似的子問題,再把子問題分成更小的子問題……直到最後子問題可以簡單的直接求解,原問題的解即子問題的解的合併。這個技巧是很多高效演算法的基礎,如排序演算法(快速排序,歸併排序),傅立葉變換等。問題的規模越小,越容易直接求解,解題所需的計算時間也越少。
分治通過減小問題規模,對問題各個擊破,其策略是:對於一個規模為n的問題,若該問題可以容易地解決(比如說規模n較小)則直接解決,否則將其分解為k個規模較小的子問題,這些子問題互相獨立且與原問題形式相同,遞迴地解這些子問題,然後將各子問題的解合併得到原問題的解。
二、分治法適用的情況
分治法所能解決的問題一般具有以下幾個特徵:
1 該問題的規模縮小到一定的程度就可以容易地解決
2 該問題可以分解為若干個規模較小的相同問題,即該問題具有最優子結構性質。
3 利用該問題分解出的子問題的解可以合併為該問題的解;
4 該問題所分解出的各個子問題是相互獨立的,即子問題之間不包含公共的子子問題
第一條特徵是絕大多數問題都可以滿足的,因為問題的計算複雜性一般是隨著問題規模的增加而增加;
第二條特徵是應用分治法的前提它也是大多數問題可以滿足的,此特徵反映了遞迴思想的應用;、
第三條特徵是關鍵,能否利用分治法完全取決於問題是否具有第三條特徵,如果具備了第一條和第二條特徵,而不具備第三條特徵,則可以考慮用貪心法或動態規劃法。
第四條特徵涉及到分治法的效率,如果各子問題是不獨立的則分治法要做許多不必要的工作,重複地解公共的子問題,此時雖然可用分治法,但一般用動態規劃法較好。
三、分治法的基本步驟
分治法在每一層遞迴上都有三個步驟:
step1 分解:將原問題分解為若干個規模較小,相互獨立,與原問題形式相同的子問題;
step2 解決:若子問題規模較小而容易被解決則直接解,否則遞迴地解各個子問題
step3 合併:將各個子問題的解合併為原問題的解。
四、以最近點問題為例
最近點對問題:給定平面上的N個點,找出距離最近的兩個點。不仔細分析演算法的效率及優化過程,直接說說該問題的解決思路:
0 如果陣列長度(即點的個數)在一定範圍內時直接求出最近點,蠻力求解
1 求出這些點的X座標的中位數mid
2 以mid為界將所有點分為兩組,分表放在表T1、T2中
3 將T1、T2錶轉換為陣列S1、S2,並將S1、S2分別按X座標升序排列
4 求S1中的點的最近距離
5 求S2中點的最近距離
6 求4、5中的兩距離的最小值,記為d1
7 在S1、S2中搜集距離中線(x=mid)小於d1的點,分別存放於表T3、T4中
8 將表T3、T4轉換為陣列型別S3、S4,並將S3、S4分別按Y座標升序排列
9 求S3、S4兩者之間可能的最近距離(與d1作比較)
五、程式碼參考
package ly.ccnu.AlgorithmDesign;
import java.util.ArrayList;
import java.util.Random;
import java.util.Set;
import java.util.TreeSet;
public class DevideAndConquer {
/**
* 最近點問題
* @param S
*/
public static Point[] closestPoint(Point [] S){
Point[] result = new Point[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座標升序排列
*/
Point [] S1 = new Point[T1.size()];
Point [] S2 = new Point[T2.size()];
T1.toArray(S1);
T2.toArray(S2);
mergeSort(S1, "x"); //按X座標升序排列
mergeSort(S2, "x"); //按X座標升序排列
/**
* 4.求S1中的最近距離的兩個點
*/
Point[] result1 = new Point[2];
result1 = closestPoint(S1);
/**
* 5.求S2中的最近距離的兩個點
*/
Point[] result2 = new Point[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座標升序排列
*/
Point [] S3 = new Point [T3.size()];
Point [] S4 = new Point [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(Point[] a, String property){
Point[] tempArray = new Point[a.length];
mergeSort(a, tempArray, 0, a.length - 1, property);
}
private static void mergeSort(Point[] a, Point [] 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(Point [] a, Point [] 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<Point> testData = new TreeSet<Point>();
Random random = new Random();
int x = 0;
int y = 0;
for(int i = 0;i < 600;i++){
x = random.nextInt(50);
y = random.nextInt(50);
System.out.println("x:" + x + " y:" + y);
testData.add(new Point(x, y));
}
Point [] S = new Point[testData.size()];
S = (Point[]) 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());
Point [] result = new Point [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)));
}
}
package ly.ccnu.AlgorithmDesign;
/**
* 建立自己的類Point
*/
class Point implements Cloneable, Comparable<Point>{
public Point() {
x = 0;
y = 0;
}
public Point(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(Point o) {
if(x == o.getX() && y == o.getY())
return 0;
else
return 1;
}
}
六、遺留問題
1、TreeSet有重複元
2、堆疊溢位java.lang.StackOverflowError(當點的數量很多,但集中在一塊區域的時候)
若哪位大俠有幸看到並發現解決了問題,還望指教!