- 深度优先搜索 DFS
- 广度优先搜索 BFS
- 树与图的存储
- 树与图的深度优先遍历
- 树与图的广度优先遍历
- 拓扑排序
DFS与BFS
DFS
尽可能往深处搜,当搜到头的时候才会回溯,然后继续向深处搜索。
DFS首先要考虑的是以何种顺序把某一道题的所有可能方案全部搜一遍
可以看成是一个非常执着的人
两个DFS的重要概念:回溯和剪枝
-
回溯
当走到头,无路可走的时候,先后 -
剪枝
提前判断当前的方案一定是不合法的,不用遍历其之后的方案,直接剪掉。
现在看一个最经典的只涉及到回溯的题:AW842
虽然样式很像一棵树,但是每次存储的时候存储的都是一条路径,当回溯的时候相对应的无用路径会被删除。而在写函数时就有一个隐含的栈来帮我们实现这个回溯的。这个栈结构可联想递归时的函数运行顺序。
回溯要注意的一点:回溯要恢复现场,即从碰壁回到原来可以分支的状态
dfs函数的结构如下:
#include<iostream>
#include<stdio.h>
using namespace std;
const int N=10;
int n;
int path[N]; //状态数组,存储走的哪条路径
//而且再每次触底返回再重新搜索时之前的数组会被覆盖,所以只需要开一个数组就好了
bool state[N]; //一个bool数组来记录这个数是否被用过(true是用过了)
void dfs(int u) //传入的参数代表的是当前处理第几层
{
if(u==n) //总共都只有n层,当u当前处理层数碰到底了,则输出并回溯
{
for(int i=0;i<n;i++) printf("%d ",path[i]);
cout<<endl;
return;
}
for(int i=1;i<=n;i++) //u<n时进行的操作
{
if(!state[i]) //如果这个数没有被用进path数组里则加进path数组,然后标记它已被使用过
{
path[u]=i;
state[i]=true;//进行操作
dfs(u+1); //向下一层搜
//path[u]=0;这句可以不写 ,因为要被后面的步骤覆盖的
state[i]=false; //恢复现场
}
}
}
int main()
{
cin>>n;
dfs(0); //传入的参数代表的是当前处理第几层
return 0;
}
再看一个经典的n皇后问题:AW843
n-皇后问题是指将 n 个皇后放在 n∗n 的国际象棋棋盘上,使得皇后不能相互攻击到,即任意两个皇后都不能处于同一行、同一列或同一斜线上。
一种对角线的截距是b=y-x由于可能出现负数,所以统一加上一个偏移量n使b>0即当col[i]时,udg[n+i-u] (i和看作纵坐标y)
同理
当col[i]时,dg[u+i] (i和看作纵坐标y)
//第一种搜索顺序
#include<iostream>
#include<stdio.h>
using namespace std;
const int N=20;
int n;
char g[N][N]; //存储每一排的棋盘状态
//而且再每次触底返回再重新搜索时之前的数组会被覆盖,所以只需要开一个数组就好了
//bool state[N]; 这是上题的状态判断数组,在本题中,状态判断要用三个数组实现
bool col[N],dg[N],udg[N]; //列、对角线、反对角线
void dfs(int u) //传入的参数代表的是当前处理第几层
{
if(u==n) //总共都只有n层,当u当前处理层数碰到底了,则输出并回溯
{
for(int i=0;i<n;i++) puts(g[i]); //输出的是一排的棋盘状态
cout<<endl;
return;
}
for(int i=0;i<n;i++) //u<n时进行的操作
{
if(!col[i]&&!dg[u+i]&&!udg[n-u+i]) //如果这个数没有被用进path数组里则加进path数组,然后标记它已被使用过
{
g[u][i]='Q';
col[i]=dg[u+i]=udg[n-u+i]=true; //进行操作
dfs(u+1); //向下一层搜 面的步骤覆盖的
col[i]=dg[u+i]=udg[n-u+i]=false; //恢复现场
g[u][i]='.';
}
}
}
int main()
{
cin>>n;
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
g[i][j] = '.';
dfs(0); //传入的参数代表的是当前处理第几层
return 0;
}
BFS
一层一层地搜,这层遍历完了才会搜下面一层
可以看成是一个稳重的人,每次都要一层遍历完了才会再走一层,不会离家太远
(先吃窝边草再吃更远的草)
题目凡是涉及到最小步数、最短距离、问最少操作几次等都是BFS(前提是没有带权边)
BFS的基本框架
- 初始状态放到队列里面去 queue
- while(队列不空)
- while循环里:每一次把队头拿出来;扩展队头
例题1:走迷宫AW844
#include<iostream>
#include<stdio.h>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
typedef pair<int,int> PII;
const int N=110;
int n,m;
int g[N][N]; //g数组存的地图
int d[N][N]; //d数组存的每一个点到起点的距离
PII q[N*N]; //q数组存的坐标
int bfs()
{
int hh=0,tt=0; //队头队尾指针,模拟队列
q[0]={0,0}; //把初始状态放到队列里
memset(d,-1,sizeof(d));
d[0][0]=0;
int dx[4]={-1,0,1,0},dy[4]={0,1,0,-1};
//当前点向四个方向扩展的代码不需要写四个判断,可以用向量来表示
//dx和dy数组的每一对只分别代表上(-1,0)右(0,1)下(1,0)左(0,-1)的向量
while(hh<=tt)
{
PII t=q[hh++]; //出队列操作,对应“把队头拿出来”
for(int i=0;i<4;i++) //向四个方向判断
{
int x=t.first+dx[i],y=t.second+dy[i]; //x朝每个方向移动
if(x>=0&&x<n&&y>=0&&y<m&&g[x][y]==0&&d[x][y]==-1)
//前四个判断:没有越地图的界||第五个判断:那个坐标可走(不是墙)||第六个判断:坐标没有被走过
{
d[x][y]=d[t.first][t.second] +1; //BFS深度加一
q[++tt]={x,y}; //队尾入队:把此刻的坐标x y入队,在下一个循环里出队并操作
}
}
}
return d[n-1][m-1]; //返回终点到起点的距离
}
int main()
{
cin>>n>>m;
for(int i=0;i<n;i++)
for(int j=0;j<m;j++)
cin>>g[i][j];
cout<<bfs()<<endl;
return 0;
}
例题2:八数码 AW845
这题是求最短要多少步能达到正确排列,很显然是一题BFS题。
难点:
-
状态表示比较复杂(这个状态表示是一个3x3的矩阵)
-
已知状态之后怎么转化到下一步
问题解决:
-
在BFS里有两个量要进行表示:队列和距离
队列直接定义queue来存
距离直接定义unordered_map<string,int>来存 -
第一步把这个字符串在脑子里转化为一个3x3的图
第二步是枚举x可能移动的四个方向
第三步是再次表示和存储处理后的图
#include <iostream>
#include <algorithm>
#include <unordered_map>
#include <queue>
using namespace std;
int bfs(string state)
{
queue<string> q; //存储状态队列
unordered_map<string, int> d; //存储距离(BFS层数) (distance)
q.push(state);
d[state] = 0;
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1}; //向量表示:上右下左
string end = "12345678x"; //正确排列
while (q.size()) //队列不空
{
string t = q.front();
q.pop();
if (t == end) return d[t]; //处理结束,返回最短距
//状态转移
int distance = d[t];
int k = t.find('x'); //string的find函数返回值为x的下标
int x = k / 3, y = k % 3; //将一个一维数组的下标转化成二维数组的下标(x,y)
for (int i = 0; i < 4; i ++ )
{
int a = x + dx[i], b = y + dy[i]; //对各方向进行枚举
if (a >= 0 && a < 3 && b >= 0 && b < 3) //每个方向都没有出界
{
swap(t[a * 3 + b], t[k]); //状态更新,是将字符串原来的x与枚举的点进行交换
//a*3+b为二位坐标转化为一位数组
if (!d.count(t)) //返回一个数在哈希表中的个数
{
d[t] = distance + 1;
q.push(t);
}
swap(t[a * 3 + b], t[k]); //状态复原
}
}
}
return -1;
}
int main()
{
char s[2];
string state;
for (int i = 0; i < 9; i ++ )
{
cin >> s; //读入初始字符串
state += *s;
}
cout << bfs(state) << endl;
return 0;
}
DFS和BFS对比
实现它所用的数据结构 | 实现方法 | 占用空间 | 最短性 | |
---|---|---|---|---|
DFS | stack | 递归 | O(h) | 不具有最短性 |
BFS | queue | 非递归 | O(2h) | 能求出最短路 |
可以看出,DFS在空间复杂度上有着绝对优势,但BFS的优点是可以得到最短路径
最短路问题与动态规划问题的关系
最短路问题是包含dp问题的,即dp是一种特殊的(没有环的)最短路
来源:CSDN
作者:qq_33164724
链接:https://blog.csdn.net/qq_33164724/article/details/103991982