搜索与图论之邻接表创建,图的DFS遍历及应用

 目录

(数组建立邻接表) 树的dfs

树的dfs模板

一个解释


例题:846. 树的重心 - AcWing题库

1.png

(数组建立邻接表) 树的dfs

用数组模拟链表 - AcWing

//邻接表
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直接结束了

posted @   光風霽月  阅读(59)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
点击右上角即可分享
微信分享提示