一.BFS:
也就是广度优先遍历,具体是从一个点向四周遍历马,一般是上下左右或者周围八个方向。bfs问题通常用来解决扩散类问题,也就是某个岛屿可以覆盖或者感染周围的岛屿类的问题,也常用于求连在一起的元素个数。通常呢,我们用队列来实现bfs问题。把某个点加入到队列中去,再取出队头,判断它四周有没有符合要求的元素,把符合的元素也加入到队列中去。
例题:acwing-奶牛选美:
听说最近两斑点的奶牛最受欢迎,约翰立即购进了一批两斑点牛。
不幸的是,时尚潮流往往变化很快,当前最受欢迎的牛变成了一斑点牛。
约翰希望通过给每头奶牛涂色,使得它们身上的两个斑点能够合为一个斑点,让它们能够更加时尚。
牛皮可用一个 N×M的字符矩阵来表示,如下所示:
................
..XXXX....XXX...
...XXXX....XX...
.XXXX......XXX..
........XXXXX...
.........XXX....
其中,X 表示斑点部分。
如果两个 X在垂直或水平方向上相邻(对角相邻不算在内),则它们属于同一个斑点,由此看出上图中恰好有两个斑点。
约翰牛群里所有的牛都有两个斑点。
约翰希望通过使用油漆给奶牛尽可能少的区域内涂色,将两个斑点合为一个。
在上面的例子中,他只需要给三个 .. 区域内涂色即可(新涂色区域用 ∗∗ 表示):
................
..XXXX....XXX...
...XXXX*...XX...
.XXXX..**..XXX..
........XXXXX...
.........XXX....
请帮助约翰确定,为了使两个斑点合为一个,他需要涂色区域的最少数量。
很基础的一道bfs题,就是bfs遍历两块斑点,再暴力找到两块斑点中的最短距离即可。
//acwing-奶牛选美
//暴力枚举+bfs
#include<iostream>
#include<vector>
#include<queue>
#include<algorithm>
using namespace std;
char a[53][53];
bool cnt[53][53];
vector<pair<int,int>>q1;
vector<pair<int,int>>q2;
queue<pair<int,int>>q;
int dx[4]={1,0,0,-1};
int dy[4]={0,1,-1,0};
int cn=0;
int ans=50000000;
void bfs(int n,int m){
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(cnt[i][j]==0&&a[i][j]=='X'){
q.push({i,j});
cnt[i][j]=1;
while(!q.empty()){
auto t=q.front();
if(cn==0){
q1.push_back(t);
}
else if(cn==1){
q2.push_back(t);
}
q.pop();
for(int i=0;i<4;i++){
if(!cnt[t.first+dx[i]][t.second+dy[i]]&&a[t.first+dx[i]][t.second+dy[i]]=='X'){
q.push({t.first+dx[i],t.second+dy[i]});
cnt[t.first+dx[i]][t.second+dy[i]]=1;
}
}
}
cn++;
}
}
}
}
int myans(int x1,int y1,int x2,int y2){
return min(ans,abs(x1-x2)+abs(y1-y2));
}
int main(){
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
cin>>a[i][j];
}
}
bfs(n,m);
for(int i=0;i<q1.size();i++){
for(int j=0;j<q2.size();j++){
ans=myans(q1[i].first,q1[i].second,q2[j].first,q2[j].second);
}
}
cout<<ans-1;
return 0;
}
二.DFS
又叫做深度优先遍历,通常用来解决路径类问题,比如符合某些条件就可以一直往后遍历,直到不满足条件为止。一般用递归来实现,所以虽然dfs代码会更简洁一些,但是往往会难理解一点。当然熟练掌握后其实选DFS做题的会更多一点。
例题:acwing-大臣的旅费
很久以前,T王国空前繁荣。
为了更好地管理国家,王国修建了大量的快速路,用于连接首都和王国内的各大城市。
为节省经费,T 国的大臣们经过思考,制定了一套优秀的修建方案,使得任何一个大城市都能从首都直接或者通过其他大城市间接到达。
同时,如果不重复经过大城市,从首都到达每个大城市的方案都是唯一的。
J是 T 国重要大臣,他巡查于各大城市之间,体察民情。
所以,从一个城市马不停蹄地到另一个城市成了 J 最常做的事情。
他有一个钱袋,用于存放往来城市间的路费。
聪明的 J 发现,如果不在某个城市停下来修整,在连续行进过程中,他所花的路费与他已走过的距离有关。
具体来说,一段连续的旅途里,第 11 千米的花费为 1111,第 22 千米的花费为 1212,第 33 千米的花费为 1313,…,第 x 千米的花费为 x+10+10。
也就是说,如果一段旅途的总长度为 11 千米,则刚好需要花费 1111,如果一段旅途的总长度为 22 千米,则第 11 千米花费 1111,第 22 千米花费 1212,一共需要花费 11+12=2311+12=23。
J 大臣想知道:他从某一个城市出发,中间不休息,到达另一个城市,所有可能花费的路费中最多是多少呢?
其实题目就是找到最短的一条路径,这里有个规律,随便找一个点,然后找到离他最远的那个点u,再从u开始找到离u最远的点就是长的路径了,那么这里我们用dfs来找到最远点,双向dfs即可解决问题。
//双dfs求最长路径问题
#include<iostream>
#include<vector>
#include<cstring>
#include<algorithm>
using namespace std;
//关键步骤
//1.选择一个结点开始遍历,找到最远的结点u
//2.再从u开始遍历第二次,找到最远结点走过的长度就是答案
int n;
struct node{
int id,length;//记录该点能到达的位置和下个点的距离
};
vector<node>h[100003];
int dist[100003];
void dfs(int u,int father,int distance){
dist[u]=distance;//记录当前结点走过的长度
for(auto i:h[u]){//遍历h[i]结点
if(i.id!=father){//下一个能走的结点不是父节点(也就是不能往回走)
dfs(i.id,u,distance+i.length);//dfs下一个结点
}
}
}
int main(){
cin>>n;
for(int i=0;i<n-1;i++){
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
h[a].push_back({b,c});//a能到达的下一个位置和长度
h[b].push_back({a,c});//b能到达的下一个位置和长度
}
dfs(1,-1,0);//从第一个点开始遍历,-1是第一个点的父亲结点,0是目前走过的距离
int u=1;
for(int i=1;i<=n;i++){//遍历n个结点,找到走过路径最长结点的下标
if(dist[i]>dist[u]){
u=i;
}
}
dfs(u,-1,0);//u是走过最远的结点,从u开始遍历第二次,找到最远结点走过的长度就是最终答案啦
for(int i=1;i<=n;i++){
if(dist[i]>dist[u]){
u=i;
}
}
int s=dist[u];
long long int ans=0;
for(int i=1;i<=s;i++){//求出走s这么长的路需要的路费
ans+=10+i;
}
cout<<ans;
return 0;
}
当然啦,其实dfs与bfs差别并不算大,而且也是属于比较简单的算法,不过都是很重要的,所以多刷刷题掌握就好。