基础算法--图论

本文介绍了图论中的基本概念,包括顶点、边及其属性如度数、有向图和无向图。讨论了图的表示方法,如邻接矩阵和邻接表,并提供了这两种表示的C++实现示例。此外,还提到了图的遍历算法,包括广度优先搜索(BFS)和深度优先搜索(DFS)。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

基础算法–图论

基本概念

一个图是由顶点集 V V V和边集 E E E组成的。其中, V V V E E E都是非空集合, V V V中的元素称为顶点, E E E中的元素称为边。边是顶点的有序对,即 E = { ( v i , v j ) ∣ v i , v j ∈ V } E=\{(v_i,v_j)|v_i,v_j\in V\} E={(vi,vj)vi,vjV}

  • 顶点的属性

    • 度数:与该顶点相关联的总边数(一个图 G G G的总度数 d ( V ) d(V) d(V)等于总边数 2 2 2倍,即 2 E 2E 2E)。当图的边具有方向时(即有向图),一个顶点又分为出度和入度
      • 出度:是以该顶点为起点的边数
      • 入度:以该顶点为终点的边数
      • 稀疏图:每个顶点的度数较小的图
      • 稠密图:每个顶点的度数较大的图,完全图是稠密图。
    • 阶数:图 G G G中顶点集 V V V的大小为 G G G的阶数
  • 边(又称为线或弧,边 ( u , v ) (u, v) (u,v)中表示 u u u v v v邻接, ( u , v ) ∈ E (u, v) \in E (u,v)E)的属性

    • 有/无向图:一条边是一个顶点对 ( u , v ) (u,v) (u,v) u , v ∈ V u, v \in V u,vV,按照图的顶点对是否有序,顶点对有序的图称为有向图,此时边 ( u , v ) (u,v) (u,v) ( v , u ) (v, u) (v,u)是两条不同的边,顶点对无序的图称为无向图,此时边 ( u , v ) (u, v) (u,v) ( v , u ) (v, u) (v,u)是两条相同的边,无向图可看作一个特殊的有向图
    • 具有成分的边:依据图的中边是否包含权值,可将图分为有权图无权图,无权图可以看作是所有边权值相同的有权图
    • 路径:一条路径是一个顶点序列 u 1 , u 2 , u 3 , … , u n , ( u i , u ( i + 1 ) ) ∈ E , 1 < = i < n u_1, u_2, u_3, …, u_n,(u_i, u_{(i + 1)}) \in E,1 <= i< n u1,u2,u3,,un(ui,u(i+1))E1<=i<n。路径长等于路径的边数(即 n − 1 n - 1 n1),不包含边的路径长为 0 0 0
      • 简单路径:路径上所有顶点互异,起点和终点可以相同
      • 环:此时 u 1 = u n u_1 = u_n u1=un,而且路径长至少为 1 1 1,有向图 ( u , v ) (u, v) (u,v) ( v , u ) (v, u) (v,u)是一个圈,无向图 ( u , v ) (u, v) (u,v) ( v , u ) (v, u) (v,u)通常不被认为是一个圈
        • 自环边:两个顶点都相同的边
        • 重边(平行边):连接两个顶点的边数超过一条,又称为多重边
        • 连通图:无向图中每个顶点到任意顶点都存在一条路径的图称为连通图。其中连通的有向图称为强连通,非连通的有向图,去掉方向其基础图是连通的,则成为弱连通的
        • 完全图:图中任意一对顶点都存在一条边

图的表示

图的表示常见有两种方式,但是具体使用哪个表示方式视情况而定,这两个方式分别是邻接矩阵邻接表

邻接矩阵表示

对于邻接矩阵,一般是针对稠密图,比如完全图,它使用一个二维数组表示,此时空间需求为 V 2 V^2 V2,如下图是一个邻接矩阵:
邻接矩阵
对于无向图 A [ u ] [ v ] = 1 A[u][v] = 1 A[u][v]=1表示顶点 u u u v v v有一条边连接,同时 A [ v ] [ u ] = 1 A[v][u] = 1 A[v][u]=1。对于有向图, A [ v ] [ u ] = 1 A[v][u] = 1 A[v][u]=1表示以 u u u为起点, v v v为终点的边,对于有权图, A [ v ] [ u ] A[v][u] A[v][u]为权值,可以使用很大或很小的权值表示不存在的边。邻接矩阵删除和插入边时间为 O ( 1 ) O(1) O(1)。下面给出一个简单的无向无权图的邻接矩阵表示法

