图的基本实现

稠密图

稠密图 邻接矩阵实现 无向图

稠密图 邻接矩阵实现 有向图

#include <iostream>
#include <cassert>
#include <vector>

/**
 * 稠密图 - 邻接矩阵
 */
class DenseGraph {
private:
    /**
     * 图中顶点数、边数
     */
    int n, m;
    /**
     * 是否为有向图
     */
    bool directed;
    /**
     * 矩阵,有边就是 true,无边就是 false。
     */
    std::vector<std::vector<bool>> g;

public:
    DenseGraph(int n, bool directed) {
        this->n = n;
        this->m = 0;
        this->directed = directed;
        for (int i = 0; i < n; ++i) {
            // 创建一个 n * n 的矩阵,每一排
            // g 中总共有 n 行
            // 每行总共有 n 个元素,每个元素默认为 false,未有任何连接
            g.push_back(std::vector<bool>(n, false));
        }
    }

    ~DenseGraph() {

    }

    /**
     * 图中有多少顶点
     *
     * @return 顶点的个数
     */
    int v() {
        return n;
    }

    /**
     * 图中有多少边
     *
     * @return 边的个数
     */
    int e() {
        return m;
    }

    /**
     * 连接 v 和 w,在他们之间建立一条边
     *
     * 平行边:v 和 w 都是唯一的,不存在平行边
     * 自环边:当 v 和 w 一样时,稠密图天生只会存在一个自环边
     */
    void addEdge(int v, int w) {
        assert(v >= 0 && v < n);
        assert(w >= 0 && w < n);

        if (hasEdge(v, w)) {
            // 已经存在边了,重复的连接操作
            return;
        }

        g[v][w] = true;
        if (!directed) {
            // 无向图
            g[w][v] = true;
        }
        ++m;
    }

    /**
     * 两顶点之间是否存在边
     *
     * 时间复杂度:O(1)
     */
    bool hasEdge(int v, int w) {
        assert(v >= 0 && v < n);
        assert(w >= 0 && w < n);

        return g[v][w];
    };
};

稀疏图

稀疏图 邻接表实现 无向图

稀疏图 邻接表实现 有向图

#include <iostream>
#include <cassert>
#include <vector>

/**
 * 稀疏图 - 邻接表
 */
class SparseGraph {
private:
    /**
     * 图中顶点数、边数
     */
    int n, m;
    /**
     * 是否为有向图
     */
    bool directed;
    /**
     * 邻接表,每行存放的都是当前行所连接的所有顶点
     */
    std::vector<std::vector<int>> g;

public:
    SparseGraph(int n, bool directed) {
        this->n = n;
        this->m = 0;
        this->directed = directed;
        g = std::vector<std::vector<int>>(n, std::vector<int>());
    }

    ~SparseGraph() {

    }

    /**
     * 图中有多少顶点
     *
     * @return 顶点的个数
     */
    int v() {
        return n;
    }

    /**
     * 图中有多少边
     *
     * @return 边的个数
     */
    int e() {
        return m;
    }

    /**
     * 连接 v 和 w,在他们之间建立一条边
     *
     * 平行边:存在平行边的情况,如果添加的时候想过滤平行边,就需要调用 hasEdge 进行判断,那 addEdge 就变成了 O(n) 时间复杂度
     *          一般不在 addEdge 时处理,在所有边都添加完毕之后再去重。
     * 自环边:当 v 和 w 一样时,并且是无向图时,就会存在添加两次自环边的情况,所以要判断筛选掉
     */
    void addEdge(int v, int w) {
        assert(v >= 0 && v < n);
        assert(w >= 0 && w < n);

        g[v].push_back(w);
        if (v != w && !directed) {
            // 无向图
            g[w].push_back(v);
        }
        ++m;
    }


    /**
     * 两顶点之间是否存在边
     *
     * 时间复杂度:O(n)
     */
    bool hasEdge(int v, int w) {
        assert(v >= 0 && v < n);
        assert(w >= 0 && w < n);

        for (int i = 0; i < g[v].size(); ++i) {
            if (g[v][i] == w) {
                return true;
            }
        }
        return false;
    }
};

遍历邻边

0 的邻边

假设遍历 0 的邻边 1、4、7,如果是稠密图,就需要遍历整个 n 的长度,为 true 的是 0 的邻边。如果是稀疏图则可直接遍历,遍历出来的都是 0 的邻边。而大多数业务场景下,很少有一个顶点能连接其他所有顶点的情况,也就是使用稠密图的情况较少见,多数为使用稀疏图,稀疏图的遍历也更加有效率。

