以回溯解高速公路重建與正序全排列

前言

你們好,這是本人算法系列最後一篇,介紹回溯算法。感謝你們支持,但願指正。java

算法介紹

回溯算法至關於窮舉搜索的巧妙實現,可是性能通常不理想。回溯算法中常常使用裁剪,
裁剪,即在一步刪除一大組可能性的作法。
下面以兩個例子進行說明。git

高速公路重建問題

問題描述

設給定N個點P1,P2,.......,PN,它們位於X軸上。Xi是Pi點的X座標。進一步假設X1=0以及這些點從左到右給出。着N個點肯定,在每一對,點間的N(N-1)/2個,形如|Xi - Xj|(i !=j)的距離。收費公路重建問題就是由這些距離重構一個點集。github

舉例分析

以書上例子說明: 令D是距離集合,且D={1,2,2,2,3,3,3,4,5,5,5,6,7,8,10},由D=15能夠知道N=6,若設X1=0,則有x6=10,x5=8。算法

下面以X1=0,x6=10,x5=8爲初始狀態進行嘗試。數組

輸入圖片說明
在這裏輸入圖片標題

目前邊集中最大值爲7,要麼x4=7,要麼x2=3,咱們分別進行嘗試
嘗試x4=7,裁剪相應邊集Dbash

image

簡單推測:性能

目前邊集中最大值爲6,要麼x3=6,要麼x2=4. 若x3=6,x4-x3=1,而剩餘邊集D中沒有1,即x3=6不成立ui

若是x2=4,x2-xo=4和x5-x2=4,而剩餘邊集D中僅有1個4,即x2=4也不成立。 由此可知,x4=7不成立咱們須要進行回溯spa

嘗試x4=7不成立,回溯到初始狀態.net

image

嘗試x2=3 裁剪相應邊集D

image

簡單推測: 目前邊集中最大值爲6,要麼x4=6,要麼x2=4.

若是x2=4,x2-xo=4和x5-x2=4,而剩餘邊集D中僅有1個4,即x2=4不成立。

若x4=6,x10-x4=4,x5-x4=8-6=2,x4-x2=6-3=3,x4-x0=6-0=6,均在剩餘邊集合D中,x4=6 可選。
嘗試x2=3 基礎上嘗試x4=6,裁剪相應邊集合D

image

簡單推測:

目前邊集中最大值爲5,要麼x3=5.

X3-x1=5-0=5,x3-x2=5-3=2,x4-x5=6-5=1,x5-x3=8-5=3,x6-x3=10-5=5,整好全部邊集均清空,咱們找到最終答案以下:

image

核心代碼實現

/**
	 * @param x
	 *            結果點集合
	 * @param distSet
	 *            邊的優先隊列,注意隊列從大到小
	 * @param n
	 *            根據邊集大小計算出來的 預計點個數, n(n-1)/2=distSet.size();
	 * @return
	 */
	public static boolean turnpike(int[] x, PriorityQueue<Integer> distSet, int n) {
		// 倒序排列邊集合
		x[1] = 0;
		x[n] = distSet.poll();
		x[n - 1] = distSet.poll();
		if (distSet.contains(x[n] - x[n - 1])) {
			distSet.remove(new Integer(x[n] - x[n - 1]));
			return place(x, distSet, n, 2, n - 2);
		} else {
			return false;
		}
	}


	/**
	 * 重建高速公路-回溯算法核心
	 * @param x  結果集合
	 * @param distSet 剩餘邊集合 從大到小構建的堆
	 * @param n       預計包含的點集合
	 * @param left    本次嘗試的左邊界
	 * @param right   本次嘗試的右邊開始點
	 */
	private static boolean place(int[] x, PriorityQueue<Integer> distSet, int n, int left, int right) {
		int dmax = 0;
		boolean found = false;
		if (distSet.isEmpty()) {
			return true;
		}
		dmax = distSet.peek();
		// 嘗試x[right]爲 dmax
		if (CheckIfRight(x, distSet, n, left, right, dmax)) {
			x[right] = dmax;
			for (int j = 1; j < left; j++) {
				distSet.remove(new Integer(Math.abs(x[j] - x[right])));
			}
			for (int j = right + 1; j <= n; j++) {
				distSet.remove(new Integer(Math.abs(x[j] - x[right])));
			}
			found = place(x, distSet, n, left, right - 1);
			if (!found) {
				for (int j = 1; j < left; j++) {
					distSet.add(new Integer(Math.abs(x[j] - x[right])));
				}
				for (int j = right + 1; j <= n; j++) {
					distSet.add(new Integer(Math.abs(x[j] - x[right])));
				}
			}
		}
		// x[left]=x[n]-dmax
		if (!found && CheckIfleft(x, distSet, n, left, right, dmax)) {
			x[left] = x[n] - dmax;
			for (int j = 1; j < left; j++) {
				distSet.remove(new Integer(Math.abs(x[n] - dmax - x[j])));
			}
			for (int j = right + 1; j <= n; j++) {
				distSet.remove(new Integer(Math.abs(x[n] - dmax - x[j])));
			}
			found = place(x, distSet, n, left + 1, right);
			if (!found) {
				for (int j = 1; j < left; j++) {
					distSet.add(new Integer(Math.abs(x[n] - dmax - x[j])));
				}
				for (int j = right + 1; j <= n; j++) {
					distSet.add(new Integer(Math.abs(x[n] - dmax - x[j])));
				}
			}
		}
		return found;
	}
