图片来自acwing算法基础课的上课截图可以更好理解这些算法
DFS(回溯+剪枝)
DFS注意顺序
一条路走到黑即选择一个方法一直走到尾部,到尾部之后返回上一个节点判断另一个方案直到本节点可以访问的节点区别访问完毕
桉树型结构来理解的话类似前序遍历
树的前序遍历是指对于树中的每个节点,先访问该节点,然后递归地访问该节点的左子树,再递归地访问该节点的右子树。树的中序遍历是指对于树中的每个节点,先递归地访问该节点的左子树,再访问该节点,最后递归地访问该节点的右子树。树的后序遍历是指对于树中的每个节点,先递归地访问该节点的左子树,再递归地访问该节点的右子树,最后访问该节点。简单来说,前序遍历是根左右,中序遍历是左根右,后序遍历是左右根。这三种遍历的顺序不同,因此它们可以被用来完成不同的任务。例如,在二叉搜索树中,中序遍历可以按照顺序访问树中的所有节点,因此可以用来排序。后序遍历则可以用来计算树中节点的后继和前驱。
经典案例:
给定一个整数 nn,将数字 1∼n1∼n 排成一排,将会有很多种排列方法。
现在,请你按照字典序将所有的排列方法输出。
输入格式
共一行,包含一个整数 nn。
输出格式
按字典序输出所有排列方案,每个方案占一行。
数据范围
1≤n≤71≤n≤7
输入样例:
3
输出样例:
1 2 3 1 3 2 2 1 3 2 3 1 3 1 2 3 2 1
package ACWing.SearchAndGraphTheory.DFS; //842. 排列数字 import java.util.ArrayList; import java.util.Scanner; public class StaggeredNumerals { public static void main(String[] args) { Scanner sc=new Scanner(System.in); int n=sc.nextInt(); ArrayList<Integer> list=new ArrayList<>(); boolean[]arr=new boolean[n+1]; dfs(list,n,arr); } public static void dfs(ArrayList<Integer> list,int n,boolean[]arr){ if(list.size()==n){ for (Integer integer : list) { System.out.print(integer+" "); }System.out.println(); return; } for (int i = 1; i <= n; i++) { if (!arr[i]){ list.add(i); arr[i]=true; dfs(list,n,arr); //恢复现场回归到上次的状态 list.remove(list.size()-1); arr[i]=false; } } } }
经典8皇后问题
package ACWing.SearchAndGraphTheory.DFS; //843. n-皇后问题 import java.util.Scanner; public class NQueensRroblem { static String[][]arr=new String[20][20];//用截距来判断b=y+x所以有可能是20所以用20 static boolean[]col=new boolean[20]; static boolean[]dg=new boolean[20]; static boolean[]udg=new boolean[20]; static boolean[]row=new boolean[20]; public static void main(String[] args) { Scanner sc=new Scanner(System.in); int n=sc.nextInt(); for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { arr[i][j]="."; } } //dfs(0,n); dfs2(0,0,0,n); } public static void dfs(int u,int n){ if(u==n){ for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { System.out.print(arr[i][j]); } System.out.println(); } System.out.println(); return; } for (int i = 0; i < n; i++) { if(!col[i]&&!dg[u+i]&&!udg[n-u+i]){//精华所在用截距来判断对角线 arr[u][i]="Q"; col[i]=dg[u+i]=udg[n-u+i]=true; dfs(u+1,n); col[i]=dg[u+i]=udg[n-u+i]=false; arr[u][i]="."; } } } //解法二与上解法的差别在于解法二通过递归遍历了整个节点去判断是否要放皇后解法一是通过循环去判断一整行如何递归到下一行 public static void dfs2(int x,int y,int s,int n){ //设置边界条件 if(y==n){ y=0; x++; } if(x==n){ if(s==n){ for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { System.out.print(arr[i][j]); } System.out.println(); } System.out.println(); }return; } //不放皇后 dfs2(x,y+1,s,n); //放皇后 if(!row[x]&&!col[y]&&!dg[x+y]&&!udg[x-y+n]){//条件允许的情况下放皇后 arr[x][y]="Q"; row[x]=col[y]=dg[x+y]=udg[x-y+n]=true; dfs2(x,y+1,s+1,n); row[x]=col[y]=dg[x+y]=udg[x-y+n]=false; arr[x][y]="."; } } }
大体的思路一致没有具体的模板
弹出(边界)条件
进入符合条件的递归
恢复现场
刷题总结:
先判断暴力搜索的情况是否为有顺序的接下去搜索还是全排列,因为时间复杂度不够确定所以尽可能剪枝来缩小查询次数
如果是不按顺序搜索的话一般情况下便是这种模板(全排列)
例题://184. 虫食算 InsectFeedingCalculation
private static boolean dfs(int u) { if(u==n){ return true; } for (int i = 0; i < n; i++) { if(!st[i]){ path[qu[u]]=i; st[i]=true; if(check()&&dfs(u+1)){ return true; } path[qu[u]]=-1; st[i]=false; } } return false; }
如果是按顺序搜索的话一般便是这种模板(内含有一个遍历的过程必须按照顺序进行遍历组合)即按顺序进行选择来遍历所有的选择情况每种情况之后都必须要恢复现场
例题//187. 导弹防御系统 MissileDefenseSystem
public static void dfs(int[]arr,int su,int sd,int k,int n){ 弹出条件 第一种一种情况 第二种情况 . . . }
dfs求组合数类型:
先对要组合的数排序再进行dfs而且必须保证值的不降序性
洛谷例题:
package luogu.dfs; //P1036 [NOIP2002 普及组] 选数 import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Scanner; public class Selectron { static boolean[]st=new boolean[30]; static int n; static int m; static int sum=0; static List<Integer> set=new ArrayList<>(); public static void main(String[] args) { Scanner sc=new Scanner(System.in); n=sc.nextInt(); m=sc.nextInt(); int[]arr=new int[n]; for (int i = 0; i < n; i++) { arr[i]=sc.nextInt(); } Arrays.sort(arr); dfs(0,0,arr); int num=0; for (Integer integer : set) { if(sushu(integer)){ num++; } } Sy