深度优先遍历的8个简单小例题

最近在看一些算法书,查缺补漏,什么?OJ?表示只做过几十个水题,现在开始正式看算法,希望早日脱离鱼塘。

本博文面向数据结构已经入门的人

引用《啊哈!算法》


从前上课的时候只知道深搜是用来搜图和二叉树的,现在才知道原来深搜的应用如此灵活

1.输入一个数n,输入1~n的全排列

比如123 132 213 231 312 321

先上代码

#include "stdafx.h"
#include <cstdio>
#include "iostream"
using namespace std;
typedef struct Node
{
	int num=0;
	bool outHand = false;
}Node;
int n;
Node list[10];
void DFS(int step)
{
	if (step == n+1)    //判断边界,此时所有数都放到了容器中
	{
		for (int i = 1;i <= n;i++)
			cout << list[i].num;       //输出容器中的状态
		cout << endl;return;   //返回到堆栈中上一个递归状态
	}

	for (int i = 1;i <= n;i++)//尝试每一种可能
	{
		if (list[i].outHand == false)//前提是这个n还没有放进去
		{
			list[step].num = i;   //把1到n放到容器list中
			list[i].outHand = true;    //改变状态
			DFS(step + 1);    //继续递归下一步
			list[i].outHand= false;//下一步完事后,肯定是要换一个数的,所以我们把之前的数收回去
		}
	}
	return;//所有可能都完事的时候,返回。
}
void main()
{
	cin >> n;
	DFS(1);
}
系统会对每一次递归的函数进行一次进堆栈操作,return即是返回到上一次调用的函数那里,也就是递归的上一次函数的状态。

也就是栈帧。

说个简单的情况,比如n=3      也就是三个位置ABC(隐含一个D,D为空)

第一次走到A,在A上尝试每一种可能,先把1放上去,到B,执行for循环,1已经放过了,循环到2,把2放上去,到C,把3放上去。再调用DFS(step+1),也就是到D,此时满足边界条件,输出容器状态,1,2,3,return到上一个递归状态,也就是到走到C时,此时DFS(step+1)执行完毕,开始执行list[3].outHand=false;for循环完毕,return;返回到走到B时的状态,此时DFS(step+1)执行完毕,开始执行list[2].outHand=false;也就是说此时,2和3都拿到了手中,然后for循环走到i=3,把3放到B中,执行DFS(step+1),走到C,在C中尝试每一种可能,因为1在A,3在B,所以2在C,再走到D,到达边界,输出132,返回,后面213 231情况类似,都是递归。

这就是深度优先遍历的一个简单的小程序。

这个程序在递归深度比较大的时候,算法效率会变得非常的低,所以我们可以自己定义一个栈来模拟递归时函数进入系统堆栈的情况,不过这个问题后面再提

2.例题1的变种,寻找满足ABC+DEF=GHI的情况有多少

比如173+286=459

看到这个题,我想大部分新手第一反应就是暴力循环,把所有可能的数字情况1到9全部枚举一遍,满足情况就输出,嗯,九重循环O(n^9)

尝试下深搜

上代码

#include "stdafx.h"
#include "iostream"
using namespace std;
bool outHand[10] = { false };
int list[10] = { 0 };
int total = 0;
void DFS(int step)
{
	if (step == 10)   //结束条件1,把9个数全填进去了
	{
		if (list[1] * 100 + list[2] * 10 + list[3]     //结束条件2,满足等式
			+ list[4] * 100 + list[5] * 10 + list[6]
			== list[7]*100 + list[8]*10 + list[9])
		{
			total++;
		}
		return;

	}
	for (int i = 1;i <= 9;i++)  //尝试每一种可能
	{
		if (outHand[i] == false)
		{
			list[step] = i;    //将这种情况放到容器中
			outHand[i] = true;  //标记
			DFS(step + 1);  //开始填下一个空
			outHand[i] = false;//再拿出来
		}
	}
	return;
}
void main()
{
	DFS(1);
	cout << total / 2;
}

把例题1稍微改一点就行了


3.二维数组地图的寻路最短步数

(下标从1开始)

0 0 1 0

0 0 0 0

0 0 1 0

0 1 0 0

0 0 0 1

这样的一个4*5的矩阵,1代表路障,0可以走,从(1,1)出发,到(4,3)的位置

