2017蓝桥杯方格分割,理解DFS深度优先搜索的使用DFS例题DFS模板深入了解递归思想暴力解题
DFS深度优先搜索——暴力
1.暴力解算法题
所谓暴力法(Brute force),是把所有可能的情况都罗列出来,然后逐一检查,从中找到答案。这种方法简单、直接,不玩花样,利用了计算机强大的计算能力。
所有问题都能用暴力法来求解。
暴力法也往往是“低效”的代名词。不过相对其他“高效”算法,暴力法不仅简单且编码量一般都更短更好写。所以当拿到一个题目后,首先想想如何用暴力法解决。如果暴力法的代码能满足题目要求的时间和空间限制,就用暴力法。若暴力法不能满足要求,也可以把它当成对照,用来验证高级算法的正确性。
暴力法的主要手段是搜索,而搜索的主要技术是 BFS 和 DFS。掌握搜索技术是学习算法的基础。搜索时,具体的问题会有相应的数据结构,例如队列、栈、图、树等,需要我们能熟练地在这些数据结构上进行搜索的操作。
2.题目:方格子分割
原题:
给定一个 6×6 的方格,沿着格子的边线剪开成两部分。 要求这两部分的形状完全相同。
请问一共有几种分割方法?
![]() |
---|
![]() |
上面是两个分割例子 |
思路:
6x6的正方形,每个点模拟成一个坐标,正方形看做二维数组,square[7] [7]
每一个切割结果必回经过中心点square[3] [3],只要确定半条到达边界的分割线,就能根据这半条对称画出另外半条。另外,题目要求旋转对称属于同一种分割法,因为结果是中心对称的,搜索出来的个数除以 4 即可。
中心点是 (3, 3),从 (3, 3)出发,向右、向左、向上、向下,四个方向 DFS 即可。
代码:
public class Main {
private static int n = 6; //6x6的正方形,n必须是偶数
private static int ans = 0;
private static int[][] dir = {{1,0},{-1,0},{0,1},{0,-1}}; //向 右/左/上/下 移动一位
private static boolean[][] visit = new boolean[n+1][n+1]; //标记该点是否被访问过
static int cnt = 0; //记录递归次数
public static void main(String[] args) {
int cn = n/2;
visit[cn][cn] = true;
dfs(cn,cn);
if (ans==1){
System.out.println("符合结果数:"+ans+"递归次数:"+cnt);
}else {
System.out.println("符合结果数:"+ans/4+"递归次数:"+cnt);
}
}
private static void dfs(int x,int y){
cnt++;
if (x == 0 || y == 0 || x == n || y == n){
ans ++;
return;
}
visit[x][y] = true;
visit[n - x][n - y] = true;
for (int i = 0; i < 4; i++) { //四个方向,0,1,2,3
int nx = x + dir[i][0]; //第一次,(3,3)移动至(4,3)
int ny = y + dir[i][1];
if (nx < 0 || nx > n || ny < 0 || ny > n)
continue;
if (!visit[nx][ny])
dfs(nx,ny);
}
visit[x][y] = false; //还原
visit[n - x][n - y] = false;
}
}
改进:
题目给出的是6x6的正方形,为了能找到正方形的中心点,正方形边长n必须是偶数(2,4,6,8..)
中心点A坐标就是(n/2,n/2)
对中心点A进行DFS,
注意:当n=2时,只有一个结果,此时需直接输出1,不能再对结果除以四。
总结:
方形切割中的DFS,dir[] []记录每个点的四种操作(二维坐标系下的上、下、左、右),visit[n+1] [n+1]记录这点是否被访问过,如果被访问过就不需要再对这个点进行dfs递归操作。
本示例代码中dir[] []为
private static int[][] dir = {{1,0},{-1,0},{0,1},{0,-1}};
对一个点先右移,一直右移到正方形边缘(x=n正方形边长),触发递归结束条件,return返回。然后进行其他三个方向的递归操作。
举个例子
对于4x4的正方形,中心点(2,2)。
for循环,四个方向,依次为右(3,2)、左(1,2)、上(2,3)、下(2,1)
因为dir[0]是[1,0]代表右移,右移到(3,2),再右移到(4,2)到达正方形右边缘,return返回。
因为dir[1]是[1,0]代表左移,所以再对(3,2)左移回到中心点(2,2),visit[2] [2]已被标记,跳过。
因为dir[1]是[0,1]代表上移,所以再对(3,2)上移到点(3,4),visit[3] [4]未被标记,对(3,4)进行dfs。
for循环结束,代表点(3,2)的右、左、上、下四个点都递归判断结束,将visit[3] [2]和visit[1] [2]还原为false
对于中心点(2,2)的四个方向,右(3,2)递归结束,还需递归(1,2)、(2,3)、(2,1)
最终全部结束,返回ans
3.回顾递归
计算斐波那契数列的第20项
//不使用递归
int[] fib = new int[25];
fib[1]=1;
fib[2]=1;
for (int i=3;i<=20;i++){
fib[i]=fib[i-1]+fib[i-2];
}
System.out.println(fib[20]);
//使用递归
public static int fibResult(int n){
cnt++; //cnt统计执行了多少次递归
if(n==1||n==2) return 1; //到达终点,即最后的小问题
return fibResult(n-1)+fibResult(n-2); //调用自己2次,复杂度O(2n)
}
为避免递归时重复计算子问题,可以在子问题得到解决时,就保存结果,再次需要这个结果时,直接返回保存的结果就行了。这种存储已经解决的子问题结果的技术称为“记忆化(Memoization)”。记忆化是递归的常用优化技术,以下是用“记忆化+递归”重写的斐波那契:
public static int fibResult2(int n){
cnt2++;
if(n==1||n==2){
data[n]=1;
return data[n];
}
if(data[n]!=0){
return data[n];
}
data[n]=fibResult2(n-1)+fibResult2(n-2); //将这次递归算出的结果存储到data[]中
return data[n];
}
4.递归解决全排列
在C++中,STL的next_permutation()函数是求排列的系统函数。
什么是全排列
1 2 3
全排列:
1 2 3
1 3 2
2 1 3
2 3 1
3 2 1
3 1 2
Tips:
三个数全排列,第一个数先与自己换,再与第二个数换,再与第三个数换。
与自己换,进入第一层递归,再对后面的依次进行相同的操作
与第二个换,进入第二层递归...
第一层递归输出结果是1 X X
第二层递归输出结果是2 X X
第三层递归输出结果是3 X X
自写全排列方法
/**
* 使用递归解决全排列
*/
public class pailie {
static int[] a={1,2,3,4,5,6,7,8,9,10,11,12,13};
public static void main(String[] args) {
System.out.println("1-3的全排列:");
dfs(0,2);
}
//从第s个数开始到第t个数结尾的全排列
public static void dfs(int s,int t){
if(s==t){
for (int i=0;i<=t;++i){
System.out.print(a[i]+" ");
}
System.out.println();
return;
}
for (int i=s;i<=t;i++){
swapFunc(s,i); //把当前第1个数与后面所有数交换位置,注意他先要与自己换
dfs(s+1,t);
swapFunc(s,i); //恢复,用于下次交换
}
}
public static void swapFunc(int num1,int num2){
int temp = a[num1];
a[num1]=a[num2];
a[num2]=temp;
}
}
不能按从小到大的顺序打印排列。而我们遇到的题目,常常需要按顺序输出排列。
/**
* System.out.println("1-3全排列+结果由小到大");
* dfs3(0,3);
*/
static int[] a={1,2,3,4,5,6,7,8,9,10,11,12,13};
static boolean[] vis = new boolean[20]; //记录第i个数是否用过
static int[] b = new int[20]; //生成的一个全排列
private static void dfs3(int s, int t) {
if (s==t){
for(int i=0;i<t;i++){
System.out.print(b[i]+" ");
}
System.out.println();
return;
}
for(int i=0;i<t;i++){
if(!vis[i]){
vis[i]=true;
b[s]=a[i];
dfs3(s+1,t);
vis[i]=false;
}
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!