DFS算法简单剖析 | 全排列数的生成

DFS算法简单剖析

深度优先搜索算法(Depth First Search),简称DFS,是一种用于遍历或搜索树或图的算法。 沿着树的深度遍历树的节点,尽可能深的搜索树的分支。当节点v的所在边都己被探寻过或者在搜寻时结点不满足条件,搜索将回溯到发现节点v的那条边的起始节点。整个进程反复进行直到所有节点都被访问为止。属于盲目搜索,最糟糕的情况算法时间复杂度为O(n!)。

基本模板

int check(参数)
{
    if(满足条件)
        return 1;
    return 0;
}
 
void dfs(int step)
{
        判断边界
        {
            相应操作
        }
        尝试每一种可能
        {
               满足check条件
               标记
               继续下一步dfs(step+1)
               恢复初始状态(回溯的时候要用到)
        }
}   

数的全排列问题

题目要求:给定一个数n,列出n的全排列。

分布解析:①从后往前排列:先排列后m (m<n) 位数,输出所有结果后排列后 m+1 位数;

​ 前n-m位数永远是有序的(从小到大升序)。

​ ②边界条件:设 m 表示倒数第 m 个数字即目前所在的数字位数,当 m>n 即 m=n+1时,

​ 由于不存在第 m+1 个数字,此时排列结束,函数体执行完毕。

​ ③check条件:给第 i 个数一个标记值 flag ,当 flag 为 0 时表示改数字还未输出过,

​ 当 flag 为1时表示该数字已经输出。

参考代码:

#include<stdio.h>
#include<stdlib.h>

int flag[25],n,vim[25];//flag用作标记数 vim用作存储数字的盒子
void dfs(int x){
	if(x==n) { //边界条件:这里是x==n因为参数从0开始而不是1
		int j;
		for(j=0;j<n;j++) printf("%d ",vim[j]); //输出存在盒子里的vim值
		printf("\n");
	}
	else{
		int i;
		for(i=1;i<=n;i++){
			if(!flag[i]){//判断i是否已经存储
				vim[x]=i;//存储i 作为第x个数字
				flag[i]=1;//标记i已经被存储
				dfs(x+1);//排列第x+1个数字
				flag[i]=0;//i完成输出 需重新重置flag值
			}
		}
	}
}
int main(){
	scanf("%d",&n);
	dfs(0);
	return 0;
}

代码解析:

我们以输入n=3为例,简要分析代码如何运行。
    
    输入n=3,此时全局变量n被赋值,全局数组flag[]、vim[]均未赋值,即值为0;
    
    执行代码: scanf("%d",&n);
    变量值监控: n=3,
               flag[]={0},
               vim[]={0}.
    
    执行函数dfs,此时为满足边界条件x==3,进入else语句的循环中;
        
    执行代码: dfs(0); 
             int i;
    变量值监控: n=3,
               flag[]={0},
               vim[]={0},
               i.
    
    i=1时,由于flag[1]=0,将1存入vim[0]等待输出,同时标记flag[1]表示1已经被存储;
        
    执行代码: vim[0]=i;
		     flag[1]=1;
    变量值监控: n=3,
               flag[]={0,1,0,0,...},
               vim[]={1,0,0,...},
               i=1.
    
    继续排列面第二个数字,此时x=1也未满足边界条件,进入循环,i=1时,由于flag[1]=11已经被存储,
    从而继续循环i++,i=2时flag[2]=0即未被存储,重复上述存储1时的步骤;
    
    执行代码: i++;
        	 vim[1]=2;
		     flag[2]=1;
    变量值监控: n=3,
               flag[]={0,1,1,0,...},
               vim[]={1,2,0,...},
               i=2.
                   
    进入第三重循环,与前两者类似,存入数字3,继续执行 dfs(x+1),但由于此时x=2+1=3满足边界条件,不再进行     存储,我们从vim[0]开始输出,得到第一个排列:1 2 3;
    
    执行代码: i++;
        	 vim[2]=3;
		     flag[3]=1;
             if(x==n) {
		          int j;
		          for(j=0;j<n;j++) printf("%d ",vim[j]); 
		          printf("\n");
	          }
    变量值监控: n=3,
               flag[]={0,1,1,1,...},
               vim[]={1,2,3,...},
               i=3.   
    输出流:    1 2 3
                   
    此时我们将flag[3]的值回调为0,回到第二个循环,将flag[2]的值回调为[0],执行循环i++,此时i=3,将3存     入vim[1],同时标记flag[3]=1,继续进入 dfs(3),由于此时flag[1]=1,1已被存储,执行循环,将2存入       vim[2]同时标记flag[2]=1,达到边界条件,进行第二组输出,得到排列:1 3 2;
    
    执行代码: flag[3]=0;
             flag[2]=0
        	 vim[1]=3;
		     flag[3]=1;
		     dfs(3):
                 flag[2]=1;
                 vim[2]=2;
             if(x==n) {
		          int j;
		          for(j=0;j<n;j++) printf("%d ",vim[j]); 
		          printf("\n");
	          }
    变量值监控: n=3,
               flag[]={0,1,1,1,...},
               vim[]={1,3,2,...},
               i=3.   
    输出流:    1 3 2
    
    此时我们已经基本明白函数执行的思路,即从最后一个数字开始不断往前进行递推,保持前k个数字的有序性,对后面     的n-k个数进行全排列,通过循环输出不同序列,在循环中通过vim存储要输出的数字,通过flag标记某个数字是否     已经完成存储。接下来返回第一个循环,即初始化flag[1]的值为[0],执行i++循环,存入2作为vim[0],得到序     列:2 1 32 3 1 ,重复上述步骤,得到3作为vim[0]的两个序列:3 1 23 2 1.
    
    输出结果:   1 2 3
               1 3 2
               2 1 3
               2 3 1
               3 1 2
               3 2 1                

拓展练习:

leetcode第200题 岛屿数量 难度:中等

问题描述:给你一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,请你计算网格中岛屿的数量。

​ 岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。

​ 此外,你可以假设该网格的四条边均被水包围。

示例1:

输入:grid = [
  ["1","1","1","1","0"],
  ["1","1","0","1","0"],
  ["1","1","0","0","0"],
  ["0","0","0","0","0"]
]
输出:1

示例2:

输入:grid = [
  ["1","1","0","0","0"],
  ["1","1","0","0","0"],
  ["0","0","1","0","0"],
  ["0","0","0","1","1"]
]
输出:3

Hint:

  • m == grid.length
  • n == grid[i].length
  • 1 <= m, n <= 300
  • grid[i][j] 的值为 '0''1'

参考代码:

class Solution {
private:
    void dfs(vector<vector<char>>& grid, int r, int c) {
        int nr = grid.size();
        int nc = grid[0].size();

        grid[r][c] = '0';
        if (r - 1 >= 0 && grid[r-1][c] == '1') dfs(grid, r - 1, c);
        if (r + 1 < nr && grid[r+1][c] == '1') dfs(grid, r + 1, c);
        if (c - 1 >= 0 && grid[r][c-1] == '1') dfs(grid, r, c - 1);
        if (c + 1 < nc && grid[r][c+1] == '1') dfs(grid, r, c + 1);
    }

public:
    int numIslands(vector<vector<char>>& grid) {
        int nr = grid.size();
        if (!nr) return 0;
        int nc = grid[0].size();

        int num_islands = 0;
        for (int r = 0; r < nr; ++r) {
            for (int c = 0; c < nc; ++c) {
                if (grid[r][c] == '1') {
                    ++num_islands;
                    dfs(grid, r, c);
                }
            }
        }

        return num_islands;
    }
};

复杂度分析:

时间复杂度:O(MN),其中 M 和 N 分别为行数和列数。

空间复杂度:O(MN),在最坏情况下,整个网格均为陆地,深度优先搜索的深度达到 MN。

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Super2121_Yolo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值