[算法笔记] 图论总结

本文组织结构:

  • 并查集
  • 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
 */
posted @ 2019-09-21 17:02  sinkinben  阅读(336)  评论(0编辑  收藏  举报