关于回溯法和DFS做下总结:
在程序设计中有一类题目求一组解或者求全部解或者求最优解等系列问题,不是根据某种特定的规则来计算,而是通过试探和回溯的搜索来查找结果,通常都会设计为递归形式.
这类题本身是一颗状态树,当只有两种情况的时候则为二叉树,这棵树不是之前建立的,而是隐含在遍历过程中的.接下来根据一些题目来提高认识.
一.二叉状态树
题目:
说白了就是一个全遍历的过程,找出每一种可能的组合.对于123则有题目中的8种情况.
思路:
这样的全排列问题可以从元素本身入手,每一个元素只有两种状态,被选择和不被选择,那么就可以用一颗二叉树来表示
0表示未选择
那么递归程序就可以设计如下
public class Test10 {
static String str = "";
public static void main(String[] args) {
int[] A = {1,2,3};
DFS(A, 0);
}
/**
* @param a 存放要遍历的集合
* @param n 代表当前选择第n个元素
*/
private static void DFS(int[] a,int n){
if (n >= a.length) {
if (str.isEmpty()) {
System.out.println("$");//集合为空的话设置为$符号
}else {
System.out.println(str);
}
}else {
//不选择当前值
DFS(a, n+1);
//选择当前值
String strTemp = new String(str);//新建一个变量,为了回溯时可以退回到之前的数据
str = str+a[n];
DFS(a, n+1);
str = strTemp;//回溯到之前的str状态
}
}
}
二.四皇后问题(可扩展为n皇后)
四皇后如果用回溯法的话,会先生一个四叉树,和字典树比较类似了,如下图
可以看出来和上面一样的情况,对于四皇后,每一次选择都有四种放置棋子的方式,所以只要一个一个放置就可以找出全部可以放置的结果了.
对于有重复的,直接截枝,也就是没必要再往下寻找了
对于n皇后,把数组中的4改为n的值就好了
import java.util.Arrays;
public class Test11 {
private static int A[] = new int[4+1];//四皇后是4*4的格子,为了便于观看,0处不要
private static int count = 0;
public static void main(String[] args) {
DFS(1);
System.out.println(count);
}
/**
* @param n 表示要放置的行数
*/
private static void DFS(int n){
if (n >= A.length) {
count++;
printArr();
}else {
//对于每一个行,棋子都有四种放置状态
for (int j = 1; j < A.length; j++) {
if (check(n, j)) {//如果可以放置
A[n] = j;//放置棋子
DFS(n+1);//放置下一行
}
A[n] = 0;//回溯时,拿掉棋子
}
}
}
//打印数组
private static void printArr(){
System.out.println(Arrays.toString(A));
System.out.println("-------------------------------------");
}
/*
*检测是否符合条件
*j==A[i] 检测当前列上有没有棋子
*Math.abs(n-i) == Math.abs(j - A[i] 检测斜线上有没有棋子
*/
private static boolean check(int n,int j){
for (int i = 1; i < n; i++) {
if (j==A[i] || Math.abs(n-i) == Math.abs(j - A[i])) {
return false;
}
}
return true;
}
}
三.poj1416
题目:
现在你要研发一种新型的碎纸机,待粉碎的纸上面有一串数字,要求把纸粉碎成的几片上的数字的和尽量接近而不能超过给定的数字target number。比如:一片纸上的数字为12346,target number为50,那么就可以把纸粉碎为1、2、34、6,其加和为43,是所有粉碎方法中最接近50而不超过50的最优解。
解释:
题目换句话来理解就是有一串数字中间用+号来连接,使其结果小于或等于指定值的最优解.
EG:
题目 50 12346
那么可以分为以下几种情况
50 1+2+3+4+6=16
50 12+3+4+6=25
50 123+4+6=133
50 1+23+4+6=34
这道题就可以利用第一题二叉状态树想法来考虑,对于数字之间的空,可以选择插入+号和不插入加号
之前也做过这道题,思路不一样,以前的做法地址如下:
http://blog.csdn.net/u012706811/article/details/51245903
public class Test12 {
private static int A[] = {1,2,3,4,6};
private static int total = 50;
private static int max = 0;
private static String maxStr = null;
private static StringBuilder builder = new StringBuilder();
public static void main(String[] args) {
DFS(0, "");
System.out.println(max);
System.out.println(maxStr);
}
private static void DFS(int n,String sum){
if (n == A.length) {
int sumtemp = getSum(sum);
if (sumtemp <= total) {
if (sumtemp > max) {
max = sumtemp;//保存最大值
maxStr = sum;//保存最大值对应的串
}
}
}else {
if (getSum(sum+A[n]) <= total) {//剪枝操作
DFS(n+1,sum+A[n]);//不加+号
}
if (getSum(sum + "+" + A[n]) <= total) {
DFS(n+1, sum + "+" + A[n]);//加+号
}
}
}
/**
* 根据字符串计算出其数值大小
* @param str
* @return
*/
private static int getSum(String str){
String[] arr = str.split("\\+");//不能直接写+号,需要转义下
int sum = 0;
for (String temp : arr) {
if (temp != null && !"".equals(temp)) {
sum = sum + Integer.parseInt(temp);
}
}
return sum;
}
}
程序没问题,但是循环次数比以前做法多了几趟,还没找到原因.