迭代器的形式实现

稠密图

/**
 * 邻边迭代器
 */
class AdjIterator {
private:
    DenseGraph &G;
    /** 顶点 */
    int v;
    /** 当前迭代到第几个 */
    int index;
public:
    AdjIterator(DenseGraph &graph, int v) : G(graph) {
        this->v = v;
        this->index = 0;
    }

    /**
        * 稠密图中,每一行的第 0 个元素不一定是相连的,要为 true 才为相连
        */
    int begin() {
        index = -1;
        return next();
    }

    int next() {
        // 只要 index 还小于顶点总数(稠密图中行和列数都是顶点总数),就可以继续向下查询
        for (index += 1; index < G.v() ; ++index) {
            if (G.g[v][index]) {
                return index;
            }
        }
        return -1;
    }

    bool end() {
        return index >= G.v();
    }
};

稀疏图

/**
    * 邻边迭代器
    */
class AdjIterator {
private:
    SparseGraph &G;
    /** 顶点数 */
    int v;
    /** 当前迭代到第几个 */
    int index;
public:
    AdjIterator(SparseGraph &graph, int v) : G(graph) {
        this->v = v;
        this->index = 0;
    }

    int begin() {
        index = 0;
        // size 大于 0
        if (G.g[v].size()) {
            return G.g[v][index];
        }
        return -1;
    }

    int next() {
        ++index;
        if (index < G.g[v].size()) {
            return G.g[v][index];
        }
        return -1;
    }

    bool end() {
        return index >= G.g[v].size();
    }
};

测试

void vertexAdjTraverseByDenseGraph() {
    int n = 20;
    int m = 100;

    // Dense Graph
    // 创建一张稠密图
    DenseGraph graph(n, false);
    // 随机让两个顶点进行相连
    for (int i = 0; i < m; i++) {
        int a = rand() % n;
        int b = rand() % n;
        graph.addEdge(a, b);
    }

    // 时间复杂度: O(V^2)
    for (int v = 0; v < n; v++) {
        std::cout << v << " : ";
        DenseGraph::AdjIterator adj(graph, v);
        for (int w = adj.begin(); !adj.end(); w = adj.next())
            std::cout << w << " ";
        std::cout << std::endl;
    }

    std::cout << std::endl;
}

void vertexAdjTraverseBySparseGraph() {
    int n = 20;
    int m = 100;

    srand(time(NULL));

    // Sparse Graph
    // 创建一张稀疏图
    SparseGraph graph(n, false);
    // 随机让两个顶点进行相连
    for (int i = 0; i < m; i++) {
        int a = rand() % n;
        int b = rand() % n;
        graph.addEdge(a, b);
    }

    // 时间复杂度: O(E),E 为边的总数
    for (int v = 0; v < n; v++) {
        std::cout << v << " : ";
        SparseGraph::AdjIterator adj(graph, v);
        for (int w = adj.begin(); !adj.end(); w = adj.next())
            std::cout << w << " ";
        std::cout << std::endl;
    }

    std::cout << std::endl;
}

int main() {
    // 测试稠密图遍历所有顶点的邻边
    vertexAdjTraverseByDenseGraph();

    // 测试稀疏图遍历所有顶点的邻边
    vertexAdjTraverseBySparseGraph();

    return 0;
}

打印结果:

稠密图所有顶点的邻边遍历
0 : 3 5 12 13 16 17 19 
1 : 4 5 6 7 10 11 15 16 
2 : 9 12 14 15 17 19 
3 : 0 5 6 8 9 10 11 12 15 16 
4 : 1 13 14 16 
5 : 0 1 3 9 10 12 13 15 17 
6 : 1 3 10 15 16 17 
7 : 1 10 11 13 14 
8 : 3 10 14 
9 : 2 3 5 9 11 14 15 
10 : 1 3 5 6 7 8 10 16 17 18 19 
11 : 1 3 7 9 12 13 15 16 19 
12 : 0 2 3 5 11 14 19 
13 : 0 4 5 7 11 13 15 16 17 18 
14 : 2 4 7 8 9 12 16 17 18 19 
15 : 1 2 3 5 6 9 11 13 16 17 
16 : 0 1 3 4 6 10 11 13 14 15 17 
17 : 0 2 5 6 10 13 14 15 16 19 
18 : 10 13 14 
19 : 0 2 10 11 12 14 17 

