搜索与图论之邻接表创建,图的DFS遍历及应用
目录
(数组建立邻接表) 树的dfs
//邻接表
int h[N], e[N * 2], ne[N * 2], idx;
void add(int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
树的dfs模板
// 需要标记数组st[N], 遍历节点的每个相邻的便
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);
}
}
}
本题的本质是树的dfs, 每次dfs可以确定以u为重心的最大连通块的节点数,并且更新一下ans。
也就是说,dfs并不直接返回答案,而是在每次更新中迭代一次答案。
这样的套路会经常用到,在 树的dfs 题目中。
标准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;
}
myAC代码
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 100100;
int h[N], e[N << 1], ne[N << 1]; //h[i]表示第i个单链表的头,e[i]表示第i条边在图中的编号, ne[i]表示第i的下一个节点
//N << 1 : 以有向图的格式存储无向图,所以每个节点至多对应2n-2条边
bool st[N]; //state状态,表示该节点有没有被遍历过
int n, ans = N, idx; //idx记录当前边数
void add(int a,int b) //头插法插入一条a->b的边
{
e[idx] = b;
ne[idx] = h[a], h[a] = idx ++ ;
}
int dfs(int u) //以u为根节点的子树中 点的数量
{
st[u] = true; //标记一下,该节点已经被搜索过了
int res = 0, sum = 1; //sum表示以该节点为根的子树(包括根)的节点总数,res表示以该节点为根的时候的各个连通块中点数的最大值
for(int i = h[u]; i != -1; i = ne[i]) //遍历所有子树
{
int j = e[i];
if(!st[j]) //没有被搜索过
{
int s = dfs(j);
res = max(res, s);
sum += s;
}
}
res = max(res, n - sum);
ans = min(ans, res);
return sum;
}
int main()
{
cin >> n;
memset(h, -1, sizeof h); //将所有单链表的头都指向-1
for(int i = 0; i < n - 1; i ++ )
{
int a, b;
cin >> a >> b;
add(a, b); add(b, a); //把无向图的一条边看成两条边
}
dfs(1); //从1~n-1的仍和一条边开始遍历都一样
cout << ans << endl;
}
一个解释
dfs(0)为什么是错误的
因为我们dfs深度搜索的是节点(顶点)而不是边,点的编号是从1开始的,边是从 0开始的
在创建单链表时,我们输入的两个顶点都是>=1的,所以 h[i] 的 i >=1
如果dfs(0,因为 h[0] = -1,dfs直接结束了
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效