DFS与BFS
数据结构
BFS: 队列(先进先出)
步骤:
1、首先A入队列,
2、A出队列时,A的邻接结点B,C相应进入队列
3、B出队列时,B的邻接结点A,C,D中未进过队列的D进入队列
4、C出队列时,C的邻接结点A,B,D,E中未进过队列的E进入队列
5、D出队列时,D的邻接结点B,C,E,F中未进过队列的F进入队列
6、E出队列,没有结点可再进队列
7、F出队列
DFS:栈(先进后出)
步骤:
1、首先A入栈,
2、A出栈时,A的邻接结点B,C相应入栈 (这里假设C在下,B在上)
3、B出栈时,B的邻接结点A,C,D中未进过栈的D入栈
4、D出栈时,D的邻接结点B,C,E,F中未进过栈的E、F入栈(假设E在下,F在上)
5、F出栈时,F没有邻接结点可入栈
6、E出栈,E的邻接结点C,D已入过栈
7、C出栈
深度优先搜索算法(英语:Depth-First-Search,简称DFS)是一种用于遍历或搜索树或图的算法。
沿着树的深度遍历树的节点,尽可能深的搜索树的分支。当节点v的所在边都己被探寻过或者在搜寻时结点不满足条件,搜索将回溯到发现节点v的那条边的起始节点。整个进程反复进行直到所有节点都被访问为止。属于盲目搜索,最糟糕的情况算法时间复杂度为O(!n)。
int check(参数)
{
if(满足条件)
return 1;
return 0;
}
void dfs(int step)
{
判断边界 //递归出口
{
相应操作
}
尝试每一种可能
{
满足check条件
标记
继续下一步dfs(step+1)
恢复初始状态(回溯的时候要用到)
}
}
**基本参数:**
path [ i ] 路径点
u 层数
st [ i ] 记录已经用过的点,定义成布尔类型
全排列问题
代码:
#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++)
printf("%d",path[i]);
puts("");
return;
}
for(int i=1;i<=n;i++){
if(!st[i]){
path[u]=i;
st[i]=true;
dfs(u+1);
st[i]=false;
}
}
}
int main(){
cin>>n;
dfs(0);
return 0;
}
n皇后问题
//解法一
//为什么解法一没有row?因为解法一是按行枚举的,这就保证了每一行只有一个。
#include<iostream>
using namespace std;
const int N=20;
int n;
char g[N][N];
bool col[N],dg[N],udg[N];//3个数组来记录列、主对角线和次对角线的方式
/*
u: 当前行数
n: 总列数
i: 当前列数
dg :主对角线
udg :副对角线
col[i]:当前行的第i列
*/
void dfs(int u){
if(u==n){
for(int i=0;i<n;i++) puts(g[i]);
puts("");
return;
}
for(int i=0;i<n;i++){ //for循环嵌套递归+if剪枝
if(!col[i]&&!dg[u+i]&&!udg[n-u+i]){
g[u][i]='Q'; //在这里面i,u是变量 表示列数和行数 ,n是定值,表示一个n×n的矩阵
//udg里面i-u或者u-i都行,关键是能通过i和u确定一个唯一的编号。
col[i]=dg[i+u]=udg[i-u+n]=true;//(x+y)(x-y+n) 行数+列数 ,行数 - 列数 + n(加的n是为了防止出现负数)
dfs(u+1);//udg[i-u+n] 这里面n是防止出现负数的,可以自己取一个合理的数字
col[i]=dg[u+i]=udg[i-u+n]=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;
}
//解法二
#include<bits/stdc++.h>
using namespace std;
const int N=20;
int n;
char g[N][N];
bool row[N],col[N],dg[N],udg[N];
void dfs(int x,int y,int s){
if(y==n) y=0,x++;
if(x==n){
if(s==n){
for(int i=0;i<n;i++) puts(g[i]);
puts("");
}
return;
}
//不放皇后
dfs(x,y+1,s);
//放皇后
if(!row[x]&&!col[y]&&!dg[x+y]&&!udg[x-y+n]){
g[x][y]='Q';
row[x]=col[y]=dg[x+y]=udg[x-y+n]=true;
dfs(x,y+1,s+1);
row[x]=col[y]=dg[x+y]=udg[x-y+n]=false;
g[x][y]='.';
}
}
int main(){
cin>>n;
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
g[i][j]='.';
dfs(0,0,0);
return 0;
}
关键的地方就是对角线判断判断的方法:
一种判别方式是 如果两个坐标行和列的差相等,则它们在同一条主对角线上 如果两个坐标行和列的和相等,则它们在同一条副对角线上 而本题用的是第二种方式
首先让我们来看一下矩阵的对角线有那些性质:
我们发现主对角线上的点的坐标(x,y)满足x=y
在蓝色这条斜线上的坐标(x,y)满足x-y=1
在粉色这条斜线上的坐标(x,y)满足x-y=-1
同样的,我们也可以吧主对角线的坐标(x,y)表示为x-y=0
所以可以用x-y来计算斜线的编号
x-y的值是从(-4+1)~(4-1)
但是数组下标(在c++)没有负数,所以我们把x-y+n当做数组下标,编号从1~(2×4-1)
同样的,对于n×n的棋盘,共有2n+1条左斜线,我们可以把向左倾斜的斜线编号(x-y+n)
下面来解决一下向右倾斜的斜线编号(还是以4*4的棋盘为例)
不难看出,在标红的这条向右倾斜的主对角线上点的坐标满足x+y=5
在粉色的这条斜线上x+y=4
在蓝色的这条斜线上x+y=6
同样的,向右倾斜的斜线共有(2×4-1)条,按照x+y来编号,编号为2~8,
同样的,对于n×n的棋盘,共有2n+1条右斜线,向右倾斜的斜线标号为(x+y)
这个对角线关键就是找到一个定值,就是一个特征,这条线上的点都存在这个特征。比如相加或者相减的结果是一个常数。
广度优先搜索
走迷宫问题
样例输出为8;
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
typedef pair<int,int> PII;
const int N=110;
int n,m;
int g[N][N];//存储地图
int d[N][N];//存储最短路径
PII q[N*N];//队列
PII Prev[N][N];//记录下前一个点是哪个,方便输出队列
/*
hh:队头
tt:队尾
*/
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};
while(hh<=tt)
{
auto t=q[hh++];
for(int i=0;i<4;i++)
{
int x=t.first+dx[i],y=t.second+dy[i];
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; //标记该点
Prev[x][y]=t;
q[++tt]={x,y}; //将该点加入队列
}
}
}
int x=n-1,y=m-1;
while(x||y){
cout<<x<<' '<<y<<endl;
auto t=Prev[x][y];
x=t.first,y=t.second;
}
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;
}
// 0 1 0 0 0
// 0 1 0 1 0
// 0 0 0 0 0
// 0 1 1 1 0
// 0 0 0 1 0
int dx[4]={-1,0,1,0},dy[4]={0,1,0,-1}的解释
数组来表示上下左右四个方向的移动
树与图的遍历:拓扑排序
树的重心
#include<string>
#include<iostream>
#include<algorithm>
#include<bits/stdc++.h>
using namespace std;
const int N=100010,M=N*2;
int n,m;
int h[N],e[M],ne[M],idx; //h 存储头节点,e存节点的值 ne存每一个节点的next值
bool st[N]; //布尔数组存下哪个已经被遍历过
void add(int a,int b) //插入一条由a指向b的边
{
e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void dfs(int u)
{
st[u]=true; //标记,已被搜索过
for(int i=h[u];i!=-1;i=ne[i])
{
int j=e[i];
if(!st[j]) dfs(j);
}
}
int main()
{
memset(h,-1,sizeof h);
}