【图的存储方式】邻接表、邻接矩阵、链式向前星

图的存储

图的常见存储方式有三种:邻接表、邻接矩阵、链式向前星。

1. 邻接矩阵

使用二维数组来存储图,二维数组下标代表点,二维数组的值代表连边

更具体地,我们一般使用 bool 数组来储存点与点之间的连边的信息:

  • adj[i][j] = true,表示点 i i i 和 点 j j j 之间存在一条由 i i i 指向 j j j 的边;

  • adj[i][j] = false,表示点 i i i 和点 j j j 之间没有连边。

优点:

矩阵存图法各种操作的时间复杂度相对来说都比较为优秀;

比如:想要添加一条从 i i i 连向 j j j 的边,我们只需要将 adj[i][j] 的值设置为 true 即可。

缺点:

邻接矩阵只适用于没有重边(或重边可以忽略)的情况。

存下一个图需要 O ( n 2 ) O(n^{2}) O(n2) 的空间复杂度,如果是点与点之间的连边数远小于 O ( n 2 ) O(n^{2}) O(n2) 的稀疏图,就有很多空间被浪费。

对于加权图来说,把bool数组改为int数组即可;
对于无向图来说,我们只需要同时存两条有向边,即令 adj[i][j] = adj[j][i] = true/false

2. 邻接表

使用一个动态数组,如 vector<vector<int> > adjacencyList(n) 来存边,其中 adjacencyList[u] 存储的是点 u u u 的所有出边的相关信息(终点、权重等)。

  • 对于无权重的有向图来说,adjacencyList[u] 只需要记录点 u 的所有出边上的终点即可;

  • 对于加权有向图来说,可以创建一个结构体 Edge 来记录终点和权重。

// 边的数据结构
struct Edge {
    int destination;
    int weight; // 如果需要权重的话
};

// 图的邻接表表示
class Graph {
public:
    Graph(int vertices);
    void addEdge(int source, int destination, int weight = 1);

private:
    int vertices;   // 端点的数目
    vector<vector<Edge>> adjacencyList;    // 二维数组存储边
};

// 构造函数,初始化邻接表
Graph::Graph(int vertices) {
    this->vertices = vertices;
    adjacencyList.resize(vertices);
}

// 添加边到邻接表
void Graph::addEdge(int source, int destination, int weight) {
    Edge edge;
    edge.destination = destination;
    edge.weight = weight;
    adjacencyList[source].push_back(edge);
    // 如果是无向图,还需要添加反向边
    // Edge reverse_edge;
    // reverse_edge.destination = source;
    // reverse_edge.weight = weight;
    // adjacencyList[destination].push_back(reverse_edge);
}

对于邻接表的存储方法来说,有无重边是不影响的!

那么,假设我们想要使用上述方法来存储一个图,会有什么特点呢?

可以想到的一点是,每个端点的编号是默认从 0 开始的连续整数;如果我们想要让每个端点拥有它自己的专属且独特的编号,可以用哈希表来实现;例如,使用unordered_map<int, vector<Edge> > adjacencyList(n) 来存储图。

相应地,需要修改以下部分:

// 图的邻接表表示
class Graph {
public:
    // 不需要原先的构造函数,因为 unordered_map 没有 resize()函数
    void addEdge(int source, int destination, int weight);

private:
    unordered_map<int, vector<Edge>> adjacencyList;
};

3. 链式向前星

链式向前星的思想是:使用链表来存储每个顶点的邻接边,以便有效地表示稀疏图。

// 链式向前星的节点结构体,包括节点编号和指向下一条边的指针
struct ListNode {
    int vertex;
    int weight;
    ListNode* next;

    ListNode(int _vertex, int _weight) : vertex(_vertex), weight(_weight), next(nullptr) {}
};

// 图的类定义
class Graph {
public:
    int vertices;           // 图中的顶点数量
    vector<ListNode*> adjList;  // 链式向前星数组

    Graph(int V) : vertices(V), adjList(V, nullptr) {}

    // 添加一条有向边
    void addEdge(int from, int dest, int weight) {
        ListNode* node = new ListNode(dest, weight);
        node -> next = adjList[from];
        adjList[from] = node;
    }

    // 打印链式向前星
    void print() {
        for (int i = 0; i < vertices; ++i) {
            cout << "Node " << i << " is connected to: ";
            ListNode* node = adjList[i];
            while (node != nullptr) {
                cout << "(" << node -> vertex << ", Weight: " << node -> weight << ") ";
                node = node -> next;
            }
            cout << endl;
        }
    }
};

同理,如果想要让每个端点拥有它自己的专属且独特的编号,可以用哈希表来实现,这里略过,参考邻接表的实现。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Skylar Lin

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值