一,深度优先遍历 (BFS)
深度优先遍历思想实际上就是一种常见的完全遍历思想,我们理解他的方法就是使用老鼠的思想,每次出现结点的时候我们就嵌套一层dfs 让他向下面进行代码的流程,所谓的老鼠下楼 ,当这个算法在平行的结点的运动时候说明他已经完成了下楼的操作,已经返回来进行下一个操作,我们叫做老鼠平移 ,这么来理解这个BFS,实际上这个BFS也可以看作为一个大型的指针来回遍历的操作。
二,深度优先遍历的思想
深度遍历有两种种类,首先第一类就是单起始位置的遍历,另一类是多起点遍历,有一个明确起始位置的遍历相对比较简单,所以我们一般把一些多起点但是能转化成单起点的遍历转化成单起点遍历,简化操作
首先我们来学习一下BFS 的总思想,再来学习这两类题型的思想 ,就像上文介绍的那样,我们BFS 最核心的操作就是一个递归的遍历操作,在这个外面还有一部分的平移的操作 一般我们使用一个循环完成。
然后就是如何完成这些操作,保证这些遍历不会出现重复的情况。 首先我们想到的是bool 遍历真假法,使用这个方式来存储这些点是否经过了遍历,来完成单个点之间是否完成了遍历操作,当这个点完成了这次的遍历操作,我们再把他返回成没有遍历的情况下。
还有其他的特殊判断的办法,特殊情况特殊处理,总的来说我dfs主要的思想就是一个嵌套循环和一个判断是否遍历过的条件判断,其他就是针对题目的解
三,深度优先遍历的算法
1,多点遍历的DFS
多点遍历一般是个完全图,我们要对他的遍历嵌套一般从0开始记入他的位置,同时迭代的操作在循环里面。这个循环是遍历这个图的每个点 ,这种题目一般来说不是很明显,但是他们绝对是一个遍历完成的题目
首先我们举例子一道非常简单的深搜排序题目
题目描述
给定一个整数 n,将数字 1∼n 排成一排,将会有很多种排列方法。现在,请你按照字典序将所有的排列方法输出。
输入格式
共一行,包含一个整数 n。
输出格式
按字典序输出所有排列方案,每个方案占一行。
数据范围
1≤n≤7
输入样例:
3
输出样例:
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1
题解
#include <iostream>
using namespace std ;
const int N = 10 ;
int n ;
int path[N] ;
bool st[N] ;
void dfs (int u )
{
if(u == n )
{
for(int i = 0 ; i < n ; i ++ )
cout << path[i] << " " ;
cout << endl;
return ; //返回上一个结点
}
for(int i = 1 ; i <= n ; i ++ )
{
if(!st[i])
{
path[u] = i ;
st[i] = true ;
//让下一层的判断条件和输出结果改变
dfs(u + 1 );
//递归到下一层
st[i] = false ;
//从底层返回到这一层,把相关的条件再改回来
}
}
return ;
}
int main ()
{
cin >> n ;
dfs(0) ;
return 0 ;
}
这个是最简单的一个 DFS 操作,我们知道这个操作还有很多的优化操作,但是本节只讲述最简单的 DFS 操作,然后有详细的章节解决其他复杂的问题 。
2,固定开始点的遍历
有固定的起始点的遍历,他们一般还有更加复杂的情况,首先他们可能是有一些固定的路径或者说是更加复杂的内容涵盖,一般出现在树或者图之中,然后我们首先了解一下树,图的存储再来完成这部分的学习。
下面的代码是以图为例子来进行的一个固定点的dfs操作
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std ;
const int N = 100010 ;
int n ;
int ans = N ;
int e[N * 2] ,ne[N * 2] , h[N] , idx ;
bool st[N] ;
void add (int a, int b )
{
e[++ idx] = b, ne[idx] = h[a] , h[a] = idx ;
}
int dfs (int u )
{
st[u] = true ;
int sum = 0 , size = 0 ;
for(int i = h[u] ; i != - 1 ; i = ne[i])
{
//向下遍历
int j = e[i] ;
if(!st[j])
{
int s = dfs(j) ;
sum += s ;
size = max (size , s ) ;
}
}
size = max (n - sum - 1 ,size) ;
ans = min (ans ,size ) ;
return sum + 1 ;
}
int main ()
{
memset(h , - 1 , sizeof h) ;
cin >> n ;
for(int i = 1 ; i < n ; i ++)
{
int a , b ;
cin >> a >> b ;
add(a , b) ;
add(b , a) ;
}
dfs(1);//首先从固定点开始操作
cout << ans << endl ;
return 0 ;
}
3,图和树的建立
树和图是搜索主要面向的对象,所以我们有必要去了解搜索的建立和相关过程的实现。他们的存储大致是有两种办法,第一种就是所谓的邻接表,我们使用他们顶点的关系来创建一个矩阵,使用这个矩阵来存储数据,还有邻接表的存储方式,他是在每个顶点之后都创建一条链表来模拟是否能到达某个顶点。
现在我们主要使用临界矩阵创建的树和图
1,树和图的创建
#include <iostream>
#include <algorithm>
using namespace std ;
const int N = 1000010 ;
//存储顶点的数量
const int M =2000020 ;
//存储边的数量
int h[N];
//树的顶点
int e[N],ne[N],idx ;
//边的存储
int main ()
{
memset( h , - 1 , sizeof h );
return 0 ;
}
2,边的创建
void add(int a,int b )
{
e[++ idx] = b , ne[idx] = h[a] , h[a] = idx ;
}
四,深度遍历的简单模板
#include <iostream>
using namespace std ;
void dfs (层数,其他遍历判断参数)
{
if(出局判断)
{
//在某个时刻完成答案的输出
return ;
//返回上一层
}
for(枚举下一层的可能的数据)
{
if(当这个位置能用的时候)
{
//改变参数
dfs(层数 + 1)
//改回参数
}
}
return ;// 返回上一层
}
int main ()
{
return 0 ;
}