#include "stdafx.h"
#include "iostream"
using namespace std;
int map[6][5] = { 0 };
bool visited[6][5] = { false };
int min = 999999;
void DFS(int x, int y, int step)
{
	int tx=0, ty=0;
	int next[4][2] = { {0,1},{1,0},{0,-1},{-1,0} };
	if (x == 4 && y == 3)    //如果到达目标位置,输出回溯
	{
		if (step < min)
			min = step;
		return;
	}
	for (int k = 0;k < 4;k++)    //对四个方向都尝试一下
	{
		tx = x+next[k][0];   //tx ty代表下一次要走的位置
		ty = y+next[k][1];
		if (tx < 1 || tx>5 || ty < 1 || ty>4)  //如果坐标越界,返回
			continue;
		if (visited[tx][ty] == false && map[tx][ty] == 0)   //如果这个坐标可以走,没有障碍没有访问过
		{
			visited[tx][ty] = true;   //跟例题1一样
			DFS(tx, ty, step + 1);
			visited[tx][ty] = false;
		}
	}
	return;
}
void main()
{
	map[1][3] = 1;
	map[3][3] = 1;
	map[4][2] = 1;
	map[5][4] = 1;
	visited[1][1] = true;
	DFS(1, 1, 0);
	cout << min;
}
总的来说,跟例题一十分相似,区别就是搜索的容器从一维数组变成了二维数组,找一下区别,面对二维数组,首先地图是二维的,此外访问标记也是二维的,对容器进行尝试的时候从简单的数字加减变成了遍历四个方向的单位向量。其他情况类似。

4.求岛屿面积

1,2,1,0,0,0,0,0,2,3

3,0,2,0,1,2,1,0,1,2

4,0,1,0,1,2,3,2,0,1

3,2,0,0,0,1,2,4,0,0

0,0,0,0,0,0,1,5,3,0

0,1,2,1,0,1,5,4,3,0

0,1,2,3,1,3,6,2,1,0

0,0,3,4,8,9,7,5,0,0

0,0,0,3,7,8,6,0,1,2

0,0,0,0,0,0,0,0,1,0

在这张二维地图中,数字代表海拔,0代表大海,求某一个点(比如6,8)所在岛屿的面积

#include "stdafx.h"   
#include "iostream"  
#define X 6
#define Y 8
using namespace std;
int map[11][11] = {
	0,0,0,0,0,0,0,0,0,0,0,
	0,1,2,1,0,0,0,0,0,2,3,
	0,3,0,2,0,1,2,1,0,1,2,
	0,4,0,1,0,1,2,3,2,0,1,
	0,3,2,0,0,0,1,2,4,0,0,
	0,0,0,0,0,0,0,1,5,3,0,
	0,0,1,2,1,0,1,5,4,3,0,
	0,0,1,2,3,1,3,6,2,1,0,
	0,0,0,3,4,8,9,7,5,0,0,
	0,0,0,0,3,7,8,6,0,1,2,
	0,0,0,0,0,0,0,0,0,1,0 };
bool visited[11][11] = { false };
int sum;
void DFS(int x, int y)
{
	int next[4][2] = { 0,1,1,0,0,-1,-1,0 };
	int tx=0, ty=0;
	for (int i = 0;i < 4;i++)   //尝试四个方向
	{
		tx = x + next[i][0];
		ty = y + next[i][1];

		if (tx < 1 || tx>10 || ty < 1 || ty>10)continue;   //边界
		if (map[tx][ty] > 0 && visited[tx][ty] == false)   //满足条件
		{
			sum++;
			visited[tx][ty] = true;
			DFS(tx, ty);    //从新的位置重新遍历
		}
	}
	return;
}
void main()
{
	visited[X][Y] = true;
	if (map[X][Y] <= 0) { sum=0;cout << sum << endl;return; }
	else sum = 1;
	DFS(X, Y);
	cout << sum << endl;
}
因为只需要统计数据,所以不需要DFS()之后的visited[][]=false操作,如果加上了,那就成了搜索路径了

具体方法就是通过一个方向数组,每次遍历一个节点时通过这个向量向四周尝试,如果成立,就从新的位置开始,重新向四周尝试,最后就会把符合条件的全部尝试一遍。
5.接上题,判断图中有几个小岛

具体思路,通过双重循环,从左上角开始遍历整个地图,比如先遍历的1,1,那么就从1,1调用深搜把1,1所在的小岛的9块陆地所在的map数组数值全部改为-1,1,继续遍历地图,1,2为-1,跳过,1,3为-1,跳过,1,4为0,跳过,一直到1,9满足,对1,9调用深搜把1,9所在的5块陆地所在的map数组数值全部改为-2,依次类推,对地图全部遍历完毕后,将分为几大块不同标记的陆地,之前用的用num标记陆地上的标记,输出-num就可以了。

上代码

#include "stdafx.h"   
#include "iostream"  
using namespace std;
bool visited[11][11] = { false };
int map[11][11] = {
	0,0,0,0,0,0,0,0,0,0,0,
	0,1,2,1,0,0,0,0,0,2,3,
	0,3,0,2,0,1,2,1,0,1,2,
	0,4,0,1,0,1,2,3,2,0,1,
	0,3,2,0,0,0,1,2,4,0,0,
	0,0,0,0,0,0,0,1,5,3,0,
	0,0,1,2,1,0,1,5,4,3,0,
	0,0,1,2,3,1,3,6,2,1,0,
	0,0,0,3,4,8,9,7,5,0,0,
	0,0,0,0,3,7,8,6,0,1,2,
	0,0,0,0,0,0,0,0,0,1,0 };
