深度优先搜索(以下简称DFS)
DFS其实可以看成递归,其过程简要来说可以是对每一个可能的分支路径深入到不能再深入为止,而且每个节点只访问一次。常用来处理走迷宫等问题 ⋅ ⋅ ⋅ ··· ⋅⋅⋅用递归的形式,用到了栈结构,先进后出
每次回溯的时候记得要恢复状态!!!
经典例题:AcWing 842.排列数字
AC核心代码:
D
F
S
(
c
n
t
+
1
)
DFS(cnt+1)
DFS(cnt+1)之后一定要恢复现场,即
f
l
a
g
[
i
]
=
f
a
l
s
e
flag[i]=false
flag[i]=false
bool flag[10];
int path[10];
void DFS(int cnt)
{
for(int i=1;i<=n;++i)
if(!flag[i])
{
path[cnt]=i;
flag[i]=true;
DFS(cnt+1);
flag[i]=false;
}
}
经典
n
−
n-
n−皇后问题:AcWing 843. n-皇后问题
思路:还是深搜,不过有点巧的是,判断每一条斜线的时候可以开一个数组来判断,
d
g
dg
dg 是正斜线
/
/
/,
u
d
g
udg
udg 是反斜线 \,同时也要注意恢复状态
AC核心代码如下:
bool col[15], dg[15], udg[15];
void DFS(int cnt)//cnt表示该填第几行的皇后
{
for(int i = 1; i <= n; ++i)//i表示第几列
if(!col[i] && !dg[cnt + i - 1] && !udg[n + cnt - i])//如果第cnt行第i列没有皇后,且满足他的同行同列以及斜线没有皇后,那么这里就填一个皇后
{
arr[cnt][i] = 'Q';
col[i] = dg[cnt + i - 1] = udg[n + cnt - i] = true;
DFS(cnt + 1);
arr[cnt][i] = '.';
col[i] = dg[cnt + i - 1] = udg[n + cnt - i] = false;
}
}
广度优先搜索(以下简称BFS)
用队列的形式,先进先出
BFS只适合于边权相同的最 短路
DP包含在最短路里面,DP是一个没有环存在的最短路问题
深搜可以保证搜到终点,但不能保证搜到的是最短的
1、可以搜到最短路,因为它是一圈一圈往外搜索,
第一圈
先把所有距离为
1
1
1 的搜索完
第二圈
把所有距离为
2
2
2 的搜索完
第三圈
搜索所有距离为
3
3
3 的
⋅
⋅
⋅
⋅
⋅
⋅
······
⋅⋅⋅⋅⋅⋅
搜到的距离一定是离起点越来越远,所以第一次搜到的时候一定是最短的
模板代码顺序:
定义一个队列
q
u
e
u
e
<
i
n
t
>
Q
queue<int>Q
queue<int>Q
压入对头
Q
.
p
u
s
h
(
队
头
)
Q.push(队头)
Q.push(队头)
w
h
i
l
e
(
Q
.
s
i
z
e
)
while(Q.size)
while(Q.size)//队列不为空
{
弹出队头
压入其他元素
}
经典模板题:AcWing 844.走迷宫
树的深度优先遍历
模板题:AcWing 846.树的重心
树在深度优先遍历的时候,在递归返回的时候可以返回节点的子树的大小
树的遍历回溯时候不需要更新状态,即把不用st[i]=false。这里要与全排列的区分开来,全排列要更新状态
用数组建立邻接表
建立无向图的时候因为要建立两条有向边,所以 e e e 数组和 n e ne ne 数组要开成点的数量的两倍大小, h h h 数组开成一倍就行了,因为 h h h 数组只用来存储点
//邻接表
int e[MAXN * 2], ne[MAXN * 2], h[MAXN], idx;
void add(int a, int b)
{
e[idx] = b;
ne[idx] = h[a];
h[a] = idx++;
}
树与图的DFS模板
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);
}
}
借鉴下大佬的有详细注释的AC代码:
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 1e5 + 10; //数据范围是10的5次方
const int M = 2 * N; //以有向图的格式存储无向图,所以每个节点至多对应2n-2条边
int h[N]; //邻接表存储树,有n个节点,所以需要n个队列头节点
int e[M]; //存储元素
int ne[M]; //存储列表的next值
int idx; //单链表指针
int n; //题目所给的输入,n个节点
int ans = N; //表示重心的所有的子树中,最大的子树的结点数目
bool st[N]; //记录节点是否被访问过,访问过则标记为true
//a所对应的单链表中插入b a作为根
void add(int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
// dfs 框架
/*
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);
}
}
}
*/
//返回以u为根的子树中节点的个数,包括u节点
int dfs(int u) {
int res = 0; //存储 删掉某个节点之后,最大的连通子图节点数
st[u] = true; //标记访问过u节点
int sum = 1; //存储 以u为根的树 的节点数, 包括u,如图中的4号节点
//访问u的每个子节点
for (int i = h[u]; i != -1; i = ne[i]) {
int j = e[i];
//因为每个节点的编号都是不一样的,所以 用编号为下标 来标记是否被访问过
if (!st[j]) {
int s = dfs(j); // u节点的单棵子树节点数 如图中的size值
res = max(res, s); // 记录最大联通子图的节点数
sum += s; //以j为根的树 的节点数
}
}
//n-sum 如图中的n-size值,不包括根节点4;
res = max(res, n - sum); // 选择u节点为重心,最大的 连通子图节点数
ans = min(res, ans); //遍历过的假设重心中,最小的最大联通子图的 节点数
return sum;
}
int main() {
memset(h, -1, sizeof h); //初始化h数组 -1表示尾节点
cin >> n; //表示树的结点数
// 题目接下来会输入,n-1行数据,
// 树中是不存在环的,对于有n个节点的树,必定是n-1条边
for (int i = 0; i < n - 1; i++) {
int a, b;
cin >> a >> b;
add(a, b), add(b, a); //无向图
}
dfs(1); //可以任意选定一个节点开始 u<=n
cout << ans << endl;
return 0;
}
作者:松鼠爱葡萄
链接:https://www.acwing.com/solution/content/13513/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
树与图的广度优先遍历
BFS适合用来求边权相同的最短路
模板题:AcWing 847.图中点的层次
这道题有点奇葩,不用设置flag数组也能过,大概是因为数据集里面没有两条边互相指,即a→b,b→a
邻接表存图
void add(int a,int b)
{
e[idx]=b;
ne[idx]=h[a];
h[a]=idx++;
}
核心代码
void BFS()
{
queue<PII> Q;
Q.push({1,0});//压入第一个元素
flag[1]=true;//不要flag也能过
while(Q.size())
{
PII node=Q.front();//每次取队头
Q.pop();//弹出队头
if(node.first==n)
{
printf("%d",node.second);
return ;
}
for(int i=h[node.first];i!=-1;i=ne[i])
{
int j=e[i];
if(!flag[j])//这里不要flag也能过
Q.push({j,node.second+1});//压入其他元素
}
}
printf("-1\n");
}