图的概念、存储与遍历

本文详细介绍了图的概念,包括有向图、无向图、节点度、入度、出度和权值等。接着,对比了邻接矩阵、邻接表和链式前向星三种图的存储方式,并阐述了它们的适用场景及优缺点。最后,深度解析了深度优先搜索(DFS)和广度优先搜索(BFS)两种常用的图遍历算法。
摘要由CSDN通过智能技术生成

图的概念、存储与遍历

一、图

图 (Graph) 是由顶点的非空集合和顶点 (结点) 之间边的集合组成,通常表示为 G = ( V , E ) G =(V,E) G=(VE)

其中 G G G 表示一个图, V V V 是图 G G G 中的顶点集合 (Vertex Set) , E E E 是图 G G G 中边的集合 (Edge Set) ;

二、概念

1. 有向图

如果边 x → y x \rightarrow y xy 在边集中,同时 y → x y \rightarrow x yx 不在边集中,则称图 G = ( V , E ) G=(V, E) G=(V,E) 为有向图;

有向图概念-1

2. 无向图

如果边 x → y x \rightarrow y xy 在边集中,同时 y → x y \rightarrow x yx 也在边集中,则称图 G = ( V , E ) G=(V, E) G=(V,E) 为无向图;

无向图概念-2

3. 结点的度

无向图中与结点相连的边的数目,称为结点的度;

无向图样例-3

节点编号
13
22
33
41
54
63

4. 结点的入度

有向图中以结点为终点的有向边的数目为此节点的入度;

有向图样例-4

节点编号入度
10
20
31
41
53
63

5. 结点的出度

有向图中以结点为起点的有向边的数目为此节点的出度;

有向图样例-4

节点编号入度
13
22
32
40
51
60

6. 权值

边的权值即为边的大小或者长度;

7. 连通图

某图中任意两个顶点之间都连通,则称此图是连通图;

连通图示例-5

8. 联通分量

非连通图的极大连通子图称为连通分量;

9. 强连通分量

非连通图的最大连通子图称为强连通分量;

10. 回路

如果图中存在一个结点,使得从该结点出发沿着边走能够回到起始结点,则称该路径为回路 (环) ;

回路示例-6

回路1, 1 → 2 → 5 → 1 1\rightarrow 2 \rightarrow 5 \rightarrow 1 1251

回路2, 1 → 2 → 3 → 4 → 5 → 1 1 \rightarrow 2 \rightarrow 3 \rightarrow 4 \rightarrow 5 \rightarrow 1 123451

回路3, 2 → 5 → 1 → 2 2 \rightarrow 5 \rightarrow 1 \rightarrow 2 2512

回路4, 2 → 3 → 4 → 5 → 2 2 \rightarrow 3 \rightarrow 4 \rightarrow 5 \rightarrow 2 23452

回路5, 3 → 4 → 5 → 2 → 3 3 \rightarrow 4 \rightarrow 5 \rightarrow 2 \rightarrow 3 34523

11. 稀疏图

对于图,其边的条数 ∣ E ∣ |E| E 远小于 ∣ V ∣ 2 |V|^2 V2 的图称为稀疏图;

12、稠密图

对于图,其边的条数 ∣ E ∣ |E| E 接近 ∣ V ∣ 2 |V|^2 V2 的图称为稀疏图;

边越多,图越稠密,反之越稀疏;

三、图的存储

1. 邻接矩阵

思路

对于具有 n n n 个顶点的有向无权图,可以使用一个 n ∗ n n * n nn 的矩阵来表示图中的边;

令矩阵为 g g g ,若顶点 i i i 到顶点 j j j 之间有一条边,则置 g [ i ] [ j ] = 1 g[i][j] = 1 g[i][j]=1,否则置 g [ i ] [ j ] = 0 g[i][j]=0 g[i][j]=0

对于有向有权图,若顶点 i i i 到顶点 j j j 之间有一条边权值为 a i , j a_{i,j} ai,j ,则置 g [ i ] [ j ] = a i , j g[i][j]=a_{i,j} g[i][j]=ai,j,否则置 g [ i ] [ j ] = ∞ g[i][j]=\infty g[i][j]=

对于其优缺点,

  1. 优点,可以快速查询某条边是否存在;

  2. 缺点,若要查询某个顶点相连的顶点,需要对矩阵进行遍历,不是很方便;其次当顶点数量较多但边数较少 (稀疏图) ,邻接矩阵表示法效率不高,矩阵中会有大量的元素为 0 ( ∞ ) (\infty) ()

无向图的邻接矩阵关于左上角到右下角对称;

实现

定义 g [ i ] [ j ] g[i][j] g[i][j] 表示从点 i i i 到点 j j j 的边的权值,定义如下,