稀疏图所有顶点的邻边遍历
0 : 19 16 13 5 17 12 3 
1 : 16 10 11 15 7 4 11 5 6 
2 : 12 19 17 15 14 9 
3 : 8 16 9 12 5 10 5 6 5 11 0 15 15 
4 : 13 1 16 14 
5 : 12 3 17 3 9 9 1 3 13 0 15 10 12 13 
6 : 16 10 17 15 16 16 10 3 1 
7 : 10 10 14 13 11 1 
8 : 3 10 14 
9 : 3 11 9 5 14 2 5 11 15 
10 : 18 16 6 10 8 1 7 18 3 7 6 10 19 5 17 
11 : 19 13 9 12 19 1 19 7 12 16 1 12 3 19 15 9 16 
12 : 5 2 3 19 11 14 11 11 0 5 
13 : 15 18 11 4 17 13 16 16 7 0 5 5 
14 : 12 7 2 9 17 8 18 19 19 4 16 
15 : 13 17 6 2 1 16 5 11 3 9 3 
16 : 1 6 3 10 6 6 13 15 13 0 11 4 17 14 11 
17 : 6 15 2 13 5 19 14 0 16 10 
18 : 13 10 10 14 
19 : 11 2 12 11 11 0 17 11 10 14 14 


进程已结束,退出代码0

可以看到,稠密图的邻边关系,天然按顺序排放,并且不会出现平行边。而稀疏图是随机排放,会出现平行边。

从文件中读取图

准备好一个两个问题件 testG1.txttestG2.txt 文件 ,如下:

testG1.txt
13 13
0 5
4 3
0 1
9 12
6 4
5 4
0 2
11 12
9 10
0 6
7 8
9 11
5 3

testG2.txt
6 8
0 1
0 2
0 5
1 2
1 3
1 4
3 4
3 5

第一行第一个数字代表有多少个顶点,第一行第二个数字代表有多少条边。第二行的两个数字代表让对应的两个顶点相连,后续行与第二行一样。

创建读取类,采用模板类,兼容稠密图和稀疏图

#include <iostream>
#include <string>
#include <fstream>
#include <sstream>
#include <cassert>

using namespace std;

/**
 * 从文件中读取图
 *
 * @tparam Graph 读取的图采用模板类,兼容稠密图、稀疏图
 */
template<typename Graph>
class ReadGraph {
public:
    ReadGraph(Graph &graph, const string &filename) {
        // 打开文件
        ifstream file(filename);
        string line;
        // 顶点和边数
        int v, e;

        assert(file.is_open());
        // 获取第一行
        assert(getline(file, line));
        stringstream ss(line);
        // 读取顶点数 和 边数
        ss >> v >> e;

        // 顶点数要对应
        assert(v == graph.v());

        for (int i = 0; i < e; ++i) {
            assert(getline(file, line));
            stringstream ss(line);

            int a, b;
            ss >> a >> b;
            assert(a >= 0 && a < v);
            assert(b >= 0 && b < v);

            graph.addEdge(a, b);
        }
    }
};

图类中增加 show 方法

/******** 稠密图 ********/
/**
 * 显示图的信息
 */
void show() {
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            std::cout << g[i][j] << "\t";
        }
        std::cout << std::endl;
    }
}

/******** 稀疏图 ********/
/**
 * 显示图的信息
 */
void show() {
    for (int i = 0; i < n; i++) {
        std::cout << "vertex " << i << ":\t";
        for (int j = 0; j < g[i].size(); j++) {
            std::cout << g[i][j] << "\t";
        }
        std::cout << std::endl;
    }
}

测试

void readGraph() {
    string filename = "testG1.txt";

    SparseGraph g1(13, false);
    ReadGraph<SparseGraph> readGraph(g1, filename);

    cout<<"test G1 in Sparse Graph:" << endl;
    g1.show();

    cout<<endl;

    DenseGraph g2( 13 , false );
    ReadGraph<DenseGraph> readGraph2( g2 , filename );
    cout<<"test G1 in Dense Graph:" << endl;
    g2.show();

    cout<<endl;

    // 使用两种图的存储方式读取 testG2.txt 文件
    filename = "testG2.txt";
    SparseGraph g3( 6 , false );
    ReadGraph<SparseGraph> readGraph3( g3 , filename );
    cout<<"test G2 in Sparse Graph:" << endl;
    g3.show();

    cout<<endl;

    DenseGraph g4( 6 , false );
    ReadGraph<DenseGraph> readGraph4( g4 , filename );
    cout<<"test G2 in Dense Graph:" << endl;
    g4.show();
}

int main() {
    readGraph();





    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值