【数据结构】图 解析+完整代码(概念、存储、基本操作、遍历)广度优先遍历、深度优先遍历、邻接矩阵、邻接表、十字链表、邻接多重表

1.图的基本概念

  • 图的定义

    图G顶点集V边集E组成,记为 G = ( V , E ) G=(V,E) G=(V,E)

    ∣ V ∣ |V| V表示图G中顶点的个数,也是图G的阶;

    ∣ E ∣ |E| E表示图G中边的条数。

    • 注:线性表和树可以为空,但图不可以为空,即V一定是非空集,但边集E可以为空。
  • 有向图

    E是有向边的集合时,则G为有向图。

    弧是顶点的有序对,记为 < v , w > <v,w> <v,w>,其中v和w是顶点,v是弧尾,w是弧头。
    G = ( V , E ) V = { 1 , 2 , 3 } E = { < 1 , 2 > , < 2 , 1 > , < 2 , 3 > } G=(V,E)\\ V=\{1,2,3\}\\ E=\{<1,2>,<2,1>,<2,3>\} G=(V,E)V={1,2,3}E={<1,2>,<2,1>,<2,3>}
    注: < v , w > ! = < w , v > <v,w>!=<w,v> <v,w>!=<w,v>

  • 无向图

    E是无向边的集合时,则G为无向图。

    弧是顶点的无序对,记为 ( v , w ) (v,w) (v,w),其中v和w是顶点。
    G = ( V , E ) V = { 1 , 2 , 3 } E = { ( 1 , 2 ) , ( 1 , 3 ) , ( 2 , 3 ) } G=(V,E)\\ V=\{1,2,3\}\\ E=\{(1,2),(1,3),(2,3)\} G=(V,E)V={1,2,3}E={(1,2),(1,3),(2,3)}
    注: ( v , w ) = = ( w , v ) (v,w)==(w,v) (v,w)==(w,v)

  • 简单图、多重图

    • 简单图:1.不存在重复边;2.不存在顶点到自身的边。

    • 多重图:1.两个结点边数多于一条;2.允许顶点和自身关联。

  • 顶点的度、入度、出度

    • 对于无向图

      顶点v的度:是指依附于该顶点的边的条数,记为 T D ( v ) TD(v) TD(v)

    • 对于有向图

      入度:以顶点v为终点的有向边的数目,记为 I D ( v ) ID(v) ID(v)

      出度:以顶点v为起点的有向边的数目,记为 O D ( v ) OD(v) OD(v)

      顶点的度:等于顶点v入度和出度之和,即 I D ( v ) + O D ( v ) ID(v)+OD(v) ID(v)+OD(v)

      在有向图中,入度之和=出度之和=边的条数

  • 顶点-顶点的关系描述

    • 路径

      顶点 v p v_p vp到顶点 v q v_q vq之间的一条路径是指顶点序列。

    • 回路/环

      第一个和最后一个结点相同的路径。

    • 简单路径

      在路径序列中,顶点不重复出现的路径。

    • 简单回路

      除了第一个和最后一个顶点,其余顶点不重复出现。

    • 路径长度

      路径上边的数目。

    • 点到点的距离

      u到v的最短路径;如果两个点间没有路径,则记距离为无穷。

    • 连通

      无向图中从顶点v到w有路径存在,则成v和w连通。

    • 强连通

      有向图中,v到w和w到v都有路径,则称这两个顶点是强连通的。

  • 连通图、强连通图

    • 连通图

      无向图中任意两个顶点连通。

      注:对n个顶点的无向图G,

      ​ 若G是连通图,则最少有 n − 1 n-1 n1条边。

      ​ 若G是非连通图,则最多边数 C n − 1 2 C^2_{n-1} Cn12

    • 强连通图

      有向图中任意两个顶点都强连通。

      注:对n个顶点的有向图G,

      ​ 若G是强连通图,则最少有n条边(形成回路)。

  • 子图、生成子图

    • 子图:取原图边和顶点的子集构成的图。
    • 生成子图:顶点都取,边取子集构成的图。
  • 连通分量

    无向图中的极大连通子图称为连通分量。

    即除了连通外,尽可能多的包含顶点和边。

  • 强连通分量

    有向图中的极大强连通子图称为有向图的强连通分量。

    即子图必须强连通,同时保留尽可能多的边。

  • 生成树

    连通图的生成树是包含图中全部顶点的一个极小连通子图

    极小:边尽量小,但保持连通。

    • 注:若图中有n个顶点,则生成树有n-1条边。

      ​ 对生成树而言,若砍去它的一条边,则变成非连通图,若加上一条则形成回路。

  • 生成森林

    在非连通图中,连通分量的生成树构成了非连通图的生成森林。

  • 边的权、带权图

    • 边的权:边上赋予的数值。
    • 带权图/网:边上带权值的图/网。
    • 带权路径长度:一条路径上权值之和。
  • 特殊形态的图

    • 无向完全图:

      无向图中任意两个顶点都存在边。

      最多边数为 C n 2 C^2_n Cn2,即 n ( n − 1 ) / 2 n(n-1)/2 n(n1)/2

    • 有向完全图:

      有向图中任意两个顶点都存在方向相反的两条边。

      最多边数为 2 C n 2 2C^2_n 2Cn2,即 n ( n − 1 ) n(n-1) n(n1)

    • 稀疏图、稠密图

      一个边少,一个边多。

    • 不存在回路且连通的无向图。

  • 重点小结

    • 对于n个顶点的无向图G,

    所有顶点的度之和 = 2 ∣ E ∣ 所有顶点的度之和=2|E| 所有顶点的度之和=2∣E

    • 若G是连通图,则最少有 n-1 条边(树),

    若 |E|>n-1,则一定有回路。

    • 若G是非连通图,则最多可能有 C n − 1 2 C^2_{n-1} Cn12 条边

    • 无向完全图共有 C n 2 C^2_n Cn2条边。

    • 对于n个顶点的有向图G,

    • 所有顶点的 出度之和 = 入度之和 = ∣ E ∣ 出度之和=入度之和=|E| 出度之和=入度之和=E

    所有顶点的度之和 = 2 ∣ E ∣ 所有顶点的度之和=2|E| 所有顶点的度之和=2∣E

    • 若G是强连通图,则最少有 n 条边(形成回路)

    • 有向完全图共有 2 C n 2 2C^2_n 2Cn2条边

    2.图的存储

2.1 邻接矩阵

  • 不带权值的邻接矩阵法

    用二位数组存储边的信息,两顶点相连为1,不相连为0。

    #define MaxVertexNum 100 
    typedef struct{
     char Vex[MaxVertexNum]; //顶点表
     int Edge[MaxVertexNum][MaxVertexNum]; //邻接矩阵,边表
     int vexnum,arcnum; //图当前顶点数/弧数
    }MGraph;
    
  • 求度

    • 无向图

      第i个结点的=第i行(或第i列)的非零元素个数。

    • 有向图

      第i个结点的出度=第i行的非零元素个数

      第i个结点的入度=第i列的非零元素个数

      第i个结点的=第i行第i列的非零元素个数之和

  • 存储带权图的邻接矩阵法

    有权值填权值,没权值则填无穷或0。

    在这里插入图片描述

    #define MaxVertexNum 100 
    #define INFINITY //最大的int值
    typedef char VertexType; //顶点的数据类型
    typedef int EdgeType; //带权图中边上权值的数据类型    
    typedef struct{
     VertexType Vex[MaxVertexNum]; //顶点表
     EdgeType Edge[MaxVertexNum][MaxVertexNum]; //邻接矩阵,边表
     int vexnum,arcnum; //图当前顶点数/弧数
    }MGraph;
    
  • 邻接矩阵法的性能分析

    • 空间复杂度: O ( ∣ V ∣ 2 ) O(|V|^2) O(V2) ——只和顶点数相关,和实际的边数无关。
    • 适用于存储稠密图。
  • 邻接矩阵的性质

    设图G的邻接矩阵为A(矩阵元素0/1),则 A n A^n An的元素 A n [ i ] [ j ] A^n[i][j] An[i][j]等于由顶点i到j的长度为n的路径的数目。

2.2 邻接表

  • 定义——顺序+链式存储

    //顶点
    typedef struct VNode{
     VertexType data; //顶点信息
     ArcNode *first; //第一条边
    }VNode,AdjList[MaxVertexNum];
    //用邻接表存储图
    typedef struct{
     AdjList vertices;
     int vexnum,arcnum;
    }ALGraph;
    //边/弧
    typedef struct ArcNode{
     int adjvex; //边指向哪个结点
     struct ArcNode *next; //指向下一条弧的指针
     //InfoType info //边权值
    }ArcNode;
    
  • 性能分析

    • 对无向图,边结点的数量是 2 ∣ E ∣ 2|E| 2∣E,整体空间复杂度为 O ( ∣ V ∣ + 2 ∣ E ∣ ) O(|V|+2|E|) O(V+2∣E) —— 每个结点存储一个空间,每条边在其连接的两个结点上分别存储一次,即每条边存储两次。

    • 对有向图,边结点数量是 ∣ E ∣ |E| E,整体空间复杂度为 O ( ∣ V ∣ + ∣ E ∣ ) O(|V|+|E|) O(V+E)

  • 注:邻接矩阵表示法是唯一的,但图的邻接表表示法不唯一(∵孩子的链接顺序不唯一)

  • 邻接表和邻接矩阵的对比

