图使用邻接矩阵存储的实现

采用C++实现了一个简单的图类(有权/无权,有向/无向),封装部分操作,包括深度、广度遍历,获取指定顶点的度,判断两个顶点是否连通,图的连通分量,求无向有权连通图的最小生成树(prim算法,kruskal算法),有向有权图的最短路径(dijkstra单源最短路径算法,floyd所有点对点之间的最短路径)等等,详细见如下实现

    const int INTMAX = std::numeric_limits<int>::max();

    enum class GraphType {
        DUWG, // 有向无权图
        DWG, // 有向有权图
        UDUWG, // 无向无权图
        UDWG, // 无向有权图
        UNKNOW, // 未知类型
    };
    // 代价最小的边数据结构,用来辅助进行prim算法求解
    struct CloseNode {
        int adjvex = 0;
        int lowcost = INTMAX;
    };

    class Graph {
    public:
        // 图的构造,通过传递顶点数量,边的集合(边通过两个顶点(点1,点2)(无权图)或者三个数据(有权图表示为(点1,点2,权值))表示,存储在vector<int>中),图的类型等
        Graph(const int n, const vector<vector<int>> &edges, const GraphType gType) : len(n), type(gType), count(0),
                                                                                      hasBfs(false), hasDfs(false) {
            assert(gType != GraphType::UNKNOW);
            if (type == GraphType::DUWG || type == GraphType::UDUWG) {
                // 无权图构造
                matrix.resize(len + 1, vector<int>(len + 1, 0)); // 无权图邻接矩阵初始化为0
//                for(int i=0; i<=len; ++i) {
//                    // 无权图i-i可达
//                    matrix[i][i] = 1;
//                }
                for (auto &edge  : edges) {
                    assert(edge.size() == 2);
                    int v = edge[0];
                    int w = edge[1];
                    matrix[v][w] = 1; // 无权图赋值1表示两点之间连通
                    if (type == GraphType::UDUWG) {
                        // 无向图
                        matrix[w][v] = 1;
                    }
                }
            } else {
                // 有权图构造
                matrix.resize(len + 1, vector<int>(len + 1, INTMAX)); // 有权图邻接矩阵初始化为INT_MAX
                for(int i=0; i<=len; ++i) {
                    // 有权图i-i权值为0
                    matrix[i][i] = 0;
                }
                for (auto &edge  : edges) {
                    assert(edge.size() == 3);
                    int v = edge[0];
                    int w = edge[1];
                    int weight = edge[2]; // 权值
                    matrix[v][w] = weight; // 有权图赋权值
                    if (type == GraphType::UDWG) {
                        // 无向图
                        matrix[w][v] = weight;
                    }
                }
            }
        }

        // floyd算法求解有向有权图每一对顶点之间的最短路径, O(N*N*N)
        vector<vector<int>> floydShortPath() {
            auto distance = matrix;
            for(int i=0; i<=len; ++i) {
                distance[i][i] = 0;
            }

            for (int i = 0; i <= len; ++i) {
                for (int v = 0; v <= len; ++v) {
                    for (int w = 0; w <= len; ++w) {
                        if (distance[v][i] == INTMAX || distance[i][w] == INTMAX) {
                            // v-i不可达或者i-w不可达,显然不可能通过i为中继优化v-w之间的最短路径距离
                            continue;
                        }
                        if (distance[v][i] + distance[i][w] < distance[v][w]) {
                            // 通过i为中继优化v-w之间的最短路径距离
                            distance[v][w] = distance[v][i] + distance[i][w];
                        }
                    }
                }
            }
            return distance;
        }

        // dijkstra算法求解有向有权图的最短路径,从某个源点到其余顶点的最短路径, O(N*N)
        vector<int> dijkstraShortPath(int v0) {
            vector<int> distance(len + 1, INTMAX);  // 记录v0到点i的最短路径距离
            visited.clear();
            visited.resize(len + 1, false);
            visited[v0] = true;
            distance[v0] = 0;

            for (int i = 0; i <= len; ++i) {
                if (matrix[v0][i] != INTMAX) {
                    // 初始化,如果存在从v0到i的边,则用其权值初始化
                    distance[i] = matrix[v0][i];
                }
            }

            for (int i = 1; i <= len; ++i) {
                int min = INTMAX;
                int index;
                for (int j = 0; j <= len; ++j) {
                    if (!visited[j] && distance[j] < min) {
                        min = distance[j];
                        index = j;
                    }
                }
                visited[index] = true;
                for (int k = 0; k <= len; ++k) {
                    if (!visited[k] && matrix[index][k] != INTMAX && distance[k] > min + matrix[index][k]) {
                        distance[k] = min + matrix[index][k];
                    }
                }
            }

            return distance;
        }

        // kruskal算法求解无向有权连通图的最小生成树,返回最小生成树的权值和
        int kruskalMinTree() {
            std::multimap<int, std::pair<int, int>> closedge;
            for (int i = 1; i <= len; ++i) {
                for (int j = 1; j <= len; ++j) {
                    closedge.insert(std::make_pair(matrix[i][j], std::pair<int, int>{i, j}));
                }
            }
            UnionFindSpace::UnionFind uf(len); // 并查集,用于判断两个顶点是否已经关联,即已添加的路径中可以使两个顶点相通
            vector<std::pair<int, int>> minTreeEdgeSet; // 逐步往里面添加最小生成树的边
            int weight = 0;
            while (minTreeEdgeSet.size() < len - 1) {
                auto &lowEdge = *closedge.begin(); // <weight, pari<v, w>>
                auto &points = lowEdge.second;
                if (!uf.isConnected(points.first, points.second)) {
                    minTreeEdgeSet.push_back({points.first, points.second});
                    uf.unionElements(points.first, points.second);
                    weight += lowEdge.first;
                }
                // 删除当前最小权值得边,该边可能已经被添加到最小树中,也可能不满足条件(不在两个不通的连通分量重)而没有添加
                closedge.erase(closedge.begin());
            }
            return weight;
        }

        // prim算法求解无向有权连通图的最小生成树,返回最小生成树的权值和
        int primMinTree() {
            assert(getConComponent() == 1);  // 保证当前的图为连通图且只有一个连通分量,否则无法进行Prim算法
            closedge.clear();
            closedge.resize(len + 1, CloseNode());
            visited.clear();
            visited.resize(len + 1, false);
            visited[1] = true;
            // 添加顶点1后初始化closedge
            for (int i = 1; i <= len; ++i) {
                if (!visited[i]) {
                    closedge[i].adjvex = 1;
                    closedge[i].lowcost = matrix[1][i];
                }
            }

            int weight = 0;
            vector<std::pair<int, int>> minTreeEdgeSet; // 逐步往里面添加最小生成树的边
            for (int i = 2; i <= len; ++i) {
                int index = 0;
                int min = INTMAX;
                // 从余下未添加的节点中寻找一个最小权值
                for (int k = 1; k <= len; ++k) {
                    if (!visited[k] && min > closedge[k].lowcost) {
                        index = k;
                        min = closedge[k].lowcost;
                    }
                }
                visited[index] = true;
                weight += min;
                minTreeEdgeSet.push_back({closedge[index].adjvex, index});
                // 新添加一个顶点后,刷新closedge集合
                for (int j = 1; j <= len; ++j) {
                    if (!visited[j] && matrix[index][j] < closedge[j].lowcost) {
                        closedge[j].lowcost = matrix[index][j];
                        closedge[j].adjvex = index;
                    }
                }
            }
            assert(len - 1 == minTreeEdgeSet.size()); // 最小生成树的边为n-1(n为节点数量)
            std::for_each(minTreeEdgeSet.cbegin(), minTreeEdgeSet.cend(),
                          [](std::pair<int, int> p) { std::cout << p.first << " - " << p.second << std::endl; });
            return weight;
        }

        // 求图中指定顶点的度,注意有向图和无向图的区别
        int getDegree(int v) {
            int degree = 0;
            for (int flag : matrix[v]) {
                degree += flag;
            }
            if (type == GraphType::DUWG) {
                // 有向图需要加上入度
                int inDegree = 0;
                for (int i = 1; i <= len; ++i) {
                    inDegree += matrix[i][v];
                }
                degree += inDegree;
            }
            return degree;
        }

        // 获取指定顶点(存储点)的邻接点(存储点)
        vector<int> getAdjPoint(const int v) {
            vector<int> adjPoints;
            auto &ws = matrix[v]; // 顶点v和其他定点是否有边的数组结合
            for (int i = 1; i <= len; ++i) {
                if (ws[i] == 0) {
                    // 与v不邻接
                    continue;
                }
                adjPoints.push_back(i);
            }
            return adjPoints;
        }

        // 图的深度遍历
        void dfsTraverse() {
            hasDfs = true;
            count = 0;  // 连通分量初始化
            visited.clear();  // 数据清理,非常重要
            visited.resize(len + 1, false); // 访问标志初始化
            id.clear(); // 数据清理,非常重要
            id.resize(len + 1, 0); //  每个顶点的连通分量初始化,从下标1开始使用
            std::cout << "start dfsTraverse: ";
            for (int v = 1; v <= len; ++v) {
                if (!visited[v]) {
                    ++count; // 连通分量
                    dfs(v);
                }
            }
            std::cout << "END\n" << std::flush;
        }

        // 获取连通分量
        int getConComponent() {
            if (!hasDfs) {
                dfsTraverse();
            }
            return count;
        }

        // 判断两个定点是否相连
        bool isConnected(int v, int w) {
            if (!hasDfs) {
                dfsTraverse();
            }
            return id[v] == id[w];
        }

        // 图的广度遍历
        void bfsTraverse() {
            visited.clear();  // 数据清理,非常重要
            visited.resize(len + 1, false); // 访问标志初始化
            from.clear();
            from.resize(len + 1, 0);
            std::queue<int> q; // 队列初始化,逻辑点
            std::cout << "start bfsTraverse: ";
            for (int v = 1; v <= len; ++v) {
                if (!visited[v]) {
                    std::cout << v << " -> "; // 访问并打印该节点,注意,应该在最靠近visited判断的地方进行访问,且在访问后立即进行visited标记
                    visited[v] = true;
                    q.push(v);
                    while (!q.empty()) {
                        int w = q.front();
                        q.pop();
                        auto adjPoints = getAdjPoint(w);
                        for (auto adjp : adjPoints) {
                            // 将v的邻接点加入到队列
                            if (!visited[adjp]) {
                                std::cout << adjp << " -> ";
                                from[adjp] = w; // (w, adjp)存在一条边
                                visited[adjp] = true;
                                q.push(adjp);
                            }
                        }
                    }
                }
            }
            std::cout << "END\n" << std::flush;
        }

        // 返回v -> w的最短路径[v, ..., w],如果不存在返回空
        vector<int> getShortPathFromUnWeighted(int v, int w) {
            if (!isConnected(v, w)) {
                return vector<int>();
            }
            if (!hasBfs) {
                bfsTraverse();
            }
            std::stack<int> record;
            record.push(w);
            while (from[w] != v) {
                record.push(from[w]);
                w = from[w];
            }
            record.push(v);
            vector<int> res;
            std::cout << "start output unweighted graph shorted path: \n";
            while (!record.empty()) {
                int t = record.top();
                std::cout << t << " -->> ";
                res.push_back(t);
                record.pop();
            }
            std::cout << "END\n" << std::flush;
            return res;
        }

    private:
        // 深度遍历递归公共函数,从顶点v(逻辑点)开始遍历
        void dfs(const int v) {
            id[v] = count;  // 记录当前访问顶点的连通分量
            visited[v] = true;
            std::cout << v << " -> "; // 访问并打印该节点
            auto adjPoints = getAdjPoint(v);
            for (int w : adjPoints) {
                // 与v邻接的定点
                if (!visited[w]) {
                    // 对v的尚未访问邻接节点递归调用dfs
                    dfs(w); // 存储点 +1 转换为逻辑点
                }
            }
        }

    private:
        vector<vector<int>> matrix; // 图的邻接矩阵存储,从1行1列开始存储,0行0列保留不使用
        GraphType type; // 图类型
        int len = 0;  // 顶点数量
        vector<bool> visited; // 是否被访问标志,从下标1开始使用
        int count = 0; // 连通分量
        vector<int> id;  // 顶点的连通分量,如果id[v] == id[w],则v,w两点间连通,从下标1开始使用
        bool hasDfs = false;  // 是否已进行深度遍历
        bool hasBfs = false; // 是否已进行广度遍历
        vector<CloseNode> closedge; // prim算法辅助数组
        vector<int> from; // 记录广度优先遍历中每个节点的上一个被访问节点,即如果(v,w)存在表,则from[w] = v,用于无权图的最短路径记录
    };