#include <iostream>
#include <vector>

struct Node {
    int data;
    Node(int v) {
        data = v;
    }
};

struct Graph {
    size_t v;                              // 顶点数
    std::vector<std::vector<int>> matrix;  // 邻接矩阵
    Graph(size_t _v) {
        v = _v;
        matrix = std::vector<std::vector<int>>(v, std::vector<int>(v, 0));
    }
};

邻接表

图的另一种表示方式是邻接表,一般来说针对稀疏图,针对完全图那和上面的邻接矩阵一样了。该方式使用一个邻接表数组 l i s t [ V ] list[V] list[V]表示图,也就是对于每个顶点,使用一个表存放与该顶点邻接的所有顶点,一共有 V V V个顶点,空间需求为 V + E V+E V+E,邻接表的实例如下图

邻接表
如果边有权值,可以在表中每一个单元额外存储权值信息,可以使用一个Pair结构体表示
G G G实际是一个邻接表数组,横向存储的顶点是与一个顶点邻接的,该组合为一个邻接表,整体上可以使用一个数组,邻接表根据使用不用的语言实现可以有不同的选择:

  • 数组或链表(vectorlist),此时允许平行边或重边和自环边,查询时间为 O ( V ) O(V) O(V),插入时间为 O ( 1 ) O(1) O(1)
  • 树,不允许平行边,查询时间和插入时间都为 O ( log ⁡ V ) O(\log V) O(logV)`
  • 散列表(hashtable),无序,不允许平行边,插入查询时间为 O ( 1 ) O(1) O(1)

以上要注意的主要是针对有权图的设计,特别是有权然后还有其它相关属性的时候,那么这时可以使用一个结构体类型表示一个单元

图的遍历

图的遍历算法其实我们早在搜索算法里面中已经提到,里面有对广度优先和深度优先算法的详细介绍。图论遍历算法主要有两种:广度优先遍历(BFS)和深度优先遍历(DFS),这两个遍历算法是图论算法的基本形式。我们这里就直接给出代码,不在详细叙述。

void bfs(Graph *graph, std::vector<Node *> &nodes) {
    if (nodes.empty() || graph == nullptr || graph->v != nodes.size()) return;
    std::queue<Node *> queue;
    std::vector<bool> visited(nodes.size(), false);
    queue.push(nodes[0]);
    visited[0] = true;
    size_t idx = 0;
    while (!queue.empty()) {
        Node *n = queue.front();
        std::cout << n->data << " ";
        for (int i = 0; i < graph->v; ++i) {
            if (graph->matrix[idx][i] && !visited[i]) {
                visited[i] = true;
                queue.push(nodes[i]);
            }
        }
        queue.pop();
        ++idx;
    }
    std::cout << std::endl;
}

void dfs(Graph *graph, std::vector<Node *> &nodes) {
    if (nodes.empty() || graph == nullptr || graph->v != nodes.size()) return;
    std::list<std::pair<Node *, int>> list;
    std::vector<bool> visited(nodes.size(), false);
    list.push_front(std::make_pair(nodes[0], 0));
    visited[0] = true;
    while (!list.empty()) {
        std::pair<Node *, int> pair = list.front();
        list.pop_front();
        std::cout << pair.first->data << " ";
        for (int i = graph->v - 1; i >= 0; --i) {
            if (graph->matrix[pair.second][i] && !visited[i]) {
                visited[i] = true;
                list.push_front(std::make_pair(nodes[i], i));
            }
        }
    }
    std::cout << std::endl;
}

int main() {
    std::vector<Node *> nodes;
    for (int i = 0; i < 5; ++i) {
        nodes.push_back(new Node(i));
    }
    Graph *graph = new Graph(5);
    graph_add_edge(graph, 0, 1);
    graph_add_edge(graph, 0, 2);
    graph_add_edge(graph, 0, 3);
    graph_add_edge(graph, 1, 3);
    graph_add_edge(graph, 1, 4);
    graph_add_edge(graph, 2, 3);
    graph_add_edge(graph, 3, 4);

    bfs(graph, nodes);
    dfs(graph, nodes);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

虎小黑

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

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

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

打赏作者

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

抵扣说明:

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

余额充值