《算法设计与分析》之回溯法

本文介绍了回溯法的基本概念及其在解决经典问题中的应用,包括N皇后问题、组合总和问题及全排列问题等,并提供了详细的代码实现。

目录

回溯法定义: 

回溯法的具体应用案例:

1、求解n皇后问题:

2、求解组合总和问题:

3、求解全排列问题:

总结:


回溯法定义: 

回溯法是对穷举法(也叫枚举法,蛮力法)的优化,即穷举过程中一旦发现某条路径不可能满足要求时就回溯(即不再沿这条路进行前进,回退到上一步),故效率一般比穷举法高。对深度优先搜索可用回溯法进行剪枝从而提高搜索效率。

回溯法的具体应用案例:

1、求解n皇后问题:

LeetCode 第52题 N皇后 ||

完整代码展示:

package leetcode_7;

public class T_52 {
	public static void main(String[] args) {
		System.out.println(totalNQueens(4));
	}
	public static int totalNQueens(int n) {
		int[] count=new int[1];
		int[] q=new int[n];
		f(0,n,count,q);
		return count[0];
	}
	private static void f(int i,int n,int[] count,int[] q) {
		if(i>=n) {
			count[0]++;
			return ;
		}
		for(int j=0;j<n;j++) {
			if(place(i,j,q)) {//如果条件成立,说明此路暂时可行,进行递归找下一个皇后位置
				q[i]=j;		//如果条件不成立,则直接跳过此路
				f(i+1,n,count,q);
			}
		}
	}
	private static boolean place(int i,int j,int[] q) {
		if(i==0)
			return true;
		int k=0;
		while(k<i) {
			if((j==q[k])||(Math.abs(i-k)==Math.abs(j-q[k])))
				return false;
			k++;
		}
		return true;
	}
}

2、求解组合总和问题:

LeetCode 第39题 组合总和

完整代码展示:

package leetcode_7;

import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

public class T_39 {
	public static void main(String[] args) {
		int[] a= {2,3,6,7};
		System.out.println(combinationSum(a,7));
	}
	public static List<List<Integer>> combinationSum(int[] candidates, int target){
		List<List<Integer>> ans=new LinkedList();
		Arrays.sort(candidates);
		for(int i=0;i<candidates.length;i++) {
			if(candidates[i]<=target) {
				List<Integer> p=new LinkedList();
				p.add(candidates[i]);
				f(candidates,target-candidates[i],i,ans,p);
			}
		}
		return ans;
	}
	private static void f(int[] a,int t,int i,List<List<Integer>> ans,List<Integer> p) {
		if(t==0) {
			ans.add(p);//已到达终点,不再此路径上继续走下去
			return ;
		}
		if(t<0||t-a[i]<0)
			return ;	//已不可能到达终点,退出此路径
		for(;i<a.length;i++) {
			if(t-a[i]>=0) {
				List<Integer> q=new LinkedList(p);
				q.add(a[i]);
				f(a,t-a[i],i,ans,q);
			}
			else		//后面情况不可能满足,剪枝
				break;
		}
	}
}

LeetCode 第40题 组合总和 ||

完整代码展示:

package leetcode_7;

import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

public class T_40 {
	public static void main(String[] args) {
		int[] a= {10,1,2,7,6,1,5};
		System.out.println(combinationSum2(a,8));
	}
	public static List<List<Integer>> combinationSum2(int[] candidates, int target){
		List<List<Integer>> ans=new LinkedList();
		Arrays.sort(candidates);
		for(int i=0;i<candidates.length;i++) {
			if(candidates[i]<=target) {
				if(i>0&&candidates[i]==candidates[i-1])//从发源地去重,可直接避免不必要的搜索
					continue;
				List<Integer> p=new LinkedList();
				p.add(candidates[i]);
				f(i,target-candidates[i],ans,p,candidates);
			}
		}
		return ans;
	}
	private static void f(int start,int t,List<List<Integer>> ans,List<Integer> p,int[] a) {
		if(t==0) {
			ans.add(p);//满足条件,保存答案,退出此路
			return ;
		}
		if(start>=a.length-1||t<0||t-a[start+1]<0)
			return ;//对于已经走完或已不可能满足要求的路径,退出
		for(int i=start+1;i<a.length;i++) {
			if(t-a[i]>=0) {
				if(i>start+1&&a[i]==a[i-1])
					continue ;//去重
				List<Integer> q=new LinkedList(p);
				q.add(a[i]);
				f(i,t-a[i],ans,q,a);
			}
			else//对于已不可能满足要求的路径直接退出
				break;
		}
    }
}

 LeetCode 第216题 组合总和 |||

完整代码展示:

package leetcode_7;

import java.util.LinkedList;
import java.util.List;

public class T_216 {
	static List<List<Integer>> ans=new LinkedList();
	static LinkedList<Integer> p=new LinkedList();
	public static void main(String[] args) {
		System.out.println(combinationSum3(3,9));
		System.out.println(combinationSum3(4,1));
	}
	public static List<List<Integer>> combinationSum3(int k, int n){
		ans=new LinkedList();//由于将ans定义为静态变量,所有值共享,
		int sum=0;	//故每次调用前应该把ans清0,避免多次调用此方法时后面答案受前面答案影响
		for(int i=1;i<=9;i++)
			sum+=i;
		f(k,1,n,sum);
		return ans;
	}
	private static void f(int k,int start,int n,int sum) {
		if(k==0) {//k为每个组合还需要数字的个数,k为0时,无论是否满足要求,都退出
			if(n==0)//若满足要求,则添加组合
				ans.add(new LinkedList(p));
			return ;
		}
		for(int i=start;i<=9;i++) {
			if(n-i>=0&&n-i-sum<=0) {
				p.add(i);
				sum-=i;
				f(k-1,i+1,n-i,sum);
				p.removeLast();//将刚添加的值删除
			}
			else//当已不可能满足要求时,直接退出
				break;
		}
	}
}

3、求解全排列问题:

LeetCode 第47题 全排列 ||

package leetcode_7;

import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

public class T_47 {
	static List<List<Integer>> ans;
	static boolean[] map;
	static List<Integer> path;
	public static void main(String[] args) {
		int[] a= {1,1,2};
		System.out.println(permuteUnique(a));
	}
	public static List<List<Integer>> permuteUnique(int[] nums){
		Arrays.sort(nums);
		ans=new LinkedList();
		path=new LinkedList();
		map=new boolean[nums.length];
		f(nums);
		return ans;
	}
	private static void f(int[] nums) {
		if(path.size()==nums.length) {
			ans.add(new LinkedList(path));
			return ;
		}
		int p=-11;
		for(int i=0;i<nums.length;i++) {
			if(map[i])
				continue;//保证已经在path中的元素不会再被取到
			if(nums[i]==p)
				continue;//保证本次遍历的元素值不重复
			map[i]=true;
			p=nums[i];
			path.add(nums[i]);
			f(nums);
			path.remove(path.size()-1);
			map[i]=false;
		}
	}
}

总结:

总之,回溯法就是提前筛选出不满足要求的情况,一般用于DFS中,称为剪枝。相比于先搜索所有答案再从中挑选符合要求的答案,回溯法是在搜索过程中就将不符要求以及重复答案剔除,大大提高了效率。

评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值