测试

1)无权图一般测试

    void graphTest() {
        Graph dg1(4, vector<vector<int>>{{1, 2},
                                         {1, 3},
                                         {3, 4},
                                         {4, 1}}, GraphType::DUWG);
        Graph udg2(5, vector<vector<int>>{{1, 2},
                                          {1, 4},
                                          {2, 3},
                                          {2, 5},
                                          {3, 4},
                                          {3, 5}}, GraphType::UDUWG);
        Graph udg3(13, vector<vector<int>>{{1,  2},
                                           {1,  3},
                                           {1,  6},
                                           {1,  12},
                                           {2,  13},
                                           {4,  5},
                                           {7,  8},
                                           {7,  9},
                                           {7,  11},
                                           {8,  11},
                                           {10, 12},
                                           {10, 13},
                                           {12, 13}}, GraphType::UDUWG);
        Graph udg4(8, vector<vector<int>>{{1, 2},
                                          {1, 3},
                                          {2, 4},
                                          {2, 5},
                                          {4, 8},
                                          {5, 8},
                                          {3, 6},
                                          {3, 7},
                                          {6, 7}}, GraphType::UDUWG);
        Graph udg5(6, vector<vector<int>>{{1, 2},
                                          {1, 3},
                                          {2, 4},
                                          {2, 5},
                                          {3, 6}}, GraphType::UDUWG);
        // 测试顶点的度
        assert(3 == dg1.getDegree(1));
        assert(1 == dg1.getDegree(2));
        assert(2 == dg1.getDegree(3));
        assert(2 == dg1.getDegree(4));

        assert(2 == udg2.getDegree(1));
        assert(3 == udg2.getDegree(2));
        assert(3 == udg2.getDegree(3));
        assert(2 == udg2.getDegree(4));
        assert(2 == udg2.getDegree(5));

        assert(4 == udg3.getDegree(1));
        assert(1 == udg3.getDegree(3));
        assert(2 == udg3.getDegree(2));
        assert(3 == udg3.getDegree(7));
        assert(2 == udg3.getDegree(10));

        // 图的遍历,连通分量
        dg1.dfsTraverse();
        dg1.bfsTraverse();

        udg2.dfsTraverse();
        udg2.bfsTraverse();

        udg5.dfsTraverse();
        udg5.bfsTraverse();

        udg4.dfsTraverse();
        udg4.bfsTraverse();
        assert(1 == udg4.getConComponent());
        assert(udg4.isConnected(1, 2));
        assert(udg4.isConnected(2, 3));
        assert(udg4.isConnected(3, 4));
        assert(udg4.isConnected(4, 5));
        assert(udg4.isConnected(5, 6));
        assert(udg4.isConnected(6, 7));
        udg3.dfsTraverse();
        udg3.bfsTraverse();
        assert(3 == udg3.getConComponent());
        assert(udg3.isConnected(1, 2));
        assert(udg3.isConnected(2, 3));
        assert(udg3.isConnected(3, 6));
        assert(udg3.isConnected(6, 12));
        assert(udg3.isConnected(12, 10));
        assert(udg3.isConnected(10, 13));

        assert(udg3.isConnected(4, 5));

        assert(udg3.isConnected(7, 8));
        assert(udg3.isConnected(8, 9));
        assert(udg3.isConnected(9, 11));

        return;
    }

