图论 C++实现 笔记1-无权图

261 阅读7分钟

邻接表

image.png

邻接表适合表示稀疏图(Sparse Graph)

邻接矩阵

image.png

邻接矩阵适合表示稠密图(Dense Graph)

  • 个人理解,稠密图中需要每个点都与图中的其他所有点的大部分都相连,才能称为稠密图
  • 即使一个图,点很多,铺开也很大,但每个点都只与身边几个点相连,也只是稀疏图
  • 完全图比较适合使用邻接矩阵来表示

邻接矩阵 C++的一种实现方式

dense_graph.h 头文件

#ifndef DENSE_GRAPH_HEAD
#define DENSE_GRAPH_HEAD

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

using namespace std;

// 邻接矩阵(Adjacency Matrix)
class DenseGraph
{
private:
    int vertices;           // 点的数量
    int edges;              // 边的数量
    bool directed;          // 是否为有向图
    vector<vector<bool> > g; // 邻接矩阵 注意:这里的两个 > 不要连在一起,否则编译器会误以为是 >>
public:
    DenseGraph(int v, bool d) : vertices(v), directed(d)
    {
        this->edges = 0;
        // n * n 的矩阵
        for (int i = 0; i < vertices; i++)
            g.push_back(vector<bool>(vertices, false));
    }
    ~DenseGraph() {}
    int V() { return vertices; }
    int E() { return edges; }
    void addEdge(int v1, int v2);
    bool hasEdge(int v1, int v2);
    void show();

    // 迭代器
    class adjIterator
    {
    private:
        DenseGraph &G;
        int v;
        int index;

    public:
        adjIterator(DenseGraph &graph, int v);
        int begin();
        int next();
        bool end();
    };
};

void DenseGraph::addEdge(int v1, int v2)
{
    assert(v1 >= 0 && v1 < vertices);
    assert(v2 >= 0 && v2 < vertices);
    if (hasEdge(v1, v2))
    {
        return;
    }
    g[v1][v2] = true;
    if (!directed)
    {
        // 无向图
        g[v2][v1] = true;
    }
    edges++;
}

bool DenseGraph::hasEdge(int v1, int v2)
{
    assert(v1 >= 0 && v1 < vertices);
    assert(v2 >= 0 && v2 < vertices);
    return g[v1][v2];
}

void DenseGraph::show()
{
    cout << "节点格式:" << endl;
    for (int i = 0; i < vertices; i++)
    {
        cout << "vertex" << i << ":\t";
        for (int j = 0; j < g[i].size(); j++)
            if (g[i][j])
                cout << j << "\t";
        cout << endl;
    }
    cout << "矩阵格式:" << endl;
    for (int i = 0; i < vertices; i++)
    {
        for (int j = 0; j < vertices; j++)
            cout << g[i][j] << "\t";
        cout << endl;
    }
}

DenseGraph::adjIterator::adjIterator(DenseGraph &graph, int v) : G(graph)
{
    this->v = v;
    this->index = -1;
}

int DenseGraph::adjIterator::begin()
{
    index = -1;
    return next();
}

int DenseGraph::adjIterator::next()
{
    for (index += 1; index < G.V(); index++)
        if (G.g[v][index])
            return index;
    return -1;
}

bool DenseGraph::adjIterator::end()
{
    return index >= G.V();
}
#endif
// 测试用例
#include "dense_graph.h"

void testcase1()
{
    int V = 20;
    int E = 100;
    srand(time(NULL));

    DenseGraph dg(V, false);
    for (int i = 0; i < E; i++)
    {
        int a = rand() % V;
        int b = rand() % V;
        dg.addEdge(a, b);
    }

    for (int v = 0; v < V; v++)
    {
        cout << v << " : ";
        DenseGraph::adjIterator adj(dg, v);
        for (int w = adj.begin(); !adj.end(); w = adj.next())
            cout << w << " ";
        cout << endl;
    }
}    

邻接表 C++的一种实现方式

sparse_graph.h

#ifndef SPARSE_GRAPH_HEAD
#define SPARSE_GRAPH_HEAD

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

using namespace std;

