2017蓝桥杯方格分割,理解DFS深度优先搜索的使用DFS例题DFS模板深入了解递归思想暴力解题

DFS深度优先搜索——暴力

1.暴力解算法题

所谓暴力法(Brute force),是把所有可能的情况都罗列出来,然后逐一检查,从中找到答案。这种方法简单、直接,不玩花样,利用了计算机强大的计算能力。

所有问题都能用暴力法来求解。

暴力法也往往是“低效”的代名词。不过相对其他“高效”算法,暴力法不仅简单且编码量一般都更短更好写。所以当拿到一个题目后,首先想想如何用暴力法解决。如果暴力法的代码能满足题目要求的时间和空间限制,就用暴力法。若暴力法不能满足要求,也可以把它当成对照,用来验证高级算法的正确性。

暴力法的主要手段是搜索,而搜索的主要技术是 BFS 和 DFS。掌握搜索技术是学习算法的基础。搜索时,具体的问题会有相应的数据结构,例如队列、栈、图、树等,需要我们能熟练地在这些数据结构上进行搜索的操作。

2.题目:方格子分割

原题:

给定一个 6×6 的方格,沿着格子的边线剪开成两部分。 要求这两部分的形状完全相同。
请问一共有几种分割方法?

image-20220823134137567
image-20220823134151870
上面是两个分割例子

思路:

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;
            }
        }
    }
posted @   BAISHUN66  阅读(119)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
点击右上角即可分享
微信分享提示