【图论】图的概念、存储和遍历 学习笔记

图论

图的概念

从数据结构的角度看,图可以看作一个多对多的数据存储结构。而结合图论算法,图就可以成为很多问题的载体。图论是数据结构与算法结合的产物。

OI Wiki 上给出的图相关概念比较全面,但是因为 OI 是民科各个地方的一些定义都不太一样,所以作大概了解即可。

图的存储

图的存储常用下面几种方式。

边目录。通常情况下,题目数据都是直接给出边的起点,终点和边权(如果有),则可以直接存储这些信息来存图。

邻接矩阵。用一个二维数组(矩阵)\(g[n][n]\) 来存图。在带权图中,\(g[u][v]=x\) 表示从点 \(u\) 到点 \(v\) 有一条权为 \(x\) 的图,\(g[u][v]=-inf\) 表示点 \(u\) 到点 \(v\) 没有边。在无权图中,\(g[u][v]\) 表示点 \(u\) 到点 \(v\) 是否有边。

邻接表。将所有边按点归类存储。用链表实现的邻接表也叫链式前向星。

下面给出存图的代码:

洛谷 B3643 图的存储 (参考代码)

#include <bits/stdc++.h>
using namespace std;
// #define int long long

int n, m;
int g1[1005][1005];
vector<int> g2[1005];

signed main() {
    ios::sync_with_stdio(0);
    #ifndef ONLINE_JUDGE
    clock_t t0 = clock();
    freopen("data.in", "r", stdin);
    freopen("data.out", "w", stdout);
    #endif

    cin >> n >> m;
    for (int i = 1;i <= m;i++) {
        int u, v;
        cin >> u >> v;
        g1[u][v] = g1[v][u] = 1;
        g2[u].push_back(v);
        g2[v].push_back(u);
    }
    for (int i = 1;i <= n;i++) {
        for (int j = 1;j <= n;j++) {
            cout << g1[i][j] << ' ';
        }
        cout << endl;
    }

    for (int i = 1;i <= n;i++) {
        sort(g2[i].begin(), g2[i].end());
        cout << g2[i].size() << ' ';
        for (int& j : g2[i]) cout << j << ' ';
        cout << endl;
    }

    // Don't stop. Don't hide. Follow the light, and you'll find tomorrow. 

    #ifndef ONLINE_JUDGE
    cerr << "Time used:" << clock() - t0 << "ms" << endl;
    #endif
    return 0;
}

图的遍历

不重复、不遗漏访问图上每个点,称为图的遍历。

遍历图时通常使用邻接表存储图。遍历图的方式通常有两种:深度优先遍历和广度优先遍历,分别用栈(递归)和队列实现。

下面是两种遍历方式的代码实现:

洛谷 P5318 查找文献 (参考代码)

#include <bits/stdc++.h>
using namespace std;
// #define int long long

const int N = 1e5 + 5;

vector<int> g[N];

int n, m;

bool vis[N];

int dfs(int p) {
    vis[p] = 1;
    cout << p << ' ';
    for (int& i : g[p]) {
        if (vis[i]) continue;
        dfs(i);
    }
}

void bfs() {
    queue<int> q;
    q.push(1);
    vis[1] = 1;
    while (!q.empty()) {
        int p = q.front(); q.pop();
        cout << p << ' ';
        // vis[p] = 1;
        for (int& i : g[p]) {
            if (vis[i]) continue;
            vis[i] = 1;
            q.push(i);
        }
    }
}

signed main() {
    ios::sync_with_stdio(0);
    #ifndef ONLINE_JUDGE
    clock_t t0 = clock();
    freopen("data.in", "r", stdin);
    freopen("data.out", "w", stdout);
    #endif

    // Don't stop. Don't hide. Follow the light, and you'll find tomorrow. 

    cin >> n >> m;
    for (int i = 1;i <= m;i++) {
        int u, v;
        cin >> u >> v;
        g[u].push_back(v);
    }

    for (int i = 1;i <= n;i++) {
        sort(g[i].begin(), g[i].end());
    }

    dfs(1);

    cout << endl;

    memset(vis, 0, sizeof(vis));

    bfs();

    #ifndef ONLINE_JUDGE
    cerr << "Time used:" << clock() - t0 << "ms" << endl;
    #endif
    return 0;
}

例题

洛谷 P3916 图的遍历

从一个点出发对图进行深度优先遍历,时间复杂度为 \(O(N + M)\)

若从每个点开始进行一次遍历,时间复杂度无法通过此题。

