穷举:生成所有候选解,然后找出需要的解。
回溯:把解表示成向量,每个分量取自一个有限集合。从部分解开始,每次添加解的一个分量,然后判断如果有可能扩展成完整解则递归下去,否则换成下一个。可以看做是隐式图上的深度优先搜索。
回溯/穷举的复杂度,最坏时和后者一样,通常情形因为不必遍历所有子节点,还是比较快的。
回溯框架:
backtrack(a[], k)
if a[0,...,k] is a solution
output;
else
k = k + 1;
for c: the i-th of candidates
a[k] = c;
bactrack(a, k);
剪枝
把快死的或者齐心怪状的树枝剪掉。对于搜索树来说,就是把候选范围缩小。
例1,八皇后问题: 把八个皇后放在8x8棋盘上,使得任意两个不在同一行或者同一列或者对角线上。
直接以棋盘格为编号,解空间为2^64, 以皇后为编号,64^8, 不同行,8^8, 不同列,8!。
如果在生成排列后再做检测,这就是穷举;如果在for分支前先做检测,这就是回溯剪枝了。
例2,子集和问题:从一个正整数集合中找一个子集,使得其元素和为给定的数d。
记当前部分和为s。 如果选择e, s + e > d 或者s + 剩下所有元素 < d,该分支肯定无解,应该剪掉。
回溯:把解表示成向量,每个分量取自一个有限集合。从部分解开始,每次添加解的一个分量,然后判断如果有可能扩展成完整解则递归下去,否则换成下一个。可以看做是隐式图上的深度优先搜索。
回溯/穷举的复杂度,最坏时和后者一样,通常情形因为不必遍历所有子节点,还是比较快的。
回溯框架:
backtrack(a[], k)
if a[0,...,k] is a solution
output;
else
k = k + 1;
for c: the i-th of candidates
a[k] = c;
bactrack(a, k);
剪枝
把快死的或者齐心怪状的树枝剪掉。对于搜索树来说,就是把候选范围缩小。
例1,八皇后问题: 把八个皇后放在8x8棋盘上,使得任意两个不在同一行或者同一列或者对角线上。
直接以棋盘格为编号,解空间为2^64, 以皇后为编号,64^8, 不同行,8^8, 不同列,8!。
如果在生成排列后再做检测,这就是穷举;如果在for分支前先做检测,这就是回溯剪枝了。
例2,子集和问题:从一个正整数集合中找一个子集,使得其元素和为给定的数d。
记当前部分和为s。 如果选择e, s + e > d 或者s + 剩下所有元素 < d,该分支肯定无解,应该剪掉。
只有我们在验证所有叶子都可达确认算法正确后,才可用剪枝。一开始上剪枝可能导致结果不正确,比如下面的求连续可重复子集和sumBT()。
代码:
import java.util.Arrays;
public class Backtrack
{
static void subsetBT(boolean[] a, int k)
{
if(k == a.length-1){
int i;
System.out.printf("{");
for(i = 1; i <= k; ++i)if(true == a[i])System.out.printf(" %d", i);
System.out.printf(" },");
return;
}
k = k + 1;
boolean[] candidates = {false, true};
for(boolean c : candidates){
a[k] = c;
subsetBT(a, k);
}
}
static void generateSubsets(int n)
{/*生成{1,2,...,n}的所有子集*/
boolean[] set = new boolean[n+1]; /*set[i] --- whether select i*/
subsetBT(set, 0);
System.out.printf("%n");
}
static boolean notUsed(int[] a, int n, int k)
{/*whech k \in a[1..n)*/
boolean ret = true;
int i;
for(i = 1; i < n; ++i)if(a[i] == k){ret = false; break;}
return ret;
}
static void permutationBT(int[] a, int k)
{
int i, n = a.length - 1;
if(k == n){
System.out.printf("{");
for(i = 1; i <= k; ++i)System.out.printf(" %d", a[i]);
System.out.printf("},");
nextPermutation(a);
return;
}
++k;
for(i = 1; i <= n; ++i)if(notUsed(a, k, i)){
a[k] = i;
permutationBT(a, k);
}
}
static void generatePermutations(int n)
{/*生成{1,2,...,n}的所有排列*/
int[] a = new int[n+1];
permutationBT(a, 0);
System.out.printf("%n");
}
static void nextPermutation(int[] a)
{
int k = 0, j = 0, i = 0, n = a.length - 1;
int[] b = new int[n+1];
boolean[] c = new boolean[n+1];
for(i = n-1; i >= 1; --i){
for(j = a[i]+1; j <= n; ++j)if(notUsed(a, i, j))break;
if(j <= n)break;
}
if(i >= 1){
//a[1..i-1],j,剩下的升序排列
for(k = 1; k < i; ++k){b[k] = a[k]; c[b[k]] = true;}
b[i] = j; c[b[i]] = true;
for(k = 1; k <= n; ++k)if(!c[k])b[++i] = k;
System.out.printf("[");
for(i = 1; i <=n; ++i)System.out.printf(" %d", b[i]);
System.out.printf("]==");
}else{
System.out.printf("==NULL");
}
}
static void setSumBT(int[] a, int k, int[] set, int d, int s)
{
int j = 1, i, n = set.length - 1;
if((s == d)||(k >= n)){
for(i = 1; i <= n; ++i)if(1 == a[i])System.out.printf("+%d", set[i]);
System.out.printf("=%d level=%d%n", s, k);
return;
}
k = k + 1;
if(true){//prune
if(s + set[k] > d) j = 0;
int sum = 0;
for(i = k; i <= n; ++i)sum += set[i];
if(s + sum < d)j = -1;
}
for(i = j; i >= 0; --i){
a[k] = i;
setSumBT(a, k, set, d, s + a[k]*set[k]);
}
}
static boolean sumBT(int[] a, int k, int[] set, int d, int s, int maxCnt)
{
int j = 1, i, n = set.length - 1;
boolean ret = false;
if((s == d)){
for(i = 1; i <= k; ++i)System.out.printf("+%d", a[i]);
System.out.printf("=%d%n", s);
return s == d;
}
if(k >= maxCnt){return false;}
k = k + 1;
for(i = 1; i <= n; ++i){
a[k] = set[i];
ret = sumBT(a, k, set, d, s + a[k], maxCnt);
if(ret == true)return true;
}
return false;
}
public static void main(String[] arg)
{
//generateSubsets(3);
//generatePermutations(4);
int[] set = {0, 3,5,6,7};
int maxCnt = 5;
int[] a = new int[maxCnt+1];
Arrays.fill(a, 0);
int d = 15;
//setSumBT(a, 0, set, d, 0);
for(d = 1; d < 100; ++d)sumBT(a, 0, set, d, 0, maxCnt);
}
}
/*
:!javac Backtrack.java && java Backtrack
{ },{ 3 },{ 2 },{ 2 3 },{ 1 },{ 1 3 },{ 1 2 },{ 1 2 3 },
{ 1 2 3 4},[ 1 2 4 3]=={ 1 2 4 3},[ 1 3 2 4]=={ 1 3 2 4},[ 1 3 4 2]=={ 1 3 4 2},[ 1 4 2 3]=={ 1 4 2 3},[ 1 4 3 2]=={ 1 4 3 2},[ 2 1 3 4]=={ 2 1 3 4},[ 2 1 4 3]=={ 2 1 4 3},[ 2 3
1 4]=={ 2 3 1 4},[ 2 3 4 1]=={ 2 3 4 1},[ 2 4 1 3]=={ 2 4 1 3},[ 2 4 3 1]=={ 2 4 3 1},[ 3 1 2 4]=={ 3 1 2 4},[ 3 1 4 2]=={ 3 1 4 2},[ 3 2 1 4]=={ 3 2 1 4},[ 3 2 4 1]=={ 3 2 4 1
},[ 3 4 1 2]=={ 3 4 1 2},[ 3 4 2 1]=={ 3 4 2 1},[ 4 1 2 3]=={ 4 1 2 3},[ 4 1 3 2]=={ 4 1 3 2},[ 4 2 1 3]=={ 4 2 1 3},[ 4 2 3 1]=={ 4 2 3 1},[ 4 3 1 2]=={ 4 3 1 2},[ 4 3 2 1]=={
4 3 2 1},==NULL
before prune:
+3+5+6+7=21 level=4
+3+5+6=14 level=4
+3+5+7=15 level=4
+3+5=8 level=4
+3+6+7=16 level=4
+3+6=9 level=4
+3+7=10 level=4
+3=3 level=4
+5+6+7=18 level=4
+5+6=11 level=4
+5+7=12 level=4
+5=5 level=4
+6+7=13 level=4
+6=6 level=4
+7=7 level=4
=0 level=4
after prune:
+3+5+6=14 level=4
+3+5+7=15 level=4
+3+5=8 level=4
+3+6=9 level=4
+5+6=11 level=4
*/