int num = 0;
void DFS(int x, int y, int color)
{
	int next[4][2] = { 0,1,1,0,0,-1,-1,0 };
	int tx, ty;
	map[x][y] = color;

	for (int i = 0;i <= 3;i++)
	{
		tx = x + next[i][0];
		ty = y + next[i][1];

		if (tx < 1 || tx>10 || ty < 1 || ty>10)continue;
		if (map[tx][ty] > 0 && visited[tx][ty] == false)
		{
			visited[tx][ty] = true;
			DFS(tx, ty, color);
		}
	}
	return;
}
void main()
{
	for (int i = 1;i <= 10;i++)
	{
		for (int j = 1;j <= 10;j++)
		{
			if (map[i][j] > 0)
			{
				num--;         //num从0开始,每发现一个陆地就-1以标记为不同的颜色
				visited[i][j] = true;
				DFS(i, j, num);
			}
		}
	}
	cout << -num << endl;
}
6.有一长度为N(1 <= N <= 10)的地板,给定两种不同瓷砖:一种长度为1,另一种长度为2,数目不限。要将这个长度为N的地板铺满,一共有多少种不同的铺法?
例如,长度为4的地面一共有如下5种铺法:
4 = 1 + 1 + 1 + 1
4 = 2 + 1 + 1
4 = 1 + 2 + 1
4 = 1 + 1 + 2
4 = 2 + 2

编程用递归的方法求解上述问题。

简单的深搜

#include "stdafx.h"   
#include "iostream"  
using namespace std;
int n, ans;
void DFS(int temp)   //temp代表当前摆上的砖的面积总数
{
	if (temp > n)return;   //结束条件,如果超了,表明摆的不成功
	if (temp == n)     //满足条件,答案+1,记得return
	{
		ans++;return;
    }
	DFS(temp + 1);//继续尝试
	DFS(temp + 2);//继续尝试

}
void main()
{
	ans = 0;
	cin >> n;
	DFS(0);
	cout << ans << endl;
}



7.图的深度优先遍历

先说下最普通的

     1

 /    |    \

2   3—5

|

4

这个无向图图的深度优先遍历应该是1 2 4 3 5

上代码

#include "stdafx.h"   
#include "iostream"  
#include<limits.h>
using namespace std;
int sum = 0;
bool visited[6] = { false };
const int map[6][6]=
{ {0,0,0,0,0,0}
,{0,0,1,1,INT_MAX ,1}
,{0,1,0,INT_MAX ,1,INT_MAX }
,{0,1,INT_MAX ,0,INT_MAX ,1}
,{0, INT_MAX ,1,INT_MAX,0,INT_MAX }
,{0,1,INT_MAX ,1,INT_MAX ,0} };
void DFS(int step)
{
	cout << step << " ";   //访问
	sum++;
	if (sum == 5)return;  //结束条件
	for (int i = 1;i <= 5;i++)   //从1开始尝试连通该节点的节点
	{
		if (map[step][i] == 1 && visited[i] == false)  //如果满足条件
		{
			visited[i] = true;
			DFS(i);   //从i号节点开始遍历
		}
	}
	return;//对图的遍历回溯应该是在节点周围不再连通的时候回溯,所以return放在循环结束的时候
}
void main()
{
	visited[1] = true;//从1开始走,所以初始化一下
	DFS(1);
}
还是跟一开始的差不多,多注意如何尝试和结束条件
8.深度优先搜索查找最短路径,跟上面那个地图寻路有点像,只不过地图的存储形式不一样

查找1到5的最短路径


用一个6*6的邻接矩阵存储这个图

上代码

#include "stdafx.h"
#include "iostream"
#include <limits.h>
using namespace std;
int map[6][6] ={
0,0,0,0,0,0,
0,0,2,INT_MAX,INT_MAX,10,
0,INT_MAX,0,3,INT_MAX,7,
0,4,INT_MAX,0,4,INT_MAX,
0,INT_MAX,INT_MAX,INT_MAX,0,5,
0,INT_MAX,INT_MAX,3,INT_MAX,0
},min= INT_MAX;
bool visited[6] = { false };
void DFS(int cur,int dis)
{
	if (dis > min)return;//结束条件1,路比最短的长
	if (cur == 5)//结束条件2,走到了5号节点
	{
		if (dis < min)min = dis;  //更新最短距离
		return;
	}

	for (int i = 1;i <= 5;i++)   //尝试每一个节点是否能连上
	{
		if (map[cur][i] != INT_MAX&&visited[i] == false)
		{
			visited[i] = true;
			DFS(i, dis + map[cur][i]);//一个套路
			visited[i] = false;
		}
	}
	return;
}
void main()
{
	DFS(1, 0);
	cout << min;
}
可以看出图的遍历基本上都差不多

不过求最短路径的最好方法是别的,这个我们再详细练吧。。。


阅读更多
个人分类: DFS
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