g [ i ] [ j ] = { 1 或权值 ( 当 v i 与 v j 之间有边或弧时,取值为 1 或权值 ) 0 或极大值 ( 当 v i 与 v j 之间无边或弧时,取值为 0 或极大值 ) g[i][j] = \begin{cases} 1 或 权值 & (当v_i 与 v_j 之间有边或弧时,取值为1或权值) \\ 0 或 极大值 & (当v_i 与 v_j 之间无边或弧时,取值为0或极大值) \end{cases} g[i][j]={1或权值0或极大值(vivj之间有边或弧时,取值为1或权值)(vivj之间无边或弧时,取值为0或极大值)

邻接矩阵一般适用于稠密图;

例子

有向图样例-4

用邻接矩阵表示:

123456
10 ∞ \infty 1 ∞ \infty 43
2 ∞ \infty 0 ∞ \infty ∞ \infty 210
3 ∞ \infty ∞ \infty 056 ∞ \infty
4 ∞ \infty ∞ \infty ∞ \infty 0 ∞ \infty ∞ \infty
5 ∞ \infty ∞ \infty ∞ \infty ∞ \infty 07
6 ∞ \infty ∞ \infty ∞ \infty ∞ \infty ∞ \infty 0
代码
有向有权图
int g[MAXN][MAXN]; // 定义g数组,表示从点i到点j的边的权值
void input(int n) { //n为边数;
    memset(g, 127, sizeof(g)); // 初始化为极大值;
    for (int i = 1; i <= n; i++) {
        int x, y, z;
        scanf("%d %d %d", &x, &y, &z);
        g[x][y] = z;
    }
}
有向无权图
int g[MAXN][MAXN]; // 定义g数组,表示从点i到点j的边的权值
void input(int n) { //n为边数;
     for (int i = 1; i <= n; i++) {
        int x, y;
        scanf("%d %d", &x, &y);
        g[x][y] = 1;
    }
}
无向有权图
int g[MAXN][MAXN]; // 定义g数组,表示从点i到点j的边的权值
void input(int n) {  //n为边数;
    memset(g, 127, sizeof(g)); // 初始化为极大值;
    for (int i = 1; i <= n; i++) {
        int x, y, z;
        scanf("%d %d %d", &x, &y, &z);
        g[x][y] = z;
        g[y][x] = z; // 无向图对称性
    }
}
无向无权图
int g[MAXN][MAXN]; // 定义g数组,表示从点i到点j的边的权值
void input(int n) { //n为边数;
     for (int i = 1; i <= n; i++) {
        int x, y;
        scanf("%d %d", &x, &y);
        g[x][y] = 1;
        g[y][x] = 1; // 无向图对称性
    }
}

2. 邻接表

思路

邻接表是一种顺序分配和链式分配相结合的存储结构;

邻接表由多个单链表组成,每个单链表的表头结点所对应的顶点存在相邻顶点,把相邻顶点依次存放于表头结点所指向的单向链表中;

实现

使用 vector[i] 模拟链表,存储以 i i i 为起点的边的终点;

若为有权图,则将 vector 内存储结构体表示边权与所达点即可;

例子

有向图样例-4

用邻接表存储

邻接表存储-7

代码
有向有权图
struct edge {
    int to, tot; // to 为终点,tot为边权
};
vector < edge > g[MAXN];
void input(int n) {
     for (int i = 1; i <= n; i++) {
        int x, y, z;
        scanf("%d %d %d", &x, &y, &z);
        g[x].push_back(edge({y, z}));
    }
}
有向无权图
vector < int > g[MAXN]; // 直接存储所达点
void input(int n) {
     for (int i = 1; i <= n; i++) {
        int x, y;
        scanf("%d %d", &x, &y);
        g[x].push_back(y);
    }
}
无向有权图
struct edge {
    int to, tot; // to 为终点,tot为边权
};
vector < edge > g[MAXN];
void input(int n) {
     for (int i = 1; i <= n; i++) {
        int x, y, z;
        scanf("%d %d %d", &x, &y, &z);
        g[x].push_back(edge({y, z}));
        g[y].push_back(edge({x, z})); // 无向图对称性
    }
}
无向无权图
vector < int > g[MAXN]; // 直接存储所达点
void input(int n) {
     for (int i = 1; i <= n; i++) {
        int x, y;
        scanf("%d %d", &x, &y);
        g[x].push_back(y);
        g[y].push_back(x); // 无向图对称性
    }
}

3. 链式前向星

思路

链式前向星是邻接表的静态建表方式,采用数组模拟链表的方式实现邻接表的功能;

实现

定义 4 个数组,含义分别如下:

  1. v e r ver ver ,存储每条边的终点,例如 v e r [ i ] ver[i] ver[i] 存储的是编号为 i i i 的边到达的终点;
  2. e d g e edge edge ,存储每条边的权值,例如 e d g e [ i ] edge[i] edge[i] 存储的是编号为 i i i 的边的权值;
  3. h e a d head head ,存储每个单链表的头节点直接相连的边的编号,例如 h e a d [ x ] head[x] head[x] 存储的是以 x x x 为头节点直接相连的有向边 ( x , y ) (x, y) (x,y) 编号;
  4. n x t nxt nxt ,存储每个边下一条边的编号,假设编号为 i i i 的边为 ( x , y ) (x, y) (x,y) ,则 n x t [ i ] nxt[i] nxt[i] 表示在邻接表中与 y y y 直接相连的边的编号;
例子

链式前向星图例-8

如上有向图,按照(1,2),(2,3),(2,5),(5,4),(5,1)存放在邻接表中,下图展示了链式前向星的存储过程。

链式前向星示例-9

关键代码
//添加(x,y)有向边,权值为z
void add (int x, int y, int z) {
    ver[++tot] = y;
    edge[tot] = z;
    nxt[tot] = head[x];
    head[x] = tot;
    //在头节点x后插入(x,y)边
}
//访问以x为头节点的单链表所有边
for(int i = head[x]; i; i = nxt[i]) {
    int y = ver[i];
    z = edge[i];
}
//找到了(x,y)边,权值为z

链式前向星的空间复杂度为 O ( n + m ) O(n+m) O(n+m) ,其中 n n n 为节点数量, m m m 为边数量;

四、图的遍历

1. 深度优先搜索遍历

思路

图的深度优先搜索 (Depth-First-Search) ,由树的先根遍历的推广;

当图中所有顶点均未被访问时,从顶点 v v v 出发,首先访问该顶点,然后依次从它的各个未被访问的邻接点出发深度优先搜索遍历图,直至图中所有和 v v v 有路径相通的顶点都被访问到;

若此时尚有其他顶点未被访问到,则另选一个未被访问的顶点作起始点,重复上述过程,直至图中所有顶点都被访问到为止;

特点

选定一个出发点后进行遍历,对于一个节点,一直遍历到与其有公共祖先的出度为 0 的节点,再继续遍历其他节点;

依此重复,直到所有与选定点相通的所有顶点都被遍历;

过程
  1. 从图中某个顶点 v i v_i vi 出发,首先访问 v i v_i vi ;

  2. 访问结点 v i v_i vi 的第一个邻接点,以这个节点的邻接点 v t v_t vt 作为一个新结点,访问 v t v_t vt 的所有邻接点,直到以 v t v_t vt 出发的所有结点都被访问;

  3. 回溯到 v i v_i vi 的下一个未被访问过的邻接点,以这个邻结点为新节点,重复步骤 2 ,直到图中所有与 v i v_i vi 相通的所有节点都被访问;

  4. 若此时图中仍有未被访问的结点,则另选图中的一个未被访问的顶点作为起始点,重复步骤 1 ,直到图中的所有节点均被访问;

例子

图的遍历图例-10

图的 DFS 序列为, v 0 → v 1 → v 3 → v 7 → v 4 → v 2 → v 5 → v 6 v_0 \rightarrow v_1 \rightarrow v_3 \rightarrow v_7 \rightarrow v_4 \rightarrow v_2 \rightarrow v_5 \rightarrow v_6 v0v1v3v7v4v2v5v6

图的遍历图例-11

图的 DFS 序列为, v 0 → v 1 → v 3 → v 2 → v 4 → v 5 → v 6 → v 7 v_0 \rightarrow v_1 \rightarrow v_3 \rightarrow v_2 \rightarrow v_4 \rightarrow v_5 \rightarrow v_6 \rightarrow v_7 v0v1v3v2v4v5v6v7

代码

以邻接表存图为例;

int vis[MAXN];
void dfs(int i) {
    vis[i] = true;
    for (int j = 0; j < g[i].size(); j++) {
        if (!vis[g[i][j]]) {
            vis[g[i][j]] = true;
            dfs(g[i][j]);
        }
    }
}
for (int i = 1; i <= n; i++) {
    if (!vis[i]) dfs(i);
}

2. 广度优先搜索遍历

思路

在广度优先遍历过程中,如果与当前顶点相连接的其他顶点尚未处理完毕,则不会处理下一个未访问顶点;

广度优先遍历则首先会发现与起始顶点 s s s 距离为 k k k 条边的所有顶点,然后才会发现与 s s s 距离为 k + 1 k+1 k+1 条边的顶点;

例子

图的遍历图例-10

图的 BFS 序列为, v 0 → v 1 → v 2 → v 3 → v 4 → v 5 → v 6 → v 8 v_0 \rightarrow v_1 \rightarrow v_2 \rightarrow v_3 \rightarrow v_4 \rightarrow v_5 \rightarrow v_6 \rightarrow v_8 v0v1v2v3v4v5v6v8

图的遍历图例-11

图的 BFS 序列为, v 0 → v 1 → v 2 → v 3 → v 4 → v 5 → v 6 → v 7 v_0 \rightarrow v_1 \rightarrow v_2 \rightarrow v_3 \rightarrow v_4 \rightarrow v_5 \rightarrow v_6 \rightarrow v_7 v0v1v2v3v4v5v6v7

代码

以邻接表存图为例;

bool vis[MAXN];
void bfs(int s) {
    queue < int > q;
    q.push(s);
    vis[s] = true;
    while (!q.empty()) {
        int x = q.front();
        q.pop();
        for (int i = 0; i < g[x].size(); i++) {
            if (!vis[g[x][i]]) {
                q.push(g[x][i]);
                vis[g[x][i]] = true;
            }
        }
    }
}
for (int i = 1; i <= n; i++) {
    if (!vis[i]) bfs(i);
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值