图论(三)——编程实现图连通分量个数求解

\quad 求图连通分量个数方法很多,这里主要讨论两种方法,一种是通过dfs、bfs等遍历手段求得,一种是并查集。

一、利用dfs求图连通分量

\quad 算法流程:

  • 初始化连通分量个数为ccount=0;
  • 从图中任一顶点开始进行dfs,通过该顶点遍历到的所有顶点属于同一连通分量,这些遍历到的顶点做好标记,表示已经被访问。ccount+=1;
  • 从未访问过的顶点中取出一个顶点重复第二步,遍历完后ccount+=1;
  • 重复上述过程,直到所有顶点均被标记;
  • 输出ccount,ccount即为图连通分量个数。
    \quad 我们还可以通过一个变量id记录每个顶点具体属于某个连通分量。在具体实现中,我实现了三个方法——1、查询图中连通分量个数;2、查询任意两点是否连通,即是否属于同一个连通分量;3、具体打印出每个连通分量包含的顶点情况。
    \quad 首先,我们先建立一个图,用邻接表存放与每个顶点相邻的顶点。
#include
using namespace std;

class graph {
public:
    vector > g;  // 图的领接表
    graph(int V, bool directed=false){
        this->V=V;  // 需传入图的顶点数
        this->directed = directed;  // 是否为有向图
        // g初始化为V个空的vector, 表示每一个g[i]都为空, 即没有任和边
        g = vector >(V, vector());
    }
    ~graph(){};
    void addEdge(int v, int w);  // 顶点v与w有一条边
    int getV() { return V; }  // 取出顶点数
    int getE() { return E; }  // 取出边数

private:
    int V = 0;  // 顶点数
    int E = 0;  // 边数
    bool directed;  // 是否为有向图

};

void graph::addEdge(int v, int w) {
    assert(v>=0 && v=0 && w

\quad 接下来具体实现求取图连通分量的类Component。

class Component {
private:
    graph &G;  // 图的引用
    bool *visited;  // 记录节点是否被访问
    int ccount;  // 连通分量个数
    int *id;  // 给每个顶点所属的连通分量标号

    void dfs(int v);  // 从顶点v开始进行dfs
public:
    Component(graph &graph): G(graph)
    {
        visited = new bool[G.getV()];
        id = new int[G.getV()];
        ccount = 0;
        for (int i = 0; i < G.getV(); ++i) {
            visited[i] = false;
            id[i] = -1;
        }

        for (int i = 0; i < G.getV(); ++i) {
            if(!visited[i])
            {
                dfs(i);
                ccount++;
            }
        }
    }

    ~Component(){
        delete[] visited;
        delete[] id;
    }

    // 返回连通分量个数
    int count()
    {
        return ccount;
    }

    // 查询v和w是否连通
    bool isConnected(int v, int w)
    {
        return id[v]==id[w];
    }

    // 以文字形式显示图的连通分量具体情况
    void verbose()
    {
        for (int i = 1; i <= ccount; ++i) {
            cout << "Component " << i << " Vertex:";
            for (int j = 0; j < G.getV(); ++j) {
                if(id[j] == i-1) cout << " " << j;
            }
            cout << endl;
        }
    }
};

void Component::dfs(int v) {
    visited[v] = true;
    id[v] = ccount;
    // 遍历图G的顶点v的领接表
    for (int i = 0; i < G.g[v].size(); ++i) {
        if(!visited[G.g[v][i]])
            dfs(G.g[v][i]);
    }
}

\quad 在主函数中建立一个6个顶点,4条边的图。
图论(三)——编程实现图连通分量个数求解_第1张图片

int main()
{
    graph G(6, false); // 建立顶点个数为6的图
    G.addEdge(0, 1);
    G.addEdge(0, 2);
    G.addEdge(1, 2);
    G.addEdge(3, 4);

    Component component(G);
    cout << "连通分量个数:" << component.count() << endl;  // 打印连通分量个数
    cout << "1,2点是否连通:" << component.isConnected(1, 2) << endl;  // 判断两点是否连通
    cout << "1,3点是否连通:" <

\quad 运行结果如下:
图论(三)——编程实现图连通分量个数求解_第2张图片

二、并查集求连通分量个数

\quad 并查集操作分为两个,并Union和查Find。Union可以将两个顶点合在一个集合里面,拥有相同的“祖先”。查可以获得当前顶点所在集合的“祖先”。若两个顶点a,b在一个集合里,则Find(a)=Find(b)。因为这个在刷一些oj上刷题时经常用到,这里就直接给出常见写法:

//
// Created by 程勇 on 2019/3/7.
//

/*
 * 并查集可用来求图连通分类和最小生成树
 */
#include
using namespace std;

const int maxn = 1e+6+10;
int parent[maxn];

int Find(int p)
{
    while(p != parent[p])
    {
        p = parent[p];
    }
    return p;
}

void Union(int p, int q)
{
    if(Find(p) != Find(q))
        parent[p] = q;
}

int main()
{
    int n, m;  // 顶点数和边数
    cin >> n >> m;
    for (int i = 1; i <= n; ++i) {
        parent[i] = i;  // 初始化每个顶点指向自己
    }
    for (int j = 0; j < m; ++j) {
        int v, w;
        cin >> v >> w;  // 依次读入边
        v = Find(v);
        w = Find(w);
        Union(v, w);
    }

    int res = 0;
    for (int i = 1; i <= n; ++i) {
        if(parent[i] == i) res++;
    }

    cout << "连通分量数:" << res-1 << endl;
    system("pause");
    return 0;
}

\quad 输入之前那个图信息,运行结果:
图论(三)——编程实现图连通分量个数求解_第3张图片

你可能感兴趣的:(图论)