dfs的递归实现
了解什么是递归
简而言之,递归就是函数自身的反复调用。
先用一道例题(斐波那契数列)引入:F(0)=0,F(1)=1,F(n)=F(n - 1)+F(n - 2)(n ≥ 3,n ∈ N*)。
1、1、2、3、5、8、13…求第n个数字的值。
递归写法:
int fib(int n){
if (n == 1){
return 1;
}
if (n == 2){
return 1;
}
return fib(n - 1) + fib(n - 2);
}
过程分析:如果是前两个数字直接返回1,后边的函数会调用自身进行求值,例如第三个数值fib(3)会返回fib(2)+fib(1)的值,再次调用fib(1)和fib(2),发现他们两个返回值是1,然后分别把返回的值返回fib(3)里,结果是return2,最终结果就是2。第五个数值fib(5)直接返回fib(4)+fib(3),再次调用自身分别施行fib(4)和fib(3),在fib(4)里又会分别调用fib(3)和fib(2),再一直调用,直到调用的是fib(1)和fib(2)的时候会把返回值1返回到上一级函数里,上一级函数得到确切返回值再返回到上上一级中,直到返回到最初的fib(4),函数执行结束得到结果。
我们可以看到,该函数虽然反复调用,但是最终都会有一个返回尽头值(即结束条件),带着这个尽头值一直返回上一级,直到返回最终结果。
这个结束条件和反复调用就是递归的关键。
dfs用数据结构里的知识表示的话属于树的先序遍历:一直访问子节点,直到访问到叶子节点结束。斐波那契跟树的遍历不同的是需要将最终结束条件的值一直返回到上一级,直到返回到根节点结束,而树只需要访问到结束条件就可以了。然而在实际问题中,不会有人让你遍历树的节点,而是问题条件就是隐含的树的节点,需要你自己构建树,进而根据所构建出来的树来解决问题。
数的排列组合就很适合来了解dfs思想
首先是全排列
对全排列构建树如图所示
1.由图可以看出终止条件就是完成了三次调用这里可以在函数里设置一个计数的参数来控制调用次数,一旦符合就输出一种情况的结果并返回。2.其次函数里需要有一个数组来记录每次的情况,3。由于每个数字只能使用一次,因此在反复调用的时候需要有一个标记数组来标记已经使用过的数字。
import java.util.Scanner;
public class 组合的输出_排列 {
static int a[]=new int[25];//存储数组
static int visited[]=new int[25];//标记数组
static int n=0;//全局变量
static int m=0;
static int cnt=0;//全局变量,计数
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt();//n的全排列
m = sc.nextInt();//组合写法需要
dfs(1,n);//全排列的写法,第一次调用,初始传参数1
System.out.println(cnt);//统计一共有多少种
System.out.println("************");
dfs(3,1,5,1);//组合的写法,C5 3,五个种选三个
System.out.println("***********");
}
public static void dfs(int m, int start, int end,int t) {
//组合按递增顺序输出,
if (t>m) {
for (int i=1;i<=t;i++)
System.out.printf("%3d",a[i]);
System.out.println();
return;
}
for (int i = start; i <= end; i++) {//这个没有标数组,
// 原因可能是循环初始化就是形参,一直在变化??一个重要原因就是满足递增,
a[t]=i;
dfs(m , i + 1, end,t+1);//要想递增,传参的start是下一个值
}
}
public static void dfs(int t,int n) {
//全排列的写法
//t是控制调用次数,也就是每组结果数字的个数,这里是3的全排列,那么个数就是3。输出123等等
if (t>n) {
for (int i=1;i<=n;i++){
System.out.print(" "+a[i]);
}//满足三次调用结束条件后打印出来并返回上一级重新寻找
System.out.println();
cnt++;
return;
}
for (int i = 1; i <=n; i++) {//循环代表每一层开始可以使用的数值
if (visited[i]==0){//如果没访问过才能被记录,把重复使用过的过滤掉
visited[i]=1;
a[t]=i;//直接用i记录,所以i从1开始,如果是0的话无效
dfs( t + 1,n);//下一个数递归调用
visited[i]=0;//!!十分关键,调用完数组已经被覆盖,防止下一种情况无法判断,需要把标记再修改回去,这种做法叫回溯。
}
}
}
}
排列和组合不同的结果,用的是同一个思想,都是递归遍历,都有终止返回条件。可以设置断点调试查看其中的微妙变化。
类似的问题还有很多,有的需要带一些值返回。
放几个洛谷题目练习:
组合的输出
全排列
选数