关于DFS(深度优先搜索算法)

基本概念

    深度优先搜索遍历类似于树的先根遍历,是树的先根遍历的推广,在算法题中经常用到,在优化图算法时,也非常方便,就像是在走迷宫过程中,当碰到岔路口时“深度”作为前进的关键词,不碰到死胡同就不回头的坚持精神,因此把这种搜索的方式称为 深度优先搜索(Depth First Search, DFS)。

算法思想

    深度优先搜索会走遍所有路径,并且每次走到死胡同就代表 一条完整路径 的形成。这就是说,深度优先搜索是 一种枚举所有完整路径以遍历所有情况的搜索方法。

    回溯法也叫试探法,它是一种系统地搜索问题的解的方法。回溯算法的基本思想是:从一条路往前走,能进则进,不能进则退回来,换一条路再试。

    使用递归可以很好地实现深度优先搜索。当然也可以使用非递归的方法实现DFS,但是非递归的方法一般情况下会比递归的方法要麻烦。

树图表示


如树图所示,当从1处开始深度优先搜索时
先进行的是:

1 2 4 8

8 处树图无法向下延伸时,退至 4 处走下一条分支 9,此时 1 2 4 9 这条路也无法向下延伸,便又退至 4 处,这时再无其他分支可走,便又回退至 2 (回退就是所谓的回溯)

此时走过的路用颜色标记,如图:(走过的路再标记后不再走就是所谓的剪枝

2 处走剩下的分支 5,无路可走时返回至 1 处,再走 1 处的另一条分支 3,再向下至 6,无路可走时返回至 3 处,走 3 的另一条分支 7,此时已全部遍历完(无路可走),便从 7 - 3 - 1 一路返回至起点。

基本模板

bool check() {
    if(满足条件) {
        return true;
    }
    return false;
}

void dfs(int step) {
    判断边界条件(找到 || 找完) {
        结束
        return 	返回至上一次搜索处    
    }
	
        遍历所有的可能性 {
       		满足check的条件
        	标记
        	继续进行下一步dfs(step + 1)
        	去掉标记(即回溯)
        }
    }
}

例题分析

(1)全排列问题

题目描述:
    输出自然数 1 到 n 所有不重复的排列,即 n 的全排列,要求所产生的任一数字序列中不允许出现重复的数字。

输入格式:
    一个整数 n

输出格式:
    由 1 ~ n 组成的所有不重复的数字序列,每行一个序列。

详情见博客 blog.)

分析:

假设有 n个字符要排列,把他们依次放到 n个箱子中;
先要检查箱子是否为空,手中还有什么字符,把他们放进并标记;
放完一次要恢复初始状态,当到 n+1个箱子时,一次排列已经结束。

源代码:

#include<stdio.h>
const int N = 20;
int n;
bool st[N];		
int path[N];

void dfs(int u) {
	if (u == n) {		//当所要求的的 n 位数填满后进行输出
		for (int i = 0; i < n; i++) {
			printf(" %d",path[i]); 	
		}
		printf("\n");
		return;			//这个return非常重要
        				//return的作用是返回上一步
	}
	for (int i = 1; i <= n; i++) {
		if (!st[i]) {
			path[u] = i;		//将第 u 位填入数字 i
			st[i] = true;		//标记数字 i 已经被使用
			dfs(u + 1);			//对下一位进行填数
			st[i] = false;		//填完 n 位数后,将已标记过的数清空 ,即回溯
		}
	} 
}

int main() {
	scanf("%d",&n);
	dfs(0);		//传入u为 0,代表从第一位开始填数
	return 0;
}


(2)N皇后问题

题目描述:
    一个如下的6×6 的跳棋棋盘,有六个棋子被放置在棋盘上,使得每行、每列有且只有一个,每条对角线(包括两条主对角线的所有平行线)上至多有一个棋子。


    上面的布局可以用序列 2 4 6 1 3 5 来描述,第 i 个数字表示在第 i 行的相应位置有一个棋子,如下:

行号 1 2 3 4 5 6

列号 2 4 6 1 3 5

    这只是棋子放置的一个解。请编一个程序找出所有棋子放置的解。

    并把它们以上面的序列方法输出,解按字典顺序排列。

请输出前 3 个解。最后一行是解的总个数。

输入输出样例:
在这里插入图片描述

分析

还是套用基本DFS的模板;
在进行标记判断时,应分别开3个bool数组对应着 列,正对角线, 反对角线 是否存在其他皇后(因为是遍历一行进行填入一个皇后,所以不用再考虑行);
每一次填完到 n 个皇后后,进行输出数组path[ ]中皇后所保存的位置。

源代码

#include<stdio.h>
const int N = 26;
int n;
int ans;			//可行的填法 
int path[N];		//保存(一轮)各个皇后所在每一行的位置 
bool col[N], diag[N], udiag[N];			//col[]代表列,diag[]代表正对角线,udiag[]代表反对角线 
void dfs(int u) {
	if (u == n) {						//表示当n行都成功填入皇后后就输出 
			ans++;						//可行的填法 
			if ( ans <= 3)				//以下 7行代表输出格式 
			for (int i = 1; i <= n; i++) {
			printf("%d ",path[i]);
		}
		if (ans <= 3)
		printf("\n");
		return;							//返回至上一次填入皇后处 
	}
	for (int i = 1; i <= n; i++) {
		if (!col[i] && !diag[u + i] && !udiag[n - u + i]) {			//当列,正对角线,反对角线都不存在其他皇后 
			path[u + 1] = i;										//存入第 u + 1行皇后的位置 
			col[i] = diag[u + i] = udiag[n - u + i] = true;			//判断对角线和反对角线时的 u + i和 n - u + i运用到了一次函数图像的性质 
			dfs(u + 1);												//进行下一行的递归 
			col[i] = diag[u + i] = udiag[n - u + i] = false;		//回溯 
		}
	}	
}

int main() {
	scanf("%d",&n);
	dfs(0);				
	printf("%d",ans);
	return 0;
}

总结

    dfs深度优先搜索)的本质就是递归。在有些题(例如油田问题)中还可能会涉及到坐标,进行上下左右各个方向不断前进探索,但它始终都是沿着一条路不断往前走,直到这条路不通或者已经探索完毕再return返回至上一次探索停止处。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值