Acwing视频课学习笔记——树和图的DFS/BFS

树与图的存储

两种存储方式,树始终特殊的图,树是无环连通图
图分为有向图和无向图,而无向图属于一种特殊的有向图——所以实际上就是研究有向图
有向图分为两类:邻接矩阵、邻接表

//树和图的存储主要就是邻接矩阵或者邻接表
//采用邻接表的更多,邻接表可以看作一个一位数据上每个点接着一条单链表,插入方式和单链表一致
#include 

using namespace std;
const int N = 1000010, M = N * 2;
int h[N], e[M], ne[M], index;

//h[]数据上的每个点都是每条单链表的头节点
//e[],ne[],index 和单链表中定义的完全一致,e[]存放值,ne[]表示指针
int add(int a, int b) {//要求实现从 a -> b 的边添加
    e[index] = b, ne[index] = h[a], h[a] = index++;
}

int main() {

}

树与图的深度优先遍历

前面提到树本质就是图,而无向图本质就是有向图——所以需要搞清楚有向图是怎么实现遍历的

//树和图的存储主要就是邻接矩阵或者邻接表
//采用邻接表的更多,邻接表可以看作一个一位数据上每个点接着一条单链表,插入方式和单链表一致
#include 

using namespace std;
const int N = 1000010, M = N * 2;
int h[N], e[M], ne[M], index;
bool st[N];//布尔数组用于存放状态,确保每一个点只走过一次
//h[]数据上的每个点都是每条单链表的头节点
//e[],ne[],index 和单链表中定义的完全一致,e[]存放值,ne[]表示指针
int add(int a, int b) {//要求实现从 a -> b 的边添加
    e[index] = b, ne[index] = h[a], h[a] = index++;
}

//数和图的深度优先搜索
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() {
    dfs(1);//从任意结点开始遍历
}
//树或图的遍历过程
#include 
#include 
/*
 * 例题:846.树的重心

给定一颗树,树中包含n个结点(编号1~n)和n-1条无向边。

请你找到树的重心,并输出将重心删除后,剩余各个连通块中点数的最大值。

重心定义:重心是指树中的一个结点,如果将这个点删除后,剩余各个连通块中点数的最大值最小,那么这个节点被称为树的重心。

本题的本质是树的dfs, 每次dfs可以确定以u为重心的最大连通块的节点数,并且更新一下ans。

也就是说,dfs并不直接返回答案,而是在每次更新中迭代一次答案。

这样的套路会经常用到,在 树的dfs 题目中‘
 */
using namespace std;
const int N = 100010, M = N * 2;
int n;//结点数
int h[N], e[M], ne[M], index;
int ans = N;
bool st[N];//st[]用于记录遍历过的节点的状态,初始化为-1

void add(int a, int b) {
    //就是通过单链表的形式在头节点位置插入
    e[index] = b, ne[index] = h[a], h[a] = index++;
}

int dfs(int u) {
    int res = 0;//res表示以 u 为根节点所有子树中结点数最大值
    int sum = 1;//sum是以 u 为根节点包含的所有子树结点数,包括自身
    st[u] = true;//遍历整个过程每个结点只经过一次所以没有回溯的操作
    for (int i = h[u]; i != -1; i = ne[i]) {//以 u 为根节点将其所有子树深度搜索一边
        int j = e[i];
        if (!st[j]) {
            int s = dfs(j);//返回的就是某一个子树的结点数
            res = max(res, s);
            sum += s;
        }
    }
    res = max(res, n - sum);
    ans = min(res, ans);
    return sum;
}

int main() {
    memset(h, -1, sizeof h);
    scanf("%d", &n);
    for (int i = 0; i < n - 1; ++i) {
        int a, b;
        scanf("%d%d", &a, &b);//无向边的两个结点
        add(a, b), add(b, a);//无向边两个方向都要满足
    }
    dfs(1);
    cout << ans << endl;
    return 0;
}

树与图的宽度优先遍历