複製代碼

正序全排列問題

舉例說明

例如 n爲5,r爲3,輸出 輸出:

[1, 2, 3] [1, 2, 4] [1, 2, 5]

[1, 3, 4] [1, 3, 5]

[1, 4, 5]

[2, 3, 4] [2, 3, 5]

[2, 4, 5]

[3, 4, 5]

分析

第1位最大值爲n-r+1=3,
第2位的最大值爲n-r+2=4
第3位的最大值爲n-r+3=5
能夠推測出第i位最大值爲n-r+i,也就是每一位超過相應值須要回溯。

實現原理

  1. 前提 數組a做爲中間變量,存儲可能的臨時結果。 a[1]標示第一位,a[2]標示第2位,...,a[r]表示第r位 resultIndex 表示當前嘗試位數,它從1開始,到r。
  2. 步驟
    2.1 咱們從a[1]=1開始到a[1]>n-r+1結束。當resultIndex=r時存儲到最終結果隊列。
    2.2 分支操做說明
  • 當resultIndex<r 咱們進行下探,即a[resultIndex+1]=a[resultIndex]+1

  • 當resultIndexr==r時進行末位試探,即a[resultIndex]=a[resultIndex]+1

  • 當 a[resultIndex+1]>n-r+resultIndex,進行回溯,即resultIndex--;a[resultIndex]=a[resultIndex]+1;

  • 當resultIndex==r 而且 a[resultIndex+1]==n-r+resultIndex,進行末位回溯 即resultIndex--;a[resultIndex]=a[resultIndex]+1;

圖例說明

image

代碼實現

/**
		 * 核心處理方法
		 * @param selectIndexList 存儲結果
		 */
		private void combOrder(List<int[]> selectIndexList) {

			if(r==0){
				return ;
			}
			if(r==1){
			  for (int i = 0; i<n; i++) {
					selectIndexList.add(new int[] { i });
			   }
				return;
			}
			int resultIndex=1;
			 a[1]=1;
			while(a[1]<=(n-r+1)){//根據第一位進行裁剪,第一位取值爲1<a[1]<n-r+1 列入5個數選3個 第一位最大爲5-3+1=3
			   if(resultIndex<r){
				if(a[resultIndex]<=n-r+resultIndex){
					a[resultIndex+1] = a[resultIndex]+1;
					resultIndex++;
				}else{// 已經超過每一位的最大值 n-r+resultIndex,進行回溯
					resultIndex--;
					a[resultIndex]=a[resultIndex]+1;
				}
			  }else if(resultIndex==r){
				  int[] selectIndex = new int[r];
					selectIndexList.add(selectIndex);
					for (int j = 1; j <= r; j++) {//存儲結果
						selectIndex[j - 1] = a[j]-1;
					}
					if (a[resultIndex]==n) { // 若當前搜索結果爲1,表示本次搜素完成,進行回溯 此時條件至關爲 ri+a[ri]==r+1 由於ri=r因此簡化啦
						resultIndex--;
						a[resultIndex] = a[resultIndex]+1; // 回溯
					} else {
						a[resultIndex] = a[resultIndex]+1; // 搜索到下一個數
					}
			  }
			}
		}
複製代碼

總結

  • 回溯算法,通常須要分析 ,推導出回溯的條件。
  • 回溯算法,效率通常比較低下。

代碼地址

碼雲

高速公路重建源碼地址

正序全排列源碼地址

github

高速公路重建源碼地址

正序全排列源碼地址

相關文章
相關標籤/搜索