1. 程式人生 > >分治法-最近距離問題Java實現

分治法-最近距離問題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(當點的數量很多,但集中在一塊區域的時候)

若哪位大俠有幸看到並發現解決了問題,還望指教!