深度优先遍历及例题加讲解
竞赛里面重要算法,全当学习笔记啦。(如有不足多多谅解)
零、什么是深度优先遍历
深度优先遍历简称DFS(Depth First Search)。
我们简单的举个栗子:
从前有位首富打野,一共14个打野点,首富从1号打野点开始,要打遍所有野区,可以有什么样的打野路线呢?
我们假设挑一个方向打野打到底,我们选择一条支路,尽可能不断深入,如果遇到死路就往回退,回退过程中如果遇到没打过的野怪就进入这条支路继续深入打野。
在图中,我们先从1号点,到2号一直道6号(蛤蟆)。这个时候发现已经是个死胡同了,不能再往前走了(男枪:懂事的打野已经去上路假装gank,再吃波兵线)
于是,我们退回到五号打野点,然后往8号点走,然后又无路可走了,于是退回到2号点,沿着2号点方向打野即探索2-12-13-14
按照这个思路,我们探索完2号点方向,就退回到1号点向其他没有打过野的方向探索。
像这样先深入探索,走到头再退回寻找其他出路的遍历方式,就叫做深度优先搜索。
二、深度优先遍历例题
(一)对数字全排列相关问题
1.题目:
对数字1-9进行全排列,求出1-9全排列有多少种。
2.基础深度优先遍历例题。
3.解题代码:
//用dfs解决1-9的全排列个数问题
#include<bits/stdc++.h>
using namespace std;
int total=0;//用于计数
int check[10]={0};//检查当前数字有没有用过,用过了就标记为1,也可以用bool类型
int lista[10]={0};//用于存储排列结果
void dfs(int step){
if(step==10){
total++;
/* 显示排列结果
for(int j=1;j<=9;j++){
cout<<lista[j];
}
*/
return;
}
for(int i=1;i<=9;i++){ //1-9个数字循环
if(check[i]==0){
check[i]=1; //当前i这个数字用过,标记为1;
lista[step]=i;//存储结果
dfs(step+1);//进行下一个位置的数字选择
check[i]=0;//将用过的数字再标记为0,再使用;
}
}
}
int main(){
dfs(1);
cout<<total;
}
同样的也可以用c++的一个函数直接求解,如下:
```cpp
//用next_permutation函数进行全排列(升序)
//需要头文件#include<algorithm>
#include<bits/stdc++.h>
using namespace std;
int main(){
int a[10]={1,2,3,4,5,6,7,8,9};
int cnt=0;
do{
cnt++;
}while(next_permutation(a,a+9));
cout<<cnt;
}
4.稍微进阶一点的题目:
1-9数字对应着a-9个字母,使得ab+cd+ef+gh+i=141,问有多少种可能?
这题仅仅在原有基础上,添加一些限制条件。
代码:
#include<bits/stdc++.h>
using namespace std;
int total=0;//用于计数
int check[10]={0};//检查当前数字有没有用过,用过了就标记为1,也可以用bool类型
int lista[10]={0};//用于存储排列结果
int cnt=0;
bool checka(){//使用布尔类型进行结果检测
int t=lista[1]*lista[2]+lista[3]*lista[4]+lista[5]*lista[6]+lista[7]*lista[8]+lista[9];
if(t==141){
return true;
}
return false;
}
void dfs(int step){
int t=0;
if(step==10){
//total++;
/* 显示排列结果
for(int j=1;j<=9;j++){
cout<<lista[j];
}
*/
if(checka()){//调用布尔类型,得出符合结果加一。
cnt++;
}
return;
}
for(int i=1;i<=9;i++){ //1-9个数字循环
if(check[i]==0){
check[i]=1; //当前i这个数字用过,标记为1;
lista[step]=i;//存储结果
dfs(step+1);//进行下一个位置的数字选择
check[i]=0;//将用过的数字再标记为0,再使用;
}
}
}
int main(){
dfs(1);
cout<<cnt;
}
(二)对地图路径种类问题
1.题目:
如下地图,0的位置可以走,1的位置是路障不能走,问从左上角到右下角有多少种走法?
0,0,0,1,0,
0,1,0,0,0,
0,0,0,1,1,
0,1,0,0,0.
2.深度优先遍历简单例题,从左上角开始,先深入走一遍,不通或者到达了就返回再走另一个方向。
3.解题代码:
#include<bits/stdc++.h>
using namespace std;
int check[5][6]={0};//检查当前地点有没有走过,走过为1;
int mape[5][6]={
0,0,0,0,0,0,
0,0,0,0,1,0,
0,0,1,0,0,0,
0,0,0,0,1,1,
0,0,1,0,0,0,
};
int x[4]={0,1,0,-1};//决定走的方向
int y[4]={1,0,-1,0};
int total=0;
void dfs(int xx,int yy){
check[1][1]=1;
int tx=1,ty=1;
if(xx==4&&yy==5){//到达目的地,计数加一
total++;
return ;
}
for(int i=0;i<4;i++){
tx=xx+x[i];
ty=yy+y[i];
if(tx<1||tx>5||ty<1||ty>6)continue;//不能超过边界
if(check[tx][ty]==0&&mape[tx][ty]==0){
check[tx][ty]=1;
dfs(tx,ty);
check[tx][ty]=0;
}
}
}
int main(){
dfs(1,1);
cout<<total;
}
4.八皇后问题:
有六位皇后,在一个6x6的表格中,有六个皇后,为了让他们不互相攻击(不在同一行同一列同一斜方向),请给她们安排位置。
如下图:
经典八皇后问题,关键点在于限制条件:不在一个列,不在一个斜线,
不在一个列用checklie[j]=0
;然而不在一个斜线则发现行号x与列号j之间的关系:
x+j与x-j+6是固定值即:checkxie[x+j]=0;checkxie2[x-j+6]=0;
解题代码:
#include<bits/stdc++.h>
using namespace std;
int checklie[100]={0};//判断当前列有没有用过
int checkxie[100]={0};//判断右上和左下的那条斜线有没有用过
int checkxie2[100]={0};//判断左上和右下的那条斜线有没有用过
int total=0;//记录结果数
void dfs(int x){ //x当前行数
if(x==7){//当前x=7是结束,结果加一
total++;
return;
}
for(int i=1;i<=6;i++){
if(checklie[i]==0&&checkxie[x+i]==0&&checkxie2[x-i+6]==0){//判断
checklie[i]=1;
checkxie[x+i]=1;
checkxie2[x-i+6]=1;
dfs(x+1);//继续深搜下一行
checklie[i]=0;//回溯
checkxie[x+i]=0;
checkxie2[x-i+6]=0;
}
}
}
int main(){
dfs(1);
cout<<total;
}