• [算法笔记] 图论总结


    本文组织结构:

    • 并查集
    • Kruskal
    • Dijkstra
    • DFS
    • BFS

    并查集 (Disjoint Set)

    思想比较简单,一个无环的连同图可以看作是一棵树,任意选定一个节点为根,并查集可找出树中每个节点的最远的根(或者说是“最早的祖先”)。

    #include <iostream>
    #include <cstring>
    #include <map>
    #include <vector>
    #define VMAX 100
    using namespace std;
    int uset[VMAX + 1];
    int findRoot(int x)
    {
        if (uset[x] == -1)
            return x;
        else
            return uset[x] = findRoot(uset[x]);
    }
    int main()
    {
        memset(uset, -1, sizeof(uset));
        int v, e;
        int a, b;
        cin >> v >> e;
        while (e--)
        {
            cin >> a >> b;
            a = findRoot(a);
            b = findRoot(b);
            if (a - b)
                uset[a] = b;
        }
        //连通分量个数就是 -1 的个数
        for (int i = 1; i <= v; i++)
            cout << uset[i] << ' ';
    }
    /*
    输入: 点数, 边数, 顶点从 0 开始
    6 3
    1 2
    3 4
    5 6
     */
    

    Kruskal

    最小生成树求解算法。

    对所有的边升序排列,每次选出一个最小边 <u,v,cost>,如果 uv 不是连通的(借助并查集判断),说明该边一定属于 MST

    #include <map>
    #include <iostream>
    #include <vector>
    #include <algorithm>
    #include <set>
    #include <cstring>
    #define VMAX 100
    #define EMAX 100
    using namespace std;
    struct Node
    {
        int u, v;
        int cost = 0;
        Node(int a = -1, int b = -1, int c = 0)
        {
            u = a;
            v = b;
            cost = c;
        }
        //当权值相同时也允许插入set,注意set的插入cmp比较条件的使用
        bool operator<(const Node &n) const
        {
            return cost <= n.cost;
        }
    };
    int root[VMAX];
    set<Node> graph; //要求以边为权值排序
    int findRoot(int x)
    {
        return (root[x] == -1) ? (x) : (root[x] = findRoot(root[x]));
    }
    
    int main()
    {
        memset(root, -1, sizeof(root));
        int v, e;
        cin >> v >> e;
        int a, b, c;
        for (int i = 0; i < e; i++)
        {
            cin >> a >> b >> c;
            //无向图
            graph.insert(Node(a, b, c));
        }
        int mincost = 0;
        for (auto &x : graph)
        {
            a = findRoot(x.u);
            b = findRoot(x.v);
            if (a != b)
            {
                root[a] = b;
                mincost += x.cost;
                cout << x.u << " " << x.v << ' ' << x.cost << endl;
            }
        }
        cout << mincost << endl;
    }
    /*
    输入样例 
    6 8
    1 6 100
    1 5 30 
    1 3 10 
    2 3 5 
    3 4 50 
    4 6 10 
    5 4 20 
    5 6 60
    
    mincost = 75
     */
    

    Dijkstra

    借助优先队列优化的 dij 算法。

    先来回顾一下最短路径的关键的点:

    • 选出一个 cost 最小的点 x
    • 扫描所有与 x 相连的点 y
    • 判断 [start->...->y][start->...->x->...->y] 哪一个路径最短

    在原始的 Dijkstra 算法中,第一点找出最小 cost 的复杂度是 0(n) ,现在利用优先队列的自动排序,找出最小 cost 的复杂度是 0(logn),最终算法被优化为 O(nlogn)

    #include <cstring>
    #include <iostream>
    #include <vector>
    #include <map>
    #include <queue>
    #define VMAX 100
    #define EMAX 100
    using namespace std;
    struct Node
    {
        int adjvex;
        int cost;
        Node(int a = -1, int c = 0) : adjvex(a), cost(c) {}
        //priority queue 中 cost 小的在前面,具有更高的优先级
        bool operator<(const Node &n) const
        {
            return cost > n.cost;
        }
    };
    
    map<int, vector<Node>> m;
    int dis[VMAX];
    bool vis[VMAX];
    int prevex[VMAX];
    void path(int x)
    {
        if (x == -1)
            return;
        path(prevex[x]);
        cout << x << ' ';
    }
    /*
        使用优先队列优化,实质上利用了 priority_queue 的自动排序
        每次 q.top 都是 mincost
        其实用 set 也可以
     */
    void dij(int start, int v)
    {
        memset(prevex, -1, sizeof(prevex));
        memset(vis, 0, sizeof(vis));
        memset(dis, 0x3f, sizeof(dis));
    
        dis[start] = 0;
        priority_queue<Node> q;
        q.push(Node(start, 0));
        Node n;
        while (!q.empty())
        {
            n = q.top();
            q.pop();
            int x = n.adjvex;
            if (!vis[x])
            {
                vis[x] = true;
                int len = m[x].size();
                for (auto &n : m[x])
                {
                    int y = n.adjvex;
                    int newcost = dis[x] + n.cost;
                    if (vis[y])
                        continue;
                    if (dis[y] > newcost)
                    {
                        dis[y] = newcost;
                        q.push(Node(y, newcost));
                        prevex[y] = x;
                    }
                    else if (dis[y] == newcost)
                    {
                        //多个最短路径
                    }
                }
            }
        }
    }
    int main()
    {
        int v, e;
        int a, b, c;
        cin >> v >> e;
        for (int i = 0; i < e; i++)
        {
            cin >> a >> b >> c;
            m[a].push_back(Node(b, c));
            m[b].push_back(Node(a, c));
        }
        dij(1, v);
    
        for (int i = 1; i <= v; i++)
        {
            path(i);
            cout << dis[i] << endl;
        }
    }
    /*
    Sample
    6 8 
    1 6 100 
    1 5 30 
    1 3 10 
    2 3 5 
    3 4 50 
    4 6 10 
    5 4 20 
    5 6 60
     */
    

    DFS和BFS

    经典算法。

    BFS实现有 2 种方法:一种是把已经访问的放入队列;一种是类似于二叉树的层次遍历,把与当前的点 x(已经被访问)相邻的,未被访问且不在队列中的点放入队列(是否在队列用 vis[y]=2 ? 来表示 )。

    #include "leetcode.h"
    #define VMAX 100
    map<int, vector<int>> m;
    int vis[VMAX] = {0};
    void dfs(int x)
    {
        vis[x] = 1;
        cout << x << ' ';
        for (int y : m[x])
        {
            if (!vis[y])
                dfs(y);
        }
    }
    void bfs(int x)
    {
        queue<int> q;
        q.push(x);
        int y;
        while (!q.empty())
        {
            y = q.front();
            q.pop();
            cout << y << ' ';
            vis[y] = 1;
            for (int z : m[y])
            {
                if (!vis[z] && vis[z] != 2)
                    q.push(z), vis[z] = 2;
            }
        }
    }
    int main()
    {
        int v, e;
        cin >> v >> e;
        int a, b;
        for (int i = 0; i < e; i++)
        {
            cin >> a >> b;
            m[a].push_back(b);
            m[b].push_back(a);
        }
        for (int i = 1; i <= v; i++)
        {
            memset(vis, 0, sizeof(vis));
            bfs(i);
            cout << endl;
        }
    }
    /*
    Sample1
    7 6
    1 2
    1 3
    2 4
    2 5
    3 6
    3 7
    
    Sample2
    6 8
    1 3
    1 5
    1 6
    2 3
    3 4
    4 5
    4 6
    5 6
     */
    
  • 相关阅读:
    理解和配置 Linux 下的 OOM Killer
    ARM各种版本号知识以及型号的发展(三星为例)
    GCC 编译使用动态链接库和静态链接库
    insmod module_param 模块参数
    cgic 写CGI程序
    嵌入式应用中CGI编程中POST、GET及环境变量详解
    CGI技术原理
    h264 流、帧结构
    LocalDate、LocalDateTime与timestamp、Date的转换
    Java8中 Date和LocalDateTime的相互转换
  • 原文地址:https://www.cnblogs.com/sinkinben/p/11563696.html
Copyright © 2020-2023  润新知