// 邻接表
class SparseGraph
{
private:
    int vertices;          // 点的数量
    int edges;             // 边的数量
    bool directed;         // 是否为有向图
    vector< vector<int> > g; // 邻接表 注意:这里的两个 > 不要连在一起,否则编译器会误以为是 >>
public:
    SparseGraph(int v, bool d) : vertices(v), directed(d)
    {
        this->edges = 0;
        for (int i = 0; i < vertices; i++)
            g.push_back(vector<int>()); // 初始化每个顶点的邻接点
    }
    ~SparseGraph() {}
    int V() { return vertices; }
    int E() { return edges; }
    void addEdge(int v1, int v2);
    bool hasEdge(int v1, int v2);
    void show();

    // 迭代器
    class adjIterator
    {
    private:
        SparseGraph &G;
        int v;
        int index;

    public:
        adjIterator(SparseGraph &graph, int v);
        int begin();
        int next();
        bool end();
    };
};

void SparseGraph::addEdge(int v1, int v2)
{
    assert(v1 >= 0 && v1 < vertices);
    assert(v2 >= 0 && v2 < vertices);
    // 关于平行边,可以在图构建完成后进行统一处理
    // if (hasEdge(v1, v2))
    //     return;
    g[v1].push_back(v2);
    if (!directed && v1 != v2)
    {
        g[v2].push_back(v1);
    }
    edges++;
}

bool SparseGraph::hasEdge(int v1, int v2)
{
    assert(v1 >= 0 && v1 < vertices);
    assert(v2 >= 0 && v2 < vertices);
    for (int i = 0; i < g[v1].size(); i++)
    {
        if (g[v1][i] == v2)
            return true;
    }
    return false;
}

void SparseGraph::show()
{
    for (int i = 0; i < vertices; i++)
    {
        cout << "vertex" << i << ":\t";
        for (int j = 0; j < g[i].size(); j++)
            cout << g[i][j] << "\t";
        cout << endl;
    }
}

SparseGraph::adjIterator::adjIterator(SparseGraph &graph, int v) : G(graph)
{
    this->index = 0;
    this->v = v;
}

int SparseGraph::adjIterator::begin()
{
    index = 0;
    if (G.g[v].size())
        return G.g[v][index];
    return -1;
}

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

bool SparseGraph::adjIterator::end()
{
    return index >= G.g[v].size();
}
#endif
// 测试用例
#include "sparse_graph.h"
void testcase2()
{
    int V = 20;
    int E = 100;
    srand(time(NULL));

    SparseGraph sg(V, false);
    for (int i = 0; i < E; i++)
    {
        int a = rand() % V;
        int b = rand() % V;
        sg.addEdge(a, b);
    }

    for (int v = 0; v < V; v++)
    {
        cout << v << " : ";
        SparseGraph::adjIterator adj(sg, v);
        for (int w = adj.begin(); !adj.end(); w = adj.next())
            cout << w << " ";
        cout << endl;
    }
}

读取文件构建图

read_graph.h 头文件

#ifndef READ_GRAPH_HEAD
#define READ_GRAPH_HEAD

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

using namespace std;

// 模板类
template <typename Graph>
class ReadGraph
{
public:
    ReadGraph(Graph &g, const string &file_name)
    {
        // 文件输入流
        ifstream file(file_name);
        string line;
        int V, E;

        // 检验文件是否打开成功
        assert(file.is_open());
        // 获取文件内容的第一行
        assert(getline(file, line));

        // 将字符串转换成字符流
        stringstream ss(line);
        // 解析字符流
        ss >> V >> E;

        assert(V == g.V());

        for (int i = 0; i < E; i++)
        {
            assert(getline(file, line));
            stringstream ss(line);
            int v1, v2;
            ss >> v1 >> v2;
            assert(v1 >= 0 && v1 < V);
            assert(v2 >= 0 && v2 < V);
            g.addEdge(v1, v2);
        }
    }
};

#endif

graph_data1.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

测试用例

#include <iostream>
#include "sparse_graph.h"
#include "dense_graph.h"
#include "read_graph.h"

using namespace std;

void testcase3()
{
    string file_name = "graph_data1.txt";
    SparseGraph sg(13, false);
    ReadGraph<SparseGraph> readGraph(sg, file_name);
    sg.show();
    cout << "---" << endl;

    DenseGraph dg(13, false);
    ReadGraph<DenseGraph> readGraph2(dg, file_name);
    dg.show();
}