2)无向有权图最小生成树测试

   void minTreeTest() {
        Graph udwg1(6, vector<vector<int>>{{1, 2, 6},
                                           {1, 3, 1},
                                           {1, 4, 5},
                                           {2, 5, 3},
                                           {2, 3, 5},
                                           {4, 3, 5},
                                           {4, 6, 2},
                                           {3, 5, 6},
                                           {3, 6, 4},
                                           {5, 6, 6}}, GraphType::UDWG);
        assert(15 == udwg1.primMinTree());
        assert(15 == udwg1.kruskalMinTree());
        return;
    }

3)有向有权图最短路径测试

   void shortPathTest() {
        Graph dwg(6, vector<vector<int>>{{0, 5, 100},
                                         {0, 4, 30},
                                         {0, 2, 10},
                                         {1, 2, 5},
                                         {2, 3, 50},
                                         {3, 5, 10},
                                         {4, 3, 20},
                                         {4, 5, 60}}, GraphType::DWG);
        auto d = dwg.dijkstraShortPath(0);
        assert(0 == d[0]);
        assert(INTMAX == d[1]);
        assert(10 == d[2]);
        assert(50 == d[3]);
        assert(30 == d[4]);
        assert(60 == d[5]);

        auto dis = dwg.floydShortPath();
        assert(0 == dis[0][0]);
        assert(INTMAX == dis[0][1]);
        assert(10 == dis[0][2]);
        assert(50 == dis[0][3]);
        assert(30 == dis[0][4]);
        assert(60 == dis[0][5]);

        assert(INTMAX == dis[1][0]);
        assert(0 == dis[1][1]);
        assert(5 == dis[1][2]);
        assert(55 == dis[1][3]);
        assert(INTMAX == dis[1][4]);
        assert(65 == dis[1][5]);

        assert(INTMAX == dis[2][0]);
        assert(INTMAX == dis[2][1]);
        assert(0 == dis[2][2]);
        assert(50 == dis[2][3]);
        assert(INTMAX == dis[2][4]);
        assert(60 == dis[2][5]);

        assert(INTMAX == dis[3][0]);
        assert(INTMAX == dis[3][1]);
        assert(INTMAX == dis[3][2]);
        assert(0 == dis[3][3]);
        assert(INTMAX == dis[3][4]);
        assert(10 == dis[3][5]);

        assert(INTMAX == dis[4][0]);
        assert(INTMAX == dis[4][1]);
        assert(INTMAX == dis[4][2]);
        assert(20 == dis[4][3]);
        assert(0 == dis[4][4]);
        assert(30 == dis[4][5]);

        assert(INTMAX == dis[5][0]);
        assert(INTMAX == dis[5][1]);
        assert(INTMAX == dis[5][2]);
        assert(INTMAX == dis[5][3]);
        assert(INTMAX == dis[5][4]);
        assert(0 == dis[5][5]);
        return;
    }

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值