2.3 十字链表

  • 只存储有向图

  • 空间复杂度: O ( ∣ V ∣ + ∣ E ∣ ) O(|V|+|E|) O(V+E)

  • 如何找到指定顶点的所有出边?——顺着绿色路线

    如何找到指定顶点的所有入边?——顺着橙色路线

  • 好处:

    解决了邻接矩阵空间复杂度太高的问题,也解决邻接表找入边不方便必须遍历的问题。

2.4 邻接多重表

  • 只存储无向图

    可方便删除结点和边

  • 空间复杂度: O ( ∣ V ∣ + ∣ E ∣ ) O(|V|+|E|) O(V+E)

  • 好处:

    解决了邻接矩阵空间复杂度太高的问题,也解决邻接表删除边和结点不方便的问题。

2.5 四种存储方式的对比

邻接矩阵邻接表十字链表邻接多重表
空间复杂度$O(V^2)$无向图$O(
适用于稠密图稀疏图只存有向图只存无向图
表示方法唯一不唯一不唯一不唯一
计算度/出度/入度必须遍历对应行或列计算有向图的度和入度不方便,其余方便。
找相邻的边必须遍历对应行和列找有向图入边不方便,其余方便很方便很方便
删除边和顶点删除边很方便,删除顶点需要大量移动数据都不方便很方便很方便

3.图的基本操作

3.1 基本操作

函数名函数作用邻接矩阵邻接表
Adjacent(G,x,y)判断图G是否存在边<x,y>或(x,y) O ( 1 ) O(1) O(1) 很方便,只需判断G[x][y]是否为0。 O ( 1 ) O(1) O(1)~ O ( V ) O(V) O(V) 要遍历所有边。
Neighbors(G,x)列出图G中与结点x相邻的边$O(V
InsertVertex(G,x)在图G中插入顶点x O ( 1 ) O(1) O(1) 在矩阵末尾插入信息 O ( 1 ) O(1) O(1) 在表末尾插入信息
DeleteVertex(G,x)从图G中删除顶点x$O(V
AddEdge(G,x,y)若无向边(x,y)或有向边<x,y>不存在,则向图G中添加该边 O ( 1 ) O(1) O(1) 直接在矩阵中写1 O ( 1 ) O(1) O(1) 用头插法把新边的信息插入到链表中
RemoveEdge(G,x,y)若(x,y)或<x,y>存在,则删除该边 O ( 1 ) O(1) O(1) 直接在矩阵中写0 O ( 1 ) O(1) O(1)~$O(
FirstNeighbor(G,x)求图G中顶点x的第一个邻接点。有则返回顶点号,无则返回-1。 O ( 1 ) O(1) O(1)~$O(V
NextNeighbor(G,x,y)假设图G中顶点y是顶点x的一个邻接点,返回除y之外顶点x的下一个邻接点顶点号,如果没有则返回-1 O ( 1 ) O(1) O(1)~$O(V
Get_edge_value(G,x,y)获取权值 O ( 1 ) O(1) O(1) O ( 1 ) O(1) O(1)~$O(
Set_edge_value(G,x,y)设置权值 O ( 1 ) O(1) O(1) O ( 1 ) O(1) O(1)~$O(

3.2 邻接表代码

#include <stdio.h>
#include <stdlib.h>

#define MaxVertexNum 100 // 最大顶点数定义为100
#define INF 9999 // 代表无穷大的定义为9999

// 边结点的定义
typedef struct ArcNode {
    int adjvex; // 边指向哪个结点
    int weight; // 边权值
    struct ArcNode *next; // 指向下一条弧的指针
} ArcNode;

// 顶点结点的定义
typedef struct VNode {
    int data; // 顶点信息
    ArcNode *first; // 第一条边
} VNode, AdjList[MaxVertexNum]; // 邻接表的定义

// 图的定义
typedef struct {
    AdjList vertices; // 顶点数组
    int vexnum, arcnum; // 顶点数和边数
} ALGraph;

// 初始化邻接表图
void InitALGraph(ALGraph *G, int vexnum, int arcnum) {
    G->vexnum = vexnum; // 初始化顶点数
    G->arcnum = arcnum; // 初始化边数
    for (int i = 0; i < vexnum; i++) { // 遍历顶点数组
        G->vertices[i].data = i; // 顶点信息为i
        G->vertices[i].first = NULL; // 第一条边为空
    }
}

// 添加有向图的边
void AddEdgeDirectedALGraph(ALGraph *G, int v1, int v2, int weight) {
    ArcNode *arcNode = (ArcNode *)malloc(sizeof(ArcNode)); // 分配边结点内存
    arcNode->adjvex = v2; // 边指向v2
    arcNode->weight = weight; // 边权值为weight
    arcNode->next = G->vertices[v1].first; // 将边插入到顶点v1的边链表中
    G->vertices[v1].first = arcNode;
}

// 添加无向图的边
void AddEdgeUndirectedALGraph(ALGraph *G, int v1, int v2, int weight) {
    // 添加v1到v2的边
    ArcNode *arcNode1 = (ArcNode *)malloc(sizeof(ArcNode)); // 分配边结点内存
    arcNode1->adjvex = v2; // 边指向v2
    arcNode1->weight = weight; // 边权值为weight
    arcNode1->next = G->vertices[v1].first; // 将边插入到顶点v1的边链表中
    G->vertices[v1].first = arcNode1;
    // 添加v2到v1的边
    ArcNode *arcNode2 = (ArcNode *)malloc(sizeof(ArcNode)); // 分配边结点内存
    arcNode2->adjvex = v1; // 边指向v1
    arcNode2->weight = weight; // 边权值为weight
    arcNode2->next = G->vertices[v2].first; // 将边插入到顶点v2的边链表中
    G->vertices[v2].first = arcNode2;
}

// 打印邻接表图
void PrintALGraph(ALGraph G) {
    for (int i = 0; i < G.vexnum; i++) { // 遍历顶点数组
        printf("%d -> ", G.vertices[i].data); // 打印顶点信息
        ArcNode *p = G.vertices[i].first; // 获取顶点i的第一条边
        while (p != NULL) { // 遍历顶点i的边链表
            printf("(%d, %d) ", p->adjvex, p->weight); // 打印边的信息
            p = p->next; // 指向下一条边
        }
        printf("\n");
    }
}

// 判断两个顶点之间是否有边相连
int Adjacent(ALGraph G, int x, int y) {
    ArcNode *p = G.vertices[x].first; // 获取顶点x的第一条边
    while (p != NULL) { // 遍历顶点x的边链表
        if (p->adjvex == y) // 如果边的另一端是y
            return 1; // 返回1,表示存在边<x, y>
        p = p->next; // 指向下一条边
    }
    return 0; // 遍历完所有边仍未找到,返回0,表示不存在边<x, y>
}

// 打印顶点x的邻居顶点
void Neighbors(ALGraph G, int x) {
    ArcNode *p = G.vertices[x].first; // 获取顶点x的第一条边
    printf("Neighbors of vertex %d: ", x); // 打印提示信息
    while (p != NULL) { // 遍历顶点x的边链表
        printf("%d ", p->adjvex); // 打印邻居顶点
        p = p->next; // 指向下一条边
    }
    printf("\n");
}

// 插入顶点
void InsertVertex(ALGraph *G, int x) {
    G->vertices[G->vexnum].data = x; // 在最后一个位置插入顶点x的信息
    G->vertices[G->vexnum].first = NULL; // 边链表为空
    G->vexnum++; // 顶点数加一
}

// 删除顶点
void DeleteVertex(ALGraph *G, int x) {
    for (int i = 0; i < G->vexnum; i++) { // 遍历顶点数组
        ArcNode *p = G->vertices[i].first; // 获取顶点i的第一条边
        ArcNode *pre = NULL; // 初始化前一个边结点为NULL
        while (p != NULL) { // 遍历顶点i的边链表
            if (p->adjvex == x) { // 如果边的另一端是x
                if (pre == NULL) // 如果是第一条边
                    G->vertices[i].first = p->next; // 将顶点i的第一条边指向下一条边
                else
                    pre->next = p->next; // 将前一条边的next指针指向下一条边
                free(p); // 释放删除的边结点的内存
                break; // 结束循环
            }
            pre = p; // 更新前一个边结点
            p = p->next; // 指向下一条边
        }
    }
    // 删除顶点x
    for (int i = x; i < G->vexnum - 1; i++) // 从顶点x开始,将后面的顶点信息依次往前移动
        G->vertices[i] = G->vertices[i + 1]; // 将后一个顶点信息覆盖前一个顶点信息
    G->vexnum--; // 顶点数减一
}

// 添加边
void AddEdge(ALGraph *G, int x, int y, int weight) {
    if (!Adjacent(*G, x, y)) { // 如果两个顶点之间不存在边相连
        AddEdgeUndirectedALGraph(G, x, y, weight); // 添加边<x, y>
    }
}

// 删除边
void RemoveEdge(ALGraph *G, int x, int y) {
    if (Adjacent(*G, x, y)) { // 如果两个顶点之间存在边相连
        ArcNode *p = G->vertices[x].first; // 获取顶点x的第一条边
        ArcNode *pre = NULL; // 初始化前一个边结点为NULL
        while (p != NULL) { // 遍历顶点x的边链表
            if (p->adjvex == y) { // 如果边的另一端是y
                if (pre == NULL) // 如果是第一条边
                    G->vertices[x].first = p->next; // 将顶点x的第一条边指向下一条边
                else
                    pre->next = p->next; // 将前一条边的next指针指向下一条边
                free(p); // 释放删除的边结点的内存
                break; // 结束循环
            }
            pre = p; // 更新前一个边结点
            p = p->next; // 指向下一条边
        }
        // 如果是无向图,还需要删除另一个方向的边
        if (G->vertices[y].first != NULL) { // 如果顶点y有边相连
            p = G->vertices[y].first; // 获取顶点y的第一条边
            pre = NULL; // 初始化前一个边结点为NULL
            while (p != NULL) { // 遍历顶点y的边链表
                if (p->adjvex == x) { // 如果边的另一端是x
                    if (pre == NULL) // 如果是第一条边
                        G->vertices[y].first = p->next; // 将顶点y的第一条边指向下一条边
                    else
                        pre->next = p->next; // 将前一条边的next指针指向下一条边
                    free(p); // 释放删除的边结点的内存
                    break; // 结束循环
                }
                pre = p; // 更新前一个边结点
                p = p->next; // 指向下一条边
            }
        }
    }
}

// 获取顶点x的第一个邻居顶点
int FirstNeighbor(ALGraph G, int x) {
    if (G.vertices[x].first != NULL) // 如果顶点x有边相连
        return G.vertices[x].first->adjvex; // 返回第一个邻居顶点的信息
    else
        return -1; // 否则返回-1,表示没有邻居顶点
}

// 获取顶点x的邻居顶点y的下一个邻居顶点
int NextNeighbor(ALGraph G, int x, int y) {
    ArcNode *p = G.vertices[x].first; // 获取顶点x的第一条边
    while (p != NULL && p->adjvex != y) // 遍历顶点x的边链表,直到找到邻居顶点y
        p = p->next; // 指向下一条边
    if (p != NULL && p->next != NULL) // 如果邻居顶点y后面还有邻居顶点
        return p->next->adjvex; // 返回邻居顶点y的下一个邻居顶点的信息
    else
        return -1; // 否则返回-1,表示没有下一个邻居顶点
}

// 获取边<x, y>的权值
int Get_edge_value(ALGraph G, int x, int y) {
    ArcNode *p = G.vertices[x].first; // 获取顶点x的第一条边
    while (p != NULL) { // 遍历顶点x的边链表
        if (p->adjvex == y) // 如果边的另一端是y
            return p->weight; // 返回边的权值
        p = p->next; // 指向下一条边
    }
    return INF; // 遍历完所有边仍未找到,返回无穷大,表示不存在边<x, y>
}

// 设置边<x, y>的权值为v
void Set_edge_value(ALGraph *G, int x, int y, int v) {
    ArcNode *p = G->vertices[x].first; // 获取顶点x的第一条边
    while (p != NULL) { // 遍历顶点x的边链表
        if (p->adjvex == y) { // 如果边的另一端是y
            p->weight = v; // 设置边的权值为v
            break; // 结束循环
        }
        p = p->next; // 指向下一条边
    }
}

int main() {
    // 创建有向图的邻接表表示
    ALGraph directedALGraph; // 定义有向图
    InitALGraph(&directedALGraph, 5, 6); // 初始化有向图
    AddEdgeDirectedALGraph(&directedALGraph, 0, 1, 1); // 添加边
    AddEdgeDirectedALGraph(&directedALGraph, 0, 2, 2); // 添加边
    AddEdgeDirectedALGraph(&directedALGraph, 1, 2, 3); // 添加边
    AddEdgeDirectedALGraph(&directedALGraph, 2, 3, 4); // 添加边
    AddEdgeDirectedALGraph(&directedALGraph, 3, 4, 5); // 添加边
    AddEdgeDirectedALGraph(&directedALGraph, 4, 0, 6); // 添加边

    // 创建无向图的邻接表表示
    ALGraph undirectedALGraph; // 定义无向图
    InitALGraph(&undirectedALGraph, 5, 6); // 初始化无向图
    AddEdgeUndirectedALGraph(&undirectedALGraph, 0, 1, 1); // 添加边
    AddEdgeUndirectedALGraph(&undirectedALGraph, 0, 2, 2); // 添加边
    AddEdgeUndirectedALGraph(&undirectedALGraph, 1, 2, 3); // 添加边
    AddEdgeUndirectedALGraph(&undirectedALGraph, 2, 3, 4); // 添加边
    AddEdgeUndirectedALGraph(&undirectedALGraph, 3, 4, 5); // 添加边
    AddEdgeUndirectedALGraph(&undirectedALGraph, 4, 0, 6); // 添加边

    printf("\nDirected Graph (Adjacency List):\n");
    PrintALGraph(directedALGraph); // 打印有向图的邻接表表示

    printf("\nUndirected Graph (Adjacency List):\n");
    PrintALGraph(undirectedALGraph); // 打印无向图的邻接表表示

    // 测试基本操作
    printf("\nTest Basic Operations:\n");
    printf("Adjacent(directedALGraph, 0, 1): %d\n", Adjacent(directedALGraph, 0, 1)); // 测试两个顶点之间是否有边相连
    printf("Adjacent(directedALGraph, 0, 3): %d\n", Adjacent(directedALGraph, 0, 3)); // 测试两个顶点之间是否有边相连
    printf("Adjacent(undirectedALGraph, 0, 1): %d\n", Adjacent(undirectedALGraph, 0, 1)); // 测试两个顶点之间是否有边相连
    printf("Neighbors(directedALGraph, 0): ");
    Neighbors(directedALGraph, 0); // 打印顶点0的邻居顶点
    printf("Neighbors(undirectedALGraph, 0): ");
    Neighbors(undirectedALGraph, 0); // 打印顶点0的邻居顶点
    InsertVertex(&directedALGraph, 5); // 插入顶点5
    printf("After InsertVertex(directedALGraph, 5):\n");
    PrintALGraph(directedALGraph); // 打印有向图的邻接表表示
    DeleteVertex(&directedALGraph, 5); // 删除顶点5
    printf("After DeleteVertex(directedALGraph, 5):\n");
    PrintALGraph(directedALGraph); // 打印有向图的邻接表表示
    AddEdge(&directedALGraph, 1, 4, 7); // 添加边
    printf("After AddEdge(directedALGraph, 1, 4, 7):\n");
    PrintALGraph(directedALGraph); // 打印有向图的邻接表表示
    RemoveEdge(&directedALGraph, 1, 4); // 删除边
    printf("After RemoveEdge(directedALGraph, 1, 4):\n");
    PrintALGraph(directedALGraph); // 打印有向图的邻接表表示
    printf("FirstNeighbor(undirectedALGraph, 2): %d\n", FirstNeighbor(undirectedALGraph, 2)); // 获取顶点2的第一个邻居顶点
    printf("NextNeighbor(undirectedALGraph, 2, 3): %d\n", NextNeighbor(undirectedALGraph, 2, 3)); // 获取顶点2的邻居顶点3的下一个邻居顶点
    printf("Get_edge_value(undirectedALGraph, 2, 3): %d\n", Get_edge_value(undirectedALGraph, 2, 3)); // 获取边
    Set_edge_value(&undirectedALGraph, 2, 3, 10);
    printf("After Set_edge_value(undirectedALGraph, 2, 3, 10):\n");
    PrintALGraph(undirectedALGraph);

    return 0;
}

在这里插入图片描述
在这里插入图片描述

3.3 邻接矩阵代码

#include <stdio.h>
#include <stdlib.h>

#define MaxVertexNum 100
#define INFINITY 9999 // 代表无穷大

// 邻接矩阵存储有向图和无向图
typedef struct {
    char Vex[MaxVertexNum]; // 顶点表
    int Edge[MaxVertexNum][MaxVertexNum]; // 邻接矩阵,边表
    int vexnum, arcnum; // 图当前顶点数/弧数
    int weight[MaxVertexNum][MaxVertexNum]; // 权值
} MGraph;

// 初始化邻接矩阵图
void InitMGraph(MGraph *G, int vexnum, int arcnum) {
    G->vexnum = vexnum;
    G->arcnum = arcnum;
    for (int i = 0; i < vexnum; i++) {
        for (int j = 0; j < vexnum; j++) {
            G->Edge[i][j] = 0; // 初始化边为0,表示无连接
            G->weight[i][j] = INFINITY; // 初始化权值为无穷大
        }
    }
}

// 添加有向图邻接矩阵的边
void AddEdgeDirectedMGraph(MGraph *G, int v1, int v2, int weight) {
    G->Edge[v1][v2] = 1; // 有连接的边置为1
    G->weight[v1][v2] = weight; // 设置权值
}

// 添加无向图邻接矩阵的边
void AddEdgeUndirectedMGraph(MGraph *G, int v1, int v2, int weight) {
    G->Edge[v1][v2] = 1; // 有连接的边置为1
    G->Edge[v2][v1] = 1; // 对称位置也置为1
    G->weight[v1][v2] = weight; // 设置权值
    G->weight[v2][v1] = weight; // 对称位置设置权值
}

// 打印邻接矩阵图
void PrintMGraph(MGraph G) {
    for (int i = 0; i < G.vexnum; i++) {
        for (int j = 0; j < G.vexnum; j++) {
            printf("%d ", G.Edge[i][j]);
        }
        printf("\n");
    }
}

// 判断图G是否存在边<x, y>或(x, y),并返回权值
int Adjacent(MGraph G, int x, int y) {
    if (G.Edge[x][y] == 1 || G.Edge[y][x] == 1) {
        return G.weight[x][y]; // 返回边的权值
    } else {
        return INFINITY; // 不存在边<x, y>或(x, y),返回无穷大
    }
}

// 列出图G中与结点x邻接的边
void Neighbors(MGraph G, int x) {
    printf("Neighbors of vertex %d: ", x);
    for (int i = 0; i < G.vexnum; i++) {
        if (G.Edge[x][i] == 1) {
            printf("%d ", i);
        }
    }
    printf("\n");
}

// 在图G中插入顶点x
void InsertVertex(MGraph *G, int x) {
    if (G->vexnum < MaxVertexNum) {
        G->Vex[G->vexnum] = x;
        G->vexnum++;
    } else {
        printf("Exceeding maximum vertex number!\n");
    }
}

// 从图G中删除顶点x
void DeleteVertex(MGraph *G, int x) {
    if (x >= G->vexnum) {
        printf("Vertex does not exist!\n");
        return;
    }

    // 删除顶点x的边
    for (int i = 0; i < G->vexnum; i++) {
        G->Edge[i][x] = 0;
        G->Edge[x][i] = 0;
        G->weight[i][x] = INFINITY; // 删除边同时将权值设置为无穷大
        G->weight[x][i] = INFINITY;
    }

    // 移动顶点表,覆盖被删除的顶点
    for (int i = x; i < G->vexnum - 1; i++) {
        G->Vex[i] = G->Vex[i + 1];
    }

    G->vexnum--;
}

// 若无向边(x, y)或有向边<x, y>不存在,则向图G中添加该边
void AddEdge(MGraph *G, int x, int y, int weight) {
    if (G->Edge[x][y] == 0) {
        G->Edge[x][y] = 1;
        G->Edge[y][x] = 1; // 对于无向图,要同时设置对称位置
        G->weight[x][y] = weight;
        G->weight[y][x] = weight; // 对称位置设置权值
    }
}

// 若无向边(x, y)或有向边<x, y>存在,则从图G中删除该边
void RemoveEdge(MGraph *G, int x, int y) {
    if (G->Edge[x][y] == 1) {
        G->Edge[x][y] = 0;
        G->Edge[y][x] = 0; // 对于无向图,要同时设置对称位置
        G->weight[x][y] = INFINITY;
        G->weight[y][x] = INFINITY; // 对称位置设置权值为无穷大
    }
}

// 求图G中顶点x的第一个邻接点,若有则返回顶点号,若x没有邻接点或图中不存在x,则返回-1
int FirstNeighbor(MGraph G, int x) {
    for (int i = 0; i < G.vexnum; i++) {
        if (G.Edge[x][i] == 1) {
            return i;
        }
    }
    return -1;
}

// 返回除y之外顶点x的下一个邻接点的顶点号,若y是x的最后一个邻接点,则返回-1
int NextNeighbor(MGraph G, int x, int y) {
    for (int i = y + 1; i < G.vexnum; i++) {
        if (G.Edge[x][i] == 1) {
            return i;
        }
    }
    return -1;
}

// 获取图G中边(x, y)或<x, y>对应的权值
int Get_edge_value(MGraph G, int x, int y) {
    return G.weight[x][y];
}

// 设置图G中边(x, y)或<x, y>对应的权值为v
void Set_edge_value(MGraph *G, int x, int y, int v) {
    G->weight[x][y] = v;
    G->weight[y][x] = v; // 对称位置设置权值
}

int main() {
    // 创建有向图的邻接矩阵表示
    MGraph directedMGraph;
    InitMGraph(&directedMGraph, 5, 6);
    AddEdgeDirectedMGraph(&directedMGraph, 0, 1, 2);
    AddEdgeDirectedMGraph(&directedMGraph, 0, 2, 3);
    AddEdgeDirectedMGraph(&directedMGraph, 1, 2, 4);
    AddEdgeDirectedMGraph(&directedMGraph, 2, 3, 5);
    AddEdgeDirectedMGraph(&directedMGraph, 3, 4, 6);
    AddEdgeDirectedMGraph(&directedMGraph, 4, 0, 7);

    // 创建无向图的邻接矩阵表示
    MGraph undirectedMGraph;
    InitMGraph(&undirectedMGraph, 5, 6);
    AddEdgeUndirectedMGraph(&undirectedMGraph, 0, 1, 2);
    AddEdgeUndirectedMGraph(&undirectedMGraph, 0, 2, 3);
    AddEdgeUndirectedMGraph(&undirectedMGraph, 1, 2, 4);
    AddEdgeUndirectedMGraph(&undirectedMGraph, 2, 3, 5);
    AddEdgeUndirectedMGraph(&undirectedMGraph, 3, 4, 6);
    AddEdgeUndirectedMGraph(&undirectedMGraph, 4, 0, 7);

    // 打印结果
    printf("Directed Graph (Adjacency Matrix):\n");
    PrintMGraph(directedMGraph);

    printf("\nUndirected Graph (Adjacency Matrix):\n");
    PrintMGraph(undirectedMGraph);

    // 测试基本操作
    printf("\nTest Basic Operations:\n");
    printf("Adjacent(directedMGraph, 0, 1): %d\n", Adjacent(directedMGraph, 0, 1));
    printf("Adjacent(directedMGraph, 0, 3): %d\n", Adjacent(directedMGraph, 0, 3));
    printf("Neighbors(directedMGraph, 0): ");
    Neighbors(directedMGraph, 0);
    printf("Neighbors(undirectedMGraph, 0): ");
    Neighbors(undirectedMGraph, 0);
    InsertVertex(&directedMGraph, 5);
    printf("After InsertVertex(directedMGraph, 5):\n");
    PrintMGraph(directedMGraph);
    DeleteVertex(&directedMGraph, 5);
    printf("After DeleteVertex(directedMGraph, 5):\n");
    PrintMGraph(directedMGraph);
    AddEdge(&directedMGraph, 1, 4, 8);
    printf("After AddEdge(directedMGraph, 1, 4):\n");
    PrintMGraph(directedMGraph);
    RemoveEdge(&directedMGraph, 1, 4);
    printf("After RemoveEdge(directedMGraph, 1, 4):\n");
    PrintMGraph(directedMGraph);
    printf("FirstNeighbor(undirectedMGraph, 2): %d\n", FirstNeighbor(undirectedMGraph, 2));
    printf("NextNeighbor(undirectedMGraph, 2, 3): %d\n", NextNeighbor(undirectedMGraph, 2, 3));
    printf("Get_edge_value(undirectedMGraph, 2, 3): %d\n", Get_edge_value(undirectedMGraph, 2, 3));
    Set_edge_value(&undirectedMGraph, 2, 3, 10);
    printf("After Set_edge_value(undirectedMGraph, 2, 3, 10):\n");
    PrintMGraph(undirectedMGraph);

    return 0;
}

在这里插入图片描述
在这里插入图片描述

4.图的遍历

4.1 广度优先遍历

  • 树的广度优先遍历==层序遍历

  • 图的广度优先遍历(BFS)

    • 定义:先访问一个顶点,再访问与该结点相邻的结点,再访问这些相邻结点的相邻结点,依次循环直到遍历完。

    • 要点:

      1.找到与一个顶点相邻的所有顶点;

      2.标记哪些顶点被访问过;

      3.需要一个辅助队列。

    • 注:

      同一个图的邻接矩阵表示方法唯一,因此广度优先遍历序列唯一。

      同一个图邻接表表示方式不唯一,因此广度优先遍历序列不唯一。

bool visited[MAX_VERTEX_NUM];//访问标记数组

//对图G进行广度优先遍历
void BFSTraverse(Graph G){
    int i;
    for(i=0;i<G.vexnum;++i)
        visited[i]=FALSE; //访问标记数组初始化
    InitQueue(Q); //初始化辅助队列
    //从0号结点开始遍历
    for(i=0;i<G.vexnum;++i){
        if(!visited[i]) //对每个连通分量调用一次BFS
            BFS(G,i); //vi未被访问,则从vi开始BFS
    }
}

//广度优先遍历
void BFS(Graph G,int v){ //从顶点v出发,广度优先遍历图G
    visit(v); //访问初始顶点v
    visited[v]=TRUE; //对v做已访问标记
    Enqueue(Q,v); //顶点v入队列Q
    while(!isEmpty(Q)){
        DeQueue(Q,v); //顶点v出队列
        for(w=FirstNeighbor(G,v);w>=0;w=NextNeighbor(G,v,w)){
            //检测v所有邻接点
            if(!visited[w]){ //w为v的尚未访问的邻接顶点
                visited(w); //访问w
                visited[w]=TRUE; //标记w已被访问
                EnQueue(Q,w); //顶点w入队
            }
        }
    }
}
  • 复杂度分析

    • 邻接矩阵存储的图:

      访问 ∣ V ∣ |V| V个顶点需要 Q ( ∣ V ∣ ) Q(|V|) Q(V)的时间

      查找每个顶点的邻接点都需要 O ( ∣ V ∣ ) O(|V|) O(V)的时间,而总共有 ∣ V ∣ |V| V个顶点。

      时间复杂度: O ( ∣ V 2 ∣ ) O(|V^2|) O(V2)

    • 邻接表存储的图:

      访问 ∣ V ∣ |V| V个顶点需要 O ( ∣ V ∣ ) O(|V|) O(V)的时间

      查找各个顶点的邻接点共需要 O ( ∣ E ∣ ) O(|E|) O(E)的时间

      时间复杂度: O ( ∣ V ∣ + ∣ E ∣ ) O(|V|+|E|) O(V+E)

  • 广度优先生成树/森林

    下图中标红的标明是第一次遍历到该结点的路径,该路径可生成树/森林:

    在这里插入图片描述

    ∵ 邻接表存储的图表示方式不唯一,遍历序列、生成树也不唯一。

4.2 深度优先遍历

  • 树的深度优先遍历 相当于树的先根遍历

  • 图的深度优先遍历

    1.访问一个结点a;

    2.访问与该结点a相连的结点b;

    3.继续访问相连结点b的相连结点,依次递归直到没有相连结点;

    4.再取未被访问的结点开始重复1,2,3步的访问。

bool visited[MAX_VERTEX_NUM]; //访问标记数组
void DFSTraverse(Graph G){
    for(v=0;v<G.vexnum;++v){
        visited[v]=false;
    }
    for(v=0;v<G.vexnum;++v){ //从第0个结点开始遍历
        if(!visited[v])
            DFS(G,v);
    }
}

void DFS(Graph G,int v){
    visit(v); //访问顶点v
    visited[v]=TRUE; //设已访问标记
    for(w=FirstNeighbor(G,v);w>=0;w=NextNeighbor(G,v,w)){
        if(!visited[w]){
            DFS(G,w);
        }
    }
}
  • 空间复杂度:

    来自函数调用栈,最坏情况,递归深度为 O ( ∣ V ∣ ) O(|V|) O(V),最好情况 O ( 1 ) O(1) O(1)

  • 时间复杂度=访问各结点所需时间+探索各条边所需的时间

    • 邻接矩阵存储的图

      访问 ∣ V ∣ |V| V个顶点需要 O ( ∣ V ∣ ) O(|V|) O(V)的时间

      查找每个顶点的邻接点都需要 O ( ∣ V ∣ ) O(|V|) O(V)的时间,而总共有 ∣ V ∣ |V| V个顶点

      时间复杂度= O ( ∣ V ∣ 2 ) O(|V|^2) O(V2)

    • 邻接表存储的图

      访问 ∣ V ∣ |V| V个顶点需要 O ( ∣ V ∣ ) O(|V|) O(V)的时间

      查找各个顶点的邻接点共需要 O ( ∣ E ∣ ) O(|E|) O(E)的时间

      时间复杂度= O ( ∣ V ∣ + ∣ E ∣ ) O(|V|+|E|) O(V+E)

  • 深度优先生成树/森林

    由深度优先遍历确定的树。

    邻接表存储的图表示方式不唯一,深度优先序列和深度优先生成树/森林也不唯一。

    和广度优先生成树类似,第一次遍历到的边标红,并形成树/森林:

    在这里插入图片描述

4.3 图的遍历与图的连通性

  • 对无向图进行BFS/DFS遍历

    调用BFS/DFS函数的次数=连通分量数

    对于连通图,只调用1次BFS/DFS

  • 对有向图进行BFS/DFS遍历

    调用BFS/DFS函数的次数要具体分析。

    若起始顶点到其他各顶点都有路径,则只需调用1次BFS/DFS函数。

    对强连通图,从任一顶点出发都只需调用一次DFS/BFS函数。

*完整代码 邻接矩阵

#include <stdio.h>
#include <stdlib.h>

#define MaxVertexNum 100
#define INFINITY 9999 // 代表无穷大

// 邻接矩阵存储有向图和无向图
typedef struct {
    char Vex[MaxVertexNum]; // 顶点表
    int Edge[MaxVertexNum][MaxVertexNum]; // 邻接矩阵,边表
    int vexnum, arcnum; // 图当前顶点数/弧数
    int weight[MaxVertexNum][MaxVertexNum]; // 权值
} MGraph;

// 初始化邻接矩阵图
void InitMGraph(MGraph *G, int vexnum, int arcnum) {
    G->vexnum = vexnum;
    G->arcnum = arcnum;
    for (int i = 0; i < vexnum; i++) {
        for (int j = 0; j < vexnum; j++) {
            G->Edge[i][j] = 0; // 初始化边为0,表示无连接
            G->weight[i][j] = INFINITY; // 初始化权值为无穷大
        }
    }
}

// 添加有向图邻接矩阵的边
void AddEdgeDirectedMGraph(MGraph *G, int v1, int v2, int weight) {
    G->Edge[v1][v2] = 1; // 有连接的边置为1
    G->weight[v1][v2] = weight; // 设置权值
}

// 添加无向图邻接矩阵的边
void AddEdgeUndirectedMGraph(MGraph *G, int v1, int v2, int weight) {
    G->Edge[v1][v2] = 1; // 有连接的边置为1
    G->Edge[v2][v1] = 1; // 对称位置也置为1
    G->weight[v1][v2] = weight; // 设置权值
    G->weight[v2][v1] = weight; // 对称位置设置权值
}

// 打印邻接矩阵图
void PrintMGraph(MGraph G) {
    for (int i = 0; i < G.vexnum; i++) {
        for (int j = 0; j < G.vexnum; j++) {
            printf("%d ", G.Edge[i][j]);
        }
        printf("\n");
    }
}

// 判断图G是否存在边<x, y>或(x, y),并返回权值
int Adjacent(MGraph G, int x, int y) {
    if (G.Edge[x][y] == 1 || G.Edge[y][x] == 1) {
        return G.weight[x][y]; // 返回边的权值
    } else {
        return INFINITY; // 不存在边<x, y>或(x, y),返回无穷大
    }
}

// 列出图G中与结点x邻接的边
void Neighbors(MGraph G, int x) {
    printf("Neighbors of vertex %d: ", x);
    for (int i = 0; i < G.vexnum; i++) {
        if (G.Edge[x][i] == 1) {
            printf("%d ", i);
        }
    }
    printf("\n");
}

// 在图G中插入顶点x
void InsertVertex(MGraph *G, int x) {
    if (G->vexnum < MaxVertexNum) {
        G->Vex[G->vexnum] = x;
        G->vexnum++;
    } else {
        printf("Exceeding maximum vertex number!\n");
    }
}

// 从图G中删除顶点x
void DeleteVertex(MGraph *G, int x) {
    if (x >= G->vexnum) {
        printf("Vertex does not exist!\n");
        return;
    }

    // 删除顶点x的边
    for (int i = 0; i < G->vexnum; i++) {
        G->Edge[i][x] = 0;
        G->Edge[x][i] = 0;
        G->weight[i][x] = INFINITY; // 删除边同时将权值设置为无穷大
        G->weight[x][i] = INFINITY;
    }

    // 移动顶点表,覆盖被删除的顶点
    for (int i = x; i < G->vexnum - 1; i++) {
        G->Vex[i] = G->Vex[i + 1];
    }

    G->vexnum--;
}

// 若无向边(x, y)或有向边<x, y>不存在,则向图G中添加该边
void AddEdge(MGraph *G, int x, int y, int weight) {
    if (G->Edge[x][y] == 0) {
        G->Edge[x][y] = 1;
        G->Edge[y][x] = 1; // 对于无向图,要同时设置对称位置
        G->weight[x][y] = weight;
        G->weight[y][x] = weight; // 对称位置设置权值
    }
}

// 若无向边(x, y)或有向边<x, y>存在,则从图G中删除该边
void RemoveEdge(MGraph *G, int x, int y) {
    if (G->Edge[x][y] == 1) {
        G->Edge[x][y] = 0;
        G->Edge[y][x] = 0; // 对于无向图,要同时设置对称位置
        G->weight[x][y] = INFINITY;
        G->weight[y][x] = INFINITY; // 对称位置设置权值为无穷大
    }
}

// 求图G中顶点x的第一个邻接点,若有则返回顶点号,若x没有邻接点或图中不存在x,则返回-1
int FirstNeighbor(MGraph G, int x) {
    for (int i = 0; i < G.vexnum; i++) {
        if (G.Edge[x][i] == 1) {
            return i;
        }
    }
    return -1;
}

// 返回除y之外顶点x的下一个邻接点的顶点号,若y是x的最后一个邻接点,则返回-1
int NextNeighbor(MGraph G, int x, int y) {
    for (int i = y + 1; i < G.vexnum; i++) {
        if (G.Edge[x][i] == 1) {
            return i;
        }
    }
    return -1;
}

// 获取图G中边(x, y)或<x, y>对应的权值
int Get_edge_value(MGraph G, int x, int y) {
    return G.weight[x][y];
}

// 设置图G中边(x, y)或<x, y>对应的权值为v
void Set_edge_value(MGraph *G, int x, int y, int v) {
    G->weight[x][y] = v;
    G->weight[y][x] = v; // 对称位置设置权值
}

void visit(int v){
    printf("%d ",v);
}

// 定义队列结点
typedef struct QueueNode {
    int data;
    struct QueueNode* next;
} QueueNode;

// 定义队列
typedef struct {
    QueueNode *front;
    QueueNode *rear;
} Queue;

// 初始化队列
void InitQueue(Queue *Q) {
    Q->front = Q->rear = NULL;
}

// 入队操作
void EnQueue(Queue *Q, int data) {
    QueueNode *newNode = (QueueNode *)malloc(sizeof(QueueNode));
    newNode->data = data;
    newNode->next = NULL;
    if (Q->rear == NULL) {
        Q->front = Q->rear = newNode;
    } else {
        Q->rear->next = newNode;
        Q->rear = newNode;
    }
}

// 出队操作
int DeQueue(Queue *Q) {
    if (Q->front == NULL)
        return -1; // 队列为空
    QueueNode *temp = Q->front;
    int data = temp->data;
    Q->front = Q->front->next;
    if (Q->front == NULL)
        Q->rear = NULL;
    free(temp);
    return data;
}

// 判断队列是否为空
int IsQueueEmpty(Queue Q) {
    return Q.front == NULL;
}

// 广度优先遍历
void BFS(MGraph G, int v, bool visited[], Queue *Q) { // 从顶点v出发,广度优先遍历图G
    visit(v); // 访问初始顶点v
    visited[v] = true; // 对v做已访问标记
    EnQueue(Q, v); // 顶点v入队列Q
    while (!IsQueueEmpty(*Q)) {
        int w;
        w=DeQueue(Q); // 顶点v出队列
        for (w = FirstNeighbor(G, v); w >= 0; w = NextNeighbor(G, v, w)) {
            // 检测v所有邻接点
            if (!visited[w]) { // w为v的尚未访问的邻接顶点
                visit(w); // 访问w
                visited[w] = true; // 标记w已被访问
                EnQueue(Q, w); // 顶点w入队
            }
        }
    }
}

// 广度优先遍历
// 对图G进行广度优先遍历
void BFSTraverse(MGraph G) {
    bool visited[MaxVertexNum] = {false};
    Queue Q;
    InitQueue(&Q); // 初始化辅助队列
    // 从0号结点开始遍历
    for (int i = 0; i < G.vexnum; ++i) {
        if (!visited[i]) // 对每个连通分量调用一次BFS
            BFS(G, i, visited, &Q); // vi未被访问,则从vi开始BFS
    }
}

// 深度优先遍历
void DFS(MGraph G, int v, bool visited[]) {
    visit(v); // 访问顶点v
    visited[v] = true; // 设已访问标记
    for (int w = FirstNeighbor(G, v); w >= 0; w = NextNeighbor(G, v, w)) {
        if (!visited[w]) {
            DFS(G, w, visited);
        }
    }
}

void DFSTraverse(MGraph G) {
    bool visited[MaxVertexNum] = {false};
    for (int i = 0; i < G.vexnum; ++i) {
        if (!visited[i]) {
            DFS(G, i, visited);
        }
    }
}



int main() {
    // 创建有向图的邻接矩阵表示
    MGraph directedMGraph;
    InitMGraph(&directedMGraph, 5, 6);
    AddEdgeDirectedMGraph(&directedMGraph, 0, 1, 2);
    AddEdgeDirectedMGraph(&directedMGraph, 0, 2, 3);
    AddEdgeDirectedMGraph(&directedMGraph, 1, 2, 4);
    AddEdgeDirectedMGraph(&directedMGraph, 2, 3, 5);
    AddEdgeDirectedMGraph(&directedMGraph, 3, 4, 6);
    AddEdgeDirectedMGraph(&directedMGraph, 4, 0, 7);

    // 创建无向图的邻接矩阵表示
    MGraph undirectedMGraph;
    InitMGraph(&undirectedMGraph, 5, 6);
    AddEdgeUndirectedMGraph(&undirectedMGraph, 0, 1, 2);
    AddEdgeUndirectedMGraph(&undirectedMGraph, 0, 2, 3);
    AddEdgeUndirectedMGraph(&undirectedMGraph, 1, 2, 4);
    AddEdgeUndirectedMGraph(&undirectedMGraph, 2, 3, 5);
    AddEdgeUndirectedMGraph(&undirectedMGraph, 3, 4, 6);
    AddEdgeUndirectedMGraph(&undirectedMGraph, 4, 0, 7);

    // 打印结果
    printf("Directed Graph (Adjacency Matrix):\n");
    PrintMGraph(directedMGraph);

    printf("\nUndirected Graph (Adjacency Matrix):\n");
    PrintMGraph(undirectedMGraph);

    // 测试基本操作
    printf("\nTest Basic Operations:\n");
    printf("Adjacent(directedMGraph, 0, 1): %d\n", Adjacent(directedMGraph, 0, 1));
    printf("Adjacent(directedMGraph, 0, 3): %d\n", Adjacent(directedMGraph, 0, 3));
    printf("Neighbors(directedMGraph, 0): ");
    Neighbors(directedMGraph, 0);
    printf("Neighbors(undirectedMGraph, 0): ");
    Neighbors(undirectedMGraph, 0);
    InsertVertex(&directedMGraph, 5);
    printf("After InsertVertex(directedMGraph, 5):\n");
    PrintMGraph(directedMGraph);
    DeleteVertex(&directedMGraph, 5);
    printf("After DeleteVertex(directedMGraph, 5):\n");
    PrintMGraph(directedMGraph);
    AddEdge(&directedMGraph, 1, 4, 8);
    printf("After AddEdge(directedMGraph, 1, 4):\n");
    PrintMGraph(directedMGraph);
    RemoveEdge(&directedMGraph, 1, 4);
    printf("After RemoveEdge(directedMGraph, 1, 4):\n");
    PrintMGraph(directedMGraph);
    printf("FirstNeighbor(undirectedMGraph, 2): %d\n", FirstNeighbor(undirectedMGraph, 2));
    printf("NextNeighbor(undirectedMGraph, 2, 3): %d\n", NextNeighbor(undirectedMGraph, 2, 3));
    printf("Get_edge_value(undirectedMGraph, 2, 3): %d\n", Get_edge_value(undirectedMGraph, 2, 3));
    Set_edge_value(&undirectedMGraph, 2, 3, 10);
    printf("After Set_edge_value(undirectedMGraph, 2, 3, 10):\n");
    PrintMGraph(undirectedMGraph);

    // 测试广度优先遍历和深度优先遍历
    printf("\nTest Traversal:\n");
    printf("BFS directed Graph:");
    BFSTraverse(directedMGraph); // 广度优先遍历有向图,起始节点为0
    printf("\nDFS directed Graph:");
    DFSTraverse(directedMGraph); // 深度优先遍历有向图,起始节点为0
    printf("\nBFS undirected Graph:");
    BFSTraverse(undirectedMGraph); // 广度优先遍历无向图,起始节点为0
    printf("\nDFS undirected Graph:");
    DFSTraverse(undirectedMGraph); // 深度优先遍历无向图,起始节点为0

    return 0;
}

在这里插入图片描述
在这里插入图片描述

*完整代码 邻接表

#include <stdio.h>
#include <stdlib.h>

#define MaxVertexNum 100 // 最大顶点数定义为100
#define INF 9999 // 代表无穷大的定义为9999

// 边结点的定义
typedef struct ArcNode {
    int adjvex; // 边指向哪个结点
    int weight; // 边权值
    struct ArcNode *next; // 指向下一条弧的指针
} ArcNode;

// 顶点结点的定义
typedef struct VNode {
    int data; // 顶点信息
    ArcNode *first; // 第一条边
} VNode, AdjList[MaxVertexNum]; // 邻接表的定义

// 图的定义
typedef struct {
    AdjList vertices; // 顶点数组
    int vexnum, arcnum; // 顶点数和边数
} ALGraph;

// 初始化邻接表图
void InitALGraph(ALGraph *G, int vexnum, int arcnum) {
    G->vexnum = vexnum; // 初始化顶点数
    G->arcnum = arcnum; // 初始化边数
    for (int i = 0; i < vexnum; i++) { // 遍历顶点数组
        G->vertices[i].data = i; // 顶点信息为i
        G->vertices[i].first = NULL; // 第一条边为空
    }
}

// 添加有向图的边
void AddEdgeDirectedALGraph(ALGraph *G, int v1, int v2, int weight) {
    ArcNode *arcNode = (ArcNode *)malloc(sizeof(ArcNode)); // 分配边结点内存
    arcNode->adjvex = v2; // 边指向v2
    arcNode->weight = weight; // 边权值为weight
    arcNode->next = G->vertices[v1].first; // 将边插入到顶点v1的边链表中
    G->vertices[v1].first = arcNode;
}

// 添加无向图的边
void AddEdgeUndirectedALGraph(ALGraph *G, int v1, int v2, int weight) {
    // 添加v1到v2的边
    ArcNode *arcNode1 = (ArcNode *)malloc(sizeof(ArcNode)); // 分配边结点内存
    arcNode1->adjvex = v2; // 边指向v2
    arcNode1->weight = weight; // 边权值为weight
    arcNode1->next = G->vertices[v1].first; // 将边插入到顶点v1的边链表中
    G->vertices[v1].first = arcNode1;
    // 添加v2到v1的边
    ArcNode *arcNode2 = (ArcNode *)malloc(sizeof(ArcNode)); // 分配边结点内存
    arcNode2->adjvex = v1; // 边指向v1
    arcNode2->weight = weight; // 边权值为weight
    arcNode2->next = G->vertices[v2].first; // 将边插入到顶点v2的边链表中
    G->vertices[v2].first = arcNode2;
}

// 打印邻接表图
void PrintALGraph(ALGraph G) {
    for (int i = 0; i < G.vexnum; i++) { // 遍历顶点数组
        printf("%d -> ", G.vertices[i].data); // 打印顶点信息
        ArcNode *p = G.vertices[i].first; // 获取顶点i的第一条边
        while (p != NULL) { // 遍历顶点i的边链表
            printf("(%d, %d) ", p->adjvex, p->weight); // 打印边的信息
            p = p->next; // 指向下一条边
        }
        printf("\n");
    }
}

// 判断两个顶点之间是否有边相连
int Adjacent(ALGraph G, int x, int y) {
    ArcNode *p = G.vertices[x].first; // 获取顶点x的第一条边
    while (p != NULL) { // 遍历顶点x的边链表
        if (p->adjvex == y) // 如果边的另一端是y
            return 1; // 返回1,表示存在边<x, y>
        p = p->next; // 指向下一条边
    }
    return 0; // 遍历完所有边仍未找到,返回0,表示不存在边<x, y>
}

// 打印顶点x的邻居顶点
void Neighbors(ALGraph G, int x) {
    ArcNode *p = G.vertices[x].first; // 获取顶点x的第一条边
    printf("Neighbors of vertex %d: ", x); // 打印提示信息
    while (p != NULL) { // 遍历顶点x的边链表
        printf("%d ", p->adjvex); // 打印邻居顶点
        p = p->next; // 指向下一条边
    }
    printf("\n");
}

// 插入顶点
void InsertVertex(ALGraph *G, int x) {
    G->vertices[G->vexnum].data = x; // 在最后一个位置插入顶点x的信息
    G->vertices[G->vexnum].first = NULL; // 边链表为空
    G->vexnum++; // 顶点数加一
}

// 删除顶点
void DeleteVertex(ALGraph *G, int x) {
    for (int i = 0; i < G->vexnum; i++) { // 遍历顶点数组
        ArcNode *p = G->vertices[i].first; // 获取顶点i的第一条边
        ArcNode *pre = NULL; // 初始化前一个边结点为NULL
        while (p != NULL) { // 遍历顶点i的边链表
            if (p->adjvex == x) { // 如果边的另一端是x
                if (pre == NULL) // 如果是第一条边
                    G->vertices[i].first = p->next; // 将顶点i的第一条边指向下一条边
                else
                    pre->next = p->next; // 将前一条边的next指针指向下一条边
                free(p); // 释放删除的边结点的内存
                break; // 结束循环
            }
            pre = p; // 更新前一个边结点
            p = p->next; // 指向下一条边
        }
    }
    // 删除顶点x
    for (int i = x; i < G->vexnum - 1; i++) // 从顶点x开始,将后面的顶点信息依次往前移动
        G->vertices[i] = G->vertices[i + 1]; // 将后一个顶点信息覆盖前一个顶点信息
    G->vexnum--; // 顶点数减一
}

// 添加边
void AddEdge(ALGraph *G, int x, int y, int weight) {
    if (!Adjacent(*G, x, y)) { // 如果两个顶点之间不存在边相连
        AddEdgeUndirectedALGraph(G, x, y, weight); // 添加边<x, y>
    }
}

// 删除边
void RemoveEdge(ALGraph *G, int x, int y) {
    if (Adjacent(*G, x, y)) { // 如果两个顶点之间存在边相连
        ArcNode *p = G->vertices[x].first; // 获取顶点x的第一条边
        ArcNode *pre = NULL; // 初始化前一个边结点为NULL
        while (p != NULL) { // 遍历顶点x的边链表
            if (p->adjvex == y) { // 如果边的另一端是y
                if (pre == NULL) // 如果是第一条边
                    G->vertices[x].first = p->next; // 将顶点x的第一条边指向下一条边
                else
                    pre->next = p->next; // 将前一条边的next指针指向下一条边
                free(p); // 释放删除的边结点的内存
                break; // 结束循环
            }
            pre = p; // 更新前一个边结点
            p = p->next; // 指向下一条边
        }
        // 如果是无向图,还需要删除另一个方向的边
        if (G->vertices[y].first != NULL) { // 如果顶点y有边相连
            p = G->vertices[y].first; // 获取顶点y的第一条边
            pre = NULL; // 初始化前一个边结点为NULL
            while (p != NULL) { // 遍历顶点y的边链表
                if (p->adjvex == x) { // 如果边的另一端是x
                    if (pre == NULL) // 如果是第一条边
                        G->vertices[y].first = p->next; // 将顶点y的第一条边指向下一条边
                    else
                        pre->next = p->next; // 将前一条边的next指针指向下一条边
                    free(p); // 释放删除的边结点的内存
                    break; // 结束循环
                }
                pre = p; // 更新前一个边结点
                p = p->next; // 指向下一条边
            }
        }
    }
}

// 获取顶点x的第一个邻居顶点
int FirstNeighbor(ALGraph G, int x) {
    if (G.vertices[x].first != NULL) // 如果顶点x有边相连
        return G.vertices[x].first->adjvex; // 返回第一个邻居顶点的信息
    else
        return -1; // 否则返回-1,表示没有邻居顶点
}

// 获取顶点x的邻居顶点y的下一个邻居顶点
int NextNeighbor(ALGraph G, int x, int y) {
    ArcNode *p = G.vertices[x].first; // 获取顶点x的第一条边
    while (p != NULL && p->adjvex != y) // 遍历顶点x的边链表,直到找到邻居顶点y
        p = p->next; // 指向下一条边
    if (p != NULL && p->next != NULL) // 如果邻居顶点y后面还有邻居顶点
        return p->next->adjvex; // 返回邻居顶点y的下一个邻居顶点的信息
    else
        return -1; // 否则返回-1,表示没有下一个邻居顶点
}

// 获取边<x, y>的权值
int Get_edge_value(ALGraph G, int x, int y) {
    ArcNode *p = G.vertices[x].first; // 获取顶点x的第一条边
    while (p != NULL) { // 遍历顶点x的边链表
        if (p->adjvex == y) // 如果边的另一端是y
            return p->weight; // 返回边的权值
        p = p->next; // 指向下一条边
    }
    return INF; // 遍历完所有边仍未找到,返回无穷大,表示不存在边<x, y>
}

// 设置边<x, y>的权值为v
void Set_edge_value(ALGraph *G, int x, int y, int v) {
    ArcNode *p = G->vertices[x].first; // 获取顶点x的第一条边
    while (p != NULL) { // 遍历顶点x的边链表
        if (p->adjvex == y) { // 如果边的另一端是y
            p->weight = v; // 设置边的权值为v
            break; // 结束循环
        }
        p = p->next; // 指向下一条边
    }
}

void visit(int v){
    printf("%d ",v);
}

// 定义队列结点
typedef struct QueueNode {
    int data;
    struct QueueNode* next;
} QueueNode;

// 定义队列
typedef struct {
    QueueNode *front;
    QueueNode *rear;
} Queue;

// 初始化队列
void InitQueue(Queue *Q) {
    Q->front = Q->rear = NULL;
}

// 入队操作
void EnQueue(Queue *Q, int data) {
    QueueNode *newNode = (QueueNode *)malloc(sizeof(QueueNode));
    newNode->data = data;
    newNode->next = NULL;
    if (Q->rear == NULL) {
        Q->front = Q->rear = newNode;
    } else {
        Q->rear->next = newNode;
        Q->rear = newNode;
    }
}

// 出队操作
int DeQueue(Queue *Q) {
    if (Q->front == NULL)
        return -1; // 队列为空
    QueueNode *temp = Q->front;
    int data = temp->data;
    Q->front = Q->front->next;
    if (Q->front == NULL)
        Q->rear = NULL;
    free(temp);
    return data;
}

// 判断队列是否为空
int IsQueueEmpty(Queue Q) {
    return Q.front == NULL;
}

// 广度优先遍历
void BFS(ALGraph G, int v, bool visited[], Queue *Q) { // 从顶点v出发,广度优先遍历图G
    visit(v); // 访问初始顶点v
    visited[v] = true; // 对v做已访问标记
    EnQueue(Q, v); // 顶点v入队列Q
    while (!IsQueueEmpty(*Q)) {
        int w;
        w=DeQueue(Q); // 顶点v出队列
        for (w = FirstNeighbor(G, v); w >= 0; w = NextNeighbor(G, v, w)) {
            // 检测v所有邻接点
            if (!visited[w]) { // w为v的尚未访问的邻接顶点
                visit(w); // 访问w
                visited[w] = true; // 标记w已被访问
                EnQueue(Q, w); // 顶点w入队
            }
        }
    }
}

// 广度优先遍历
// 对图G进行广度优先遍历
void BFSTraverse(ALGraph G) {
    bool visited[MaxVertexNum] = {false};
    Queue Q;
    InitQueue(&Q); // 初始化辅助队列
    // 从0号结点开始遍历
    for (int i = 0; i < G.vexnum; ++i) {
        if (!visited[i]) // 对每个连通分量调用一次BFS
            BFS(G, i, visited, &Q); // vi未被访问,则从vi开始BFS
    }
}

// 深度优先遍历
void DFS(ALGraph G, int v, bool visited[]) {
    visit(v); // 访问顶点v
    visited[v] = true; // 设已访问标记
    for (int w = FirstNeighbor(G, v); w >= 0; w = NextNeighbor(G, v, w)) {
        if (!visited[w]) {
            DFS(G, w, visited);
        }
    }
}

void DFSTraverse(ALGraph G) {
    bool visited[MaxVertexNum] = {false};
    for (int i = 0; i < G.vexnum; ++i) {
        if (!visited[i]) {
            DFS(G, i, visited);
        }
    }
}

int main() {
    // 创建有向图的邻接表表示
    ALGraph directedALGraph; // 定义有向图
    InitALGraph(&directedALGraph, 5, 6); // 初始化有向图
    AddEdgeDirectedALGraph(&directedALGraph, 0, 1, 1); // 添加边
    AddEdgeDirectedALGraph(&directedALGraph, 0, 2, 2); // 添加边
    AddEdgeDirectedALGraph(&directedALGraph, 1, 2, 3); // 添加边
    AddEdgeDirectedALGraph(&directedALGraph, 2, 3, 4); // 添加边
    AddEdgeDirectedALGraph(&directedALGraph, 3, 4, 5); // 添加边
    AddEdgeDirectedALGraph(&directedALGraph, 4, 0, 6); // 添加边

    // 创建无向图的邻接表表示
    ALGraph undirectedALGraph; // 定义无向图
    InitALGraph(&undirectedALGraph, 5, 6); // 初始化无向图
    AddEdgeUndirectedALGraph(&undirectedALGraph, 0, 1, 1); // 添加边
    AddEdgeUndirectedALGraph(&undirectedALGraph, 0, 2, 2); // 添加边
    AddEdgeUndirectedALGraph(&undirectedALGraph, 1, 2, 3); // 添加边
    AddEdgeUndirectedALGraph(&undirectedALGraph, 2, 3, 4); // 添加边
    AddEdgeUndirectedALGraph(&undirectedALGraph, 3, 4, 5); // 添加边
    AddEdgeUndirectedALGraph(&undirectedALGraph, 4, 0, 6); // 添加边

    printf("\nDirected Graph (Adjacency List):\n");
    PrintALGraph(directedALGraph); // 打印有向图的邻接表表示

    printf("\nUndirected Graph (Adjacency List):\n");
    PrintALGraph(undirectedALGraph); // 打印无向图的邻接表表示

    // 测试基本操作
    printf("\nTest Basic Operations:\n");
    printf("Adjacent(directedALGraph, 0, 1): %d\n", Adjacent(directedALGraph, 0, 1)); // 测试两个顶点之间是否有边相连
    printf("Adjacent(directedALGraph, 0, 3): %d\n", Adjacent(directedALGraph, 0, 3)); // 测试两个顶点之间是否有边相连
    printf("Adjacent(undirectedALGraph, 0, 1): %d\n", Adjacent(undirectedALGraph, 0, 1)); // 测试两个顶点之间是否有边相连
    printf("Neighbors(directedALGraph, 0): ");
    Neighbors(directedALGraph, 0); // 打印顶点0的邻居顶点
    printf("Neighbors(undirectedALGraph, 0): ");
    Neighbors(undirectedALGraph, 0); // 打印顶点0的邻居顶点
    InsertVertex(&directedALGraph, 5); // 插入顶点5
    printf("After InsertVertex(directedALGraph, 5):\n");
    PrintALGraph(directedALGraph); // 打印有向图的邻接表表示
    DeleteVertex(&directedALGraph, 5); // 删除顶点5
    printf("After DeleteVertex(directedALGraph, 5):\n");
    PrintALGraph(directedALGraph); // 打印有向图的邻接表表示
    AddEdge(&directedALGraph, 1, 4, 7); // 添加边
    printf("After AddEdge(directedALGraph, 1, 4, 7):\n");
    PrintALGraph(directedALGraph); // 打印有向图的邻接表表示
    RemoveEdge(&directedALGraph, 1, 4); // 删除边
    printf("After RemoveEdge(directedALGraph, 1, 4):\n");
    PrintALGraph(directedALGraph); // 打印有向图的邻接表表示
    printf("FirstNeighbor(undirectedALGraph, 2): %d\n", FirstNeighbor(undirectedALGraph, 2)); // 获取顶点2的第一个邻居顶点
    printf("NextNeighbor(undirectedALGraph, 2, 3): %d\n", NextNeighbor(undirectedALGraph, 2, 3)); // 获取顶点2的邻居顶点3的下一个邻居顶点
    printf("Get_edge_value(undirectedALGraph, 2, 3): %d\n", Get_edge_value(undirectedALGraph, 2, 3)); // 获取边
    Set_edge_value(&undirectedALGraph, 2, 3, 10);
    printf("After Set_edge_value(undirectedALGraph, 2, 3, 10):\n");
    PrintALGraph(undirectedALGraph);

    // 测试广度优先遍历和深度优先遍历
    printf("\nTest Traversal:\n");
    printf("BFS directed Graph:");
    BFSTraverse(directedALGraph); // 广度优先遍历有向图,起始节点为0
    printf("\nDFS directed Graph:");
    DFSTraverse(directedALGraph); // 深度优先遍历有向图,起始节点为0
    printf("\nBFS undirected Graph:");
    BFSTraverse(undirectedALGraph); // 广度优先遍历无向图,起始节点为0
    printf("\nDFS undirected Graph:");
    DFSTraverse(undirectedALGraph); // 深度优先遍历无向图,起始节点为0

    return 0;
}

在这里插入图片描述
在这里插入图片描述

  • 30
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我可以回答您的问题。 首先,是一种非线性数据结构,它由一组节点(顶点)和一组边组成。可以用多种方式存储,其中邻接矩阵是一种常见的存储方式。邻接矩阵是一个二维数组,数组中的元素中两个节点之间是否有边相连。如果节点i和节点j之间有边相连,则邻接矩阵中第i行第j列的元素为1,否则为0。 下面是用C语言实现邻接矩阵存储: ``` #define MAX_VERTEX_NUM 100 //最大顶点数 typedef struct { int vexs[MAX_VERTEX_NUM]; //顶点数组 int arcs[MAX_VERTEX_NUM][MAX_VERTEX_NUM]; //邻接矩阵 int vexnum, arcnum; //的顶点数和边数 } MGraph; ``` 其中,vexs数组用来存储的顶点,arcs数组用来存储邻接矩阵,vexnum的顶点数,arcnum的边数。 接下来是的遍的遍是指按照某种顺序访问中的每个节点。常用的两种遍方式是深度优先广度优先深度优先(Depth-First-Search,DFS)是一种先访问深度较深的节点的遍方式。具体实现可以用递归或栈来实现。 下面是用C语言实现深度优先: ``` void DFS(MGraph G, int v, int* visited) { visited[v] = 1; //标记节点v已经被访问 printf("%d ", G.vexs[v]); //输出节点v的值 for (int i = 0; i < G.vexnum; i++) { if (G.arcs[v][i] == 1 && visited[i] == 0) { //如果节点v和节点i之间有边相连且节点i未被访问过 DFS(G, i, visited); //递归访问节点i } } } void DFSTraverse(MGraph G) { int visited[MAX_VERTEX_NUM] = {0}; //标记数组,用来记录每个节点是否被访问过 for (int i = 0; i < G.vexnum; i++) { if (visited[i] == 0) { //如果节点i未被访问过 DFS(G, i, visited); //从节点i开始进行深度优先 } } } ``` 其中,DFS函数是递归实现的深度优先,DFSTraverse函数是遍整个的入口函数。 广度优先(Breadth-First-Search,BFS)是一种先访问深度较浅的节点的遍方式。具体实现可以用队列来实现。 下面是用C语言实现广度优先: ``` void BFSTraverse(MGraph G) { int visited[MAX_VERTEX_NUM] = {0}; //标记数组,用来记录每个节点是否被访问过 int queue[MAX_VERTEX_NUM]; //队列 int front = -1, rear = -1; //队列的头和尾 for (int i = 0; i < G.vexnum; i++) { if (visited[i] == 0) { //如果节点i未被访问过 visited[i] = 1; //标记节点i已经被访问 printf("%d ", G.vexs[i]); //输出节点i的值 queue[++rear] = i; //将节点i入队 while (front != rear) { //当队列不为空时 int j = queue[++front]; //将队列头部元素出队 for (int k = 0; k < G.vexnum; k++) { if (G.arcs[j][k] == 1 && visited[k] == 0) { //如果节点j和节点k之间有边相连且节点k未被访问过 visited[k] = 1; //标记节点k已经被访问 printf("%d ", G.vexs[k]); //输出节点k的值 queue[++rear] = k; //将节点k入队 } } } } } } ``` 其中,BFSTraverse函数是遍整个的入口函数,使用了队列来实现广度优先。 希望我的回答能够解决您的问题。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值