考研复试系列——第四节 深度优先搜索
前言
深度优先搜索(DFS)算法在ACM中是经常出现的解决问题的算法,相比暴力搜索可以更快的解决问题。
深度优先搜索算法的题目从整体架构上是类似的,所以我们先给出一个大体的DFS的模板:
dfs:
if(判断条件) //在这里进行递归的退出
return;
for(遍历内容) //进行遍历,例如图中访问每一个满足某一条件的节点
{
if(判断是否访问过以及其他题中给出的条件)
{
进行访问条件的设置及其他操作
dfs //递归调用
访问条件的重新设置及其他操作
}
}
例题
下面来介绍几道DFS的题目来加深对DFS的理解。
题目一
N皇后问题 在N*N的棋盘上放置 N 个皇后,使其不能够相互攻击 ,即任意两个皇后不能在同一行,同一列,同一斜线上。
思考 :显然每一行肯定有且仅有一个皇后,对于第一行我们确定了一个皇后之后,再确定第二行的皇后,依次确定到第N行的皇后。假如前N-1个皇后都已经确定了,
我们在确定最后一个皇后N时,确定一个位置后进行判断,然后再回到上一步,重新确定第N个皇后,由此可以看出这个问题可以使用DFS解决。
按照上面的模板,我们可以得出核心的DFS代码为:
void dfs(int n)//参数n表示当前确定第n个皇后
{
if(n > N) //当前要确定的皇后n大于皇后的总个数N时
{
judge();//当前所有皇后的位置都确定了,进行判断是否满足题中条件(不能互相攻击)
return;//退出递归
}
int i;
for(i=1;i<=N;i++) //对于每一行(即每一个皇后)都有N个位置,要依次遍历
{
if(position[i] == 0) //若该位置没有访问过
{
position[i] = 1;//标记该位置被访问过
tag[n] = i;//记录第n行皇后的位置,因为在judge函数中比较满足条件与否时要用到
dfs(n+1);//递归调用
position[i] = 0;//重新标记该位置,因为回溯后可能再次用到该位置
}
}
}
接下来我们只需要确定judge函数就可以解决了。 如果使用 i , j 分别表示两个皇后(1<= i <= N , 1<= j <= N , i 不等于 j )
xi ,xj 分别表示这两个皇后在棋盘中列的位置 (1<= xi <= N , 1<= xj <= N ,x i 不等于 xj )
上面i 不等于j 以及 xi 不等于 xj 已经将横向和纵向的情况排除 。 那如何表示不能在同一斜线上呢 ?如果两者在同一斜线上,则有
| i - j | = | xi - xj | 所以有| i - j | 不等于 | xi - xj | 。前面代码中可以看出皇后的位置保存在tag一维数组中,tag [1 ] 为第一个皇后的位置 。
由此judge函数为:
void judge()
{
int i,j;
for(i=1;i<=N;i++)
for(j=1;j<i;j++)
if(abs(j-i)==abs(tag[j]-tag[i]))
return ;
count++; //记录种类
}
最终我们的程序如下:
#include<iostream>
#include<math.h>
using namespace std;
int count = 0,N;
int *position,*tag;
void judge()
{
int i,j;
for(i=1;i<=N;i++)
for(j=1;j<i;j++)
if(abs(j-i)==abs(tag[j]-tag[i]))
return ;
count++; //记录种类
}
void dfs(int n)//参数n表示当前确定第n个皇后
{
if(n > N) //当前要确定的皇后n大于皇后的总个数N时
{
judge();//当前所有皇后的位置都确定了,进行判断是否满足题中条件(不能互相攻击)
return;//退出递归
}
int i;
for(i=1;i<=N;i++) //对于每一行(即每一个皇后)都有N个位置,要依次遍历
{
if(position[i] == 0) //若该位置没有访问过
{
position[i] = 1;//标记该位置被访问过
tag[n] = i;//记录第n行皇后的位置,因为在judge函数中比较满足条件与否时要用到
dfs(n+1);//递归调用
position[i] = 0;//重新标记该位置,因为回溯后可能再次用到该位置
}
}
}
int main()
{
int i;
cin>>N;
position = new int[N+1];
tag = new int[N+1];
for(i=0;i<=N;i++)
position[i] = 0;
dfs(1);
cout<<count<<endl;
return 0;
}
题目二
整数划分 。这道题目在我以前的一篇博文上有过记录 ,链接如下:
http://blog.csdn.net/cassiepython/article/details/49797623
题目三
四色图问题
给定一个图,使用邻接矩阵表示,表示国家与国家的相邻关系,使用至多4种颜色进行着色,求总共有多少种着色方法。
相邻的国家不能使用同一种颜色。
#include<iostream>
using namespace std;
int map[5][5] = {
0,1,1,0,0,
1,0,1,0,0,
1,1,0,0,0,
0,0,0,0,1,
0,0,0,1,0
};
int color[5]; //记录节点的着色情况
int count = 0;
void judge()
{
int i,j;
for(i=0;i<5;i++)
{
for(j=0;j<i;j++)
{
if(map[i][j] == 1 && color[i] == color[j])
return;
}
}
count++;
}
void dfs(int n,int cr)
{
color[n] = cr;
if( n == 4)
{
judge();
return;
}
int i;
for(i=0;i<4;i++)
dfs(n+1,i);
}
int main()
{
for(int i=0;i<4;i++)
dfs(0,i);
cout<<count<<endl;
return 0;
}
题目四
给定一个n,输出1—n的全排列。如给定n=3,则输出全排列123 132 213 231 312 321 。
思考:我们先确定第一位,然后再确定第二位,确定第三位后再回溯到上一步,更改第二位的数,由此我们可以使用DFS来解决这道问题。
#include<iostream>
using namespace std;
int n;
int out[100];//保存结果
int number[100];//标记是否使用
void dfs(int step)
{
int i;
if(step > n)
{
for(i=1;i<=n;i++)
cout<<out[i];
cout<<endl;
return;
}
for(i=1;i<=n;i++)
{
if(number[i] == 0)
{
number[i] = 1;
out[step] = i;
dfs(step+1);
number[i] = 0;
}
}
}
int main()
{
cin>>n;
dfs(1);
return 0;
}
关于全排列的算法不止是使用DFS实现,还有很多种方法,想要进一步知道请参考我的另一篇博文:
http://blog.csdn.net/cassiepython/article/details/45767361
题目五
![微笑](http://static.blog.csdn.net/xheditor/xheditor_emot/default/smile.gif)
![微笑](http://static.blog.csdn.net/xheditor/xheditor_emot/default/smile.gif)
![微笑](http://static.blog.csdn.net/xheditor/xheditor_emot/default/smile.gif)
![微笑](http://static.blog.csdn.net/xheditor/xheditor_emot/default/smile.gif)
![微笑](http://static.blog.csdn.net/xheditor/xheditor_emot/default/smile.gif)
![微笑](http://static.blog.csdn.net/xheditor/xheditor_emot/default/smile.gif)
![微笑](http://static.blog.csdn.net/xheditor/xheditor_emot/default/smile.gif)
![微笑](http://static.blog.csdn.net/xheditor/xheditor_emot/default/smile.gif)
![微笑](http://static.blog.csdn.net/xheditor/xheditor_emot/default/smile.gif)
#include<iostream>
using namespace std;
int out[10];//保存结果
int number[10];//标记是否使用
int total = 0;//记录总共多少种可能
void dfs(int step)
{
int i;
if(step > 9)
{
//判断是否满足条件
if(out[1]*100+out[2]*10+out[3]+out[4]*100+out[5]*10+out[6]
== out[7]*100 + out[8]*10 + out[9] )
{
total++;
cout<<out[1]<<out[2]<<out[3]<<"+"<<out[4]<<out[5]<<out[6]
<<"="<<out[7]<<out[8]<<out[9]<<endl;
return;
}
}
for(i=1;i<=9;i++)
{
if(number[i] == 0)
{
number[i] = 1;
out[step] = i;
dfs(step+1);
number[i] = 0;
}
}
}
int main()
{
dfs(1);
cout<<"总共的种类为:"<<total<<endl;
return 0;
}
如果173 + 286 = 459 与 286+173 = 459算同一种的话 结果total除以2即可。