例题:847. 图中点的层次
给定一个n个点m条边的有向图,图中可能存在重边和自环。所有边的长度都是1,点的编号为1~n。
请你求出1号点到n号点的最短距离,如果从1号点无法走到n号点,输出-1。

//树或者图的宽度优先遍历
#include 
#include 

using namespace std;
const int N = 100010, M = 2 * N;
int n, m;
int h[N], e[M], ne[M], index;
int d[N], q[N];//q[]表示队列;d[]标记宽度搜索过程中是否已经搜过,没有搜过的标记为-1
int add(int a, int b) {//建立边的过程
    e[index] = b, ne[index] = h[a], h[a] = index++;
}

int bfs() {
    memset(d, -1, sizeof d);
    int hh = 0, tt = 0;//分别表示头结点和尾结点
    d[1] = 0;
    q[hh] = 1;
    while (hh <= tt) {//队列不为空
        int u = q[hh++];//取出队头元素
        for (int i = h[u]; i != -1; i = ne[i]) {
            int k = e[i];//循环取出以 u 为头节点所有的子节点值
            if (d[k] == -1) {//当d[k]等于-1的时候表示k没有被访问过
                d[k] = d[u] + 1;//k是前一个结点u的距离为1的下一个结点
                q[++tt] = k;
            }
        }
    }
    return d[n];
}

int main() {
    memset(h, -1, sizeof h);
    scanf("%d%d", &n, &m);
    while (m--) {
        int a, b;
        scanf("%d%d", &a, &b);
        add(a, b);
    }
    int res = bfs();
    cout << res << endl;
    return 0;
}

拓扑排序

图的宽度优先遍历应用就是拓扑序列,拓扑序列一定是针对有向图而言
有向无环图一定有拓扑序列,拓扑序列中所有的边都是从前指向后的
度的概念:每个点都有入度(有多少边指向自己)和出度(多少条边出去)的概念
例题:848.有向图的拓扑序列给定一个n个点m条边的有向图,点的编号是1到n,图中可能存在重边和自环。请输出任意一个该有向图的拓扑序列,如果拓扑序列不存在,则输出-1。若一个由图中所有点构成的序列A满足:对于图中的每条边(x, y),x在A中都出现在y之前,则称A是该图的一个拓扑序列。

//一个又向无环图一定存在至少一个入度为0的点
#include 
#include 

using namespace std;
const int N = 100010;
int n, m;
int h[N], e[N], ne[N], index;
int d[N];//d[]数组用来记录入度
int q[N];//q[]表示队列,如果一个结点满足入读为零就放入队列中
void add(int a, int b) {
    e[index] = b, ne[index] = h[a], h[a] = index++;
}

bool topsort() {
    int hh = 0, tt = -1;
    //首先需要找出所有入度为0的结点
    for (int i = 1; i <= n; ++i) {
        if (!d[i]) {
            q[++tt] = i;//加入队列
        }
    }
    while (hh <= tt) {//队列不为空
        int u = q[hh++];//取出队头结点
        //通过不断删除入度为0的结点,逐一攻破,如果存在有环图,最后加入队列的数目一定小于n-1
        for (int i = h[u]; i != -1; i = ne[i]) {//找出u结点的所有相连结点
            int k = e[i];
            d[k]--;//删除相连的边,即入度--
            if (!d[k]) {//如果入度为0,表示可以加入队列
                q[++tt] = k;
            }
        }
    }
    return tt == n - 1;
}

int main() {
    memset(h, -1, sizeof h);
    scanf("%d%d", &n, &m);
    while (m--) {
        int a, b;
        scanf("%d%d", &a, &b);
        add(a, b);
        d[b]++;//每建立一条边,b的入度++
    }
    if (topsort()) {
        //满足拓扑排序,则输出
        for (int i = 0; i < n; ++i) {
            printf("%d", q[i]);
        }
    } else {
        printf("%d", -1);
    }
    return 0;
}

注意事项:拓扑序列最后的输出结果顺序不唯一

你可能感兴趣的:(数据结构和算法杂谈,深度优先,学习,宽度优先)