正难则反。考虑编号大的点能从哪些点到达。对原图建立反图,从节点大小由大到小遍历,找出这个点可以从哪些点到达,则这些点能到达的最大点是这个点。

参考代码:

#include <bits/stdc++.h>
using namespace std;
// #define int long long

const int N = 1e5 + 5;

vector<int> g[N];

int n, m;

int ans[N];

void dfs(int p, int u) { // 可以到达点 u 的点
    ans[p] = max(ans[p], u);
    for (int& i : g[p]) {
        if (ans[i]) continue;
        dfs(i, u);
    }
}

signed main() {
    ios::sync_with_stdio(0);
    #ifndef ONLINE_JUDGE
    clock_t t0 = clock();
    freopen("data.in", "r", stdin);
    freopen("data.out", "w", stdout);
    #endif

    // Don't stop. Don't hide. Follow the light, and you'll find tomorrow. 

    cin >> n >> m;
    for (int i = 1;i <= m;i++) {
        int u, v;
        cin >> u >> v;
        g[v].push_back(u); // 建立反图
    }

    for (int i = n;i > 0;i--) dfs(i, i);

    for (int i = 1;i <= n;i++) cout << ans[i] << ' ';

    #ifndef ONLINE_JUDGE
    cerr << "Time used:" << clock() - t0 << "ms" << endl;
    #endif
    return 0;
}

P1113 杂务

对原关系建立一个有向图,每个点建立指向必须完成他才能进行的任务,然后从对图进行 dfs 就可以求出完成每个点及其之后杂物的最少时间。

#include <bits/stdc++.h>
using namespace std;
// #define int long long

const int N = 1e4 + 5;

vector<int> g[N];

int n, t[N];

int d[N];

int dfs(int x) {
    if (d[x]) return d[x];
    for (int& v : g[x]) d[x] = max(d[x], dfs(v));
    d[x] += t[x];
    return d[x];
}

signed main() {
    ios::sync_with_stdio(0);
    #ifdef DEBUG
    clock_t t0 = clock();
    freopen("data.in", "r", stdin);
    freopen("data.out", "w", stdout);
    #endif

    // Don't stop. Don't hide. Follow the light, and you'll find tomorrow. 

    cin >> n;
    for (int i = 1;i <= n;i++) {
        int u, v;
        cin >> u >> t[i];
        while (cin >> v, v) {
            g[v].push_back(u);
        }
    }

    int ans = 0;
    for (int i = 1;i <= n;i++) {
        ans = max(ans, dfs(i));
    }

    cout << ans << endl;

    #ifdef DEBUG
    cerr << "Time used:" << clock() - t0 << "ms" << endl;
    #endif
    return 0;
}

P4017 最大食物链计数

对原图建立反图,用记忆化搜索求出到达每个点的食物链数量,然后将所有终点的数量相加得到答案。

#include <bits/stdc++.h>
using namespace std;
// #define int long long

const int N = 5005, M = 5e5 + 5, mod = 80112002;

int in[N], out[N];

int n, m;

vector<int> g[N];

int f[N];

int dfs(int x) {
    if (f[x]) return f[x];
    int ans = 0;
    for (int& v : g[x]) {
        ans = (ans + dfs(v)) % mod;
    }
    return f[x] = ans;
}

signed main() {
    ios::sync_with_stdio(0);
    #ifdef DEBUG
    clock_t t0 = clock();
    freopen("data.in", "r", stdin);
    freopen("data.out", "w", stdout);
    #endif

    // Don't stop. Don't hide. Follow the light, and you'll find tomorrow. 

    cin >> n >> m;
    for (int i = 1, u, v;i <= m;i++) {
        cin >> u >> v;
        out[u]++, in[v]++;
        g[v].push_back(u);
    }

    for (int i = 1;i <= n;i++) {
        if (in[i] == 0)f[i] = 1;
    }
    for (int i = 1;i <= n;i++) dfs(i);
    int ans = 0;
    for (int i = 1;i <= n;i++) if (out[i] == 0) ans = (ans + f[i]) % mod;

    cout << ans << endl;

    #ifdef DEBUG
    cerr << "Time used:" << clock() - t0 << "ms" << endl;
    #endif
    return 0;
}

拓展阅读 && 参考资料 && 推荐题目

  1. 图论部分简介 - OI Wiki
  2. 《深入浅出程序设计竞赛(基础篇)》 第 18 章 图的基本应用
  3. 《算法竞赛进阶指南》 0x21 树和图的遍历
posted @ 2023-07-01 13:54  蒟蒻OIer-zaochen  阅读(176)  评论(0编辑  收藏  举报