int main()
{
    testcase3();
    return 0;
}

图的深度优先遍历

graph_dfs.h

#ifndef GRAPH_DFS_HEAD
#define GRAPH_DFS_HEAD

#include <iostream>
#include <cassert>

using namespace std;

// 模板类
template <typename Graph>
class GraphDfs
{
private:
    Graph &G;
    bool *visited;
    int *id;
    int ccount; // 连通分量

public:
    GraphDfs(Graph &graph) : G(graph)
    {
        visited = new bool[G.V()];
        id = new int[G.V()];
        ccount = 0;
        for (int i = 0; i < G.V(); i++)
        {
            visited[i] = false;
            id[i] = -1;
        }
        for (int i = 0; i < G.V(); i++)
        {
            if (!visited[i])
            {
                dfs(i);
                cout << endl;
                ccount++;
            }
        }
    }
    ~GraphDfs()
    {
        delete[] visited;
        delete[] id;
    }
    void dfs(int v);
    int count() { return ccount; }
    bool isConnect(int v1, int v2)
    {
        assert(v1 >= 0 && v1 < G.V());
        assert(v2 >= 0 && v2 < G.V());
        return id[v1] == id[v2];
    }
};

template <typename Graph>
void GraphDfs<Graph>::dfs(int v)
{
    cout << v << "\t";
    visited[v] = true;
    id[v] = ccount + 1;
    typename Graph::adjIterator adj(G, v);
    for (int i = adj.begin(); !adj.end(); i = adj.next())
    {
        if (!visited[i])
            dfs(i);
    }
}

#endif

测试用例

#include <iostream>
#include "sparse_graph.h"
#include "dense_graph.h"
#include "read_graph.h"
#include "graph_dfs.h"

using namespace std;

void testcase4()
{
    string file_name = "graph_data1.txt";
    SparseGraph sg(13, false);
    ReadGraph<SparseGraph> readGraph(sg, file_name);
    GraphDfs<SparseGraph> dfs(sg);
    // dfs.dfs(0); // 从节点0开始进行深度优先遍历
    cout << "连通分量:" << dfs.count() << endl;
    cout << dfs.isConnect(7, 8) << endl;
}

int main()
{
    testcase4();
    return 0;
}

图的深度优先遍历的寻路

path.h

#ifndef GRAPH_PATH_HEAD
#define GRAPH_PATH_HEAD

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

using namespace std;

// 模板类
template <typename Graph>
class Path
{
private:
    Graph &G;
    int v_start;
    bool *visited;
    int *from;
    // 遍历v的相邻节点
    void dfs(int v)
    {
        visited[v] = true;
        typename Graph::adjIterator adj(G, v);
        for (int i = adj.begin(); !adj.end(); i = adj.next())
        {
            if (!visited[i])
            {
                from[i] = v;
                dfs(i);
            }
        }
    }

public:
    Path(Graph &graph, int s) : G(graph)
    {
        assert(s >= 0 && s < G.V());
        visited = new bool[G.V()];
        from = new int[G.V()];
        for (int i = 0; i < G.V(); i++)
        {
            visited[i] = false;
            from[i] = -1;
        }
        this->v_start = s;

        // 寻路
        dfs(s);
    }
    ~Path()
    {
        delete[] visited;
        delete[] from;
    }
    bool hasPath(int v);
    void path(int v, vector<int> &vec);
    void showPath(int v);
};

template <typename Graph>
bool Path<Graph>::hasPath(int v)
{
    assert(v >= 0 && v < G.V());
    return visited[v];
}

template <typename Graph>
void Path<Graph>::path(int v, vector<int> &vec)
{
    assert(v >= 0 && v < G.V());
    stack<int> s;
    int p = v;
    while (p != -1)
    {
        s.push(p);
        p = from[p];
    }

    //
    vec.clear();
    while (!s.empty())
    {
        vec.push_back(s.top());
        s.pop();
    }
}

template <typename Graph>
void Path<Graph>::showPath(int v)
{
    assert(v >= 0 && v < G.V());
    vector<int> vec;
    path(v, vec);
    for (int i = 0; i < vec.size(); i++)
    {
        cout << vec[i];
        if (i == vec.size() - 1)
            cout << endl;
        else
            cout << " -> ";
    }
}
#endif

graph_data2.txt

7 8
0 1
0 2
0 5
0 6
3 4
3 5
4 5
4 6

测试用例

#include <iostream>
#include "sparse_graph.h"
#include "dense_graph.h"
#include "read_graph.h"
#include "path.h"

using namespace std;

void testcase5()
{
    string file_name = "graph_data2.txt";
    SparseGraph sg(7, false);
    ReadGraph<SparseGraph> readGraph(sg, file_name);

    // 寻找节点0到节点6的路径
    Path<SparseGraph> p(sg, 0);
    p.showPath(6);
}

int main()
{
    testcase5();
    return 0;
}

图的深度优先遍历的时间复杂度

邻接表 O(V+E)

邻接矩阵 O(V^2)

图的广度优先遍历

广度优先遍历可以得到无权图最短路径

shortest_path.h

#ifndef GRAPH_SHORTEST_PATH_HEAD
#define GRAPH_SHORTEST_PATH_HEAD

#include <iostream>
#include <cassert>
#include <vector>
#include <stack>
#include <queue>

using namespace std;

// 模板类
template <typename Graph>
class ShortestPath
{
private:
    Graph &G;
    int v_start; //     起始点
    bool *visited;
    int *from; //
    int *ord;  // 距离起始点的距离
public:
    ShortestPath(Graph &graph, int s) : G(graph)
    {
        assert(s >= 0 && s < G.V());
        visited = new bool[G.V()];
        from = new int[G.V()];
        ord = new int[G.V()];
        for (int i = 0; i < G.V(); i++)
        {
            visited[i] = false;
            from[i] = -1;
            ord[i] = -1;
        }
        this->v_start = s;

        // 无向图最短路径
        queue<int> q;
        // 广度优先遍历
        q.push(s);
        visited[s] = true;
        ord[s] = 0;
        while (!q.empty())
        {
            int v_top = q.front();
            q.pop();

            typename Graph::adjIterator adj(G, v_top);
            for (int i = adj.begin(); !adj.end(); i = adj.next())
            {
                if (!visited[i])
                {
                    q.push(i);
                    visited[i] = true;
                    from[i] = v_top; // 记录来源 : i 是从 v_top 的遍历而来
                    ord[i] = ord[v_top] + 1;
                }
            }
        }
    }
    ~ShortestPath()
    {
        delete[] visited;
        delete[] from;
        delete[] ord;
    }
    bool hasPath(int v);
    void path(int v, vector<int> &vec);
    void showPath(int v);
    int length(int v);
};

template <typename Graph>
bool ShortestPath<Graph>::hasPath(int v)
{
    assert(v >= 0 && v < G.V());
    return visited[v];
}

template <typename Graph>
int ShortestPath<Graph>::length(int v)
{
    assert(v >= 0 && v < G.V());
    return ord[v];
}

template <typename Graph>
void ShortestPath<Graph>::path(int v, vector<int> &vec)
{
    assert(v >= 0 && v < G.V());
    stack<int> s;
    int p = v;
    while (p != -1)
    {
        s.push(p);
        p = from[p];
    }

    //
    vec.clear();
    while (!s.empty())
    {
        vec.push_back(s.top());
        s.pop();
    }
}

template <typename Graph>
void ShortestPath<Graph>::showPath(int v)
{
    assert(v >= 0 && v < G.V());
    vector<int> vec;
    path(v, vec);
    for (int i = 0; i < vec.size(); i++)
    {
        cout << vec[i];
        if (i == vec.size() - 1)
            cout << endl;
        else
            cout << " -> ";
    }
}
#endif

测试用例

#include <iostream>
#include "sparse_graph.h"
#include "dense_graph.h"
#include "read_graph.h"
#include "graph_dfs.h"
#include "path.h"
#include "shortest_path.h"

using namespace std;

void testcase5()
{
    string file_name = "graph_data2.txt";
    SparseGraph sg(7, false);
    ReadGraph<SparseGraph> readGraph(sg, file_name);

    // 寻找节点0到节点6的路径
    Path<SparseGraph> p(sg, 0);
    p.showPath(6);

    // 广度优先算法寻找无权图的最短路径
    ShortestPath<SparseGraph> sp(sg, 0);
    sp.showPath(6);
}

int main()
{
    testcase5();
    return 0;
}