【数据结构16】图:基于邻接矩阵、邻接表实现DFS/BFS

目录

二、图的存储

三、邻接矩阵遍历

3.1邻接矩阵DFS

3.2邻接矩阵BFS

四、邻接表遍历

[4.1 邻接表添加边](#4.1 邻接表添加边)

4.2邻接表DFS

4.3邻接表BFS


一、图基础知识

图的数据结构:描述多对多的复杂关系

简单图 : 若不存在顶点到其自身的边,且同一条边不重复出现

无向图:边关系用(Vi,Vj)表示

有向图:边的关系用<Vi,Vj>

表示

有向无环图:一个有向图,从任一顶点出发无法经过若干条边回到该顶点,不存在有向环

混合图:边可能有向,可能无向

完全图:图中每两个顶点之间,都存在一条边

完全有向图:有n*(n-1)条边

完全无向图:有n*(n-1)/2条边

顶点的度:在无向图中,顶点所具有的边的数目

入度、出度:(有向图)

子图:设有两个图G = (V,E) 和 G1` = (V`,E`) ,若V`是V的子集,E' 是 E 的子集,则称G`是G的子图

回路或环:一条路径上开始点与结束点为同一个顶点的

欧拉环路:如果经过图中各边一次且恰好一次的环路,其长度恰好等与图中边的总数(欧拉环路相当于旅游,每条路都走一遍)

哈密尔顿环路:经过图中各顶点恰好一次的环路,其长度等于构成环路的边数(哈密尔顿相当于你去打工,到对应顶点打卡就行

连通图(无向图):图中任意两点存在直接或间接路径可相互抵达,为连通图,否则为非连通图

连通图只有一个极大连通子图(它本身)

非连通图(无直接或间接路径可抵达)有多个极大连通子图(其极大连通子图被称为连通分量,每个分量都是连通图,极大是因为再加一个非图中的点,就会导致它不再连通)

强连通图 (有向图):图中任意i、j两个顶点,从顶点i 到顶点j 和从ji都存在路径,则该图是强连通图,其只有一个极大强连通子图(它本身,也被称为极大强连通分量)

非强连通图有多个极大强连通子图(即强连通分量)

二、图的存储

邻接矩阵:有向图、无向图都可,适合无向稠密图

(有向)计算度:遍历一行(到谁) 入度:遍历一列(谁到)

(无向)计算度:遍历一行或一列(是对称矩阵)

邻接表:有向图、无向图都行,适合有向稀疏图

例:

正邻接(代表v1节点能到达哪些元素):

逆邻接(代表哪些节点能到达v1节点):

三、邻接矩阵遍历

3.1邻接矩阵DFS

visited数组:已访问数组用于标记元素有无被访问到(已经打印出来的,已经处理的)

复制代码
C

static int isEdge(int weight)
{
    if (weight > 0 && weight < INF)
    {
        return 1;  // 该边存在
    }
    return 0;     // 该边不存在
}

void DFS_MGraph(MGraph* graph, int v)
{
    visitMGraphNode(&graph->vex[v]);
    // 深搜:第一个元素标记为1
    MGraphVisited[v] = 1;
    for (int i = 0; i < graph->nodeNum; ++i)
    {
        // 以v为起始,遍历出每个与v相邻的元素 && 未被访问的
        if (isEdge(graph->edges[v][i]) && MGraphVisited[i] == 0)
        {
            // 以该点i,为起始点再继续深搜
            DFS_MGraph(graph,i);
        }
    }
}

这里的**visitMGraphNode数组,**标识为1的元素已经被访问过,标识为0的元素尚未被访问过

3.2邻接矩阵BFS

复制代码
C

void BFS_MGraph(MGraph* graph, int v)
{
    // 队列
    int que[MaxNodeNum];
    int rear = 0,front = 0;int cur;

    // rear先后移一位
    rear = (rear + 1) % MaxNodeNum;
    // v存在此时rear对应位置中
    que[rear] = v;
    // 如果已经进入队列,则将对应元素标记为1(表示已经进入队列)
    MGraphVisited[v] = 1;
    while (front != rear)
    {
        front = (front + 1) % MaxNodeNum;
        // 第一回:取出第一个元素
        cur = que[front];
        // 输出该元素
        visitMGraphNode(&graph->vex[cur]);
        for (int i = 0; i < graph->nodeNum; ++i)
        {
            // 如果存在与cur元素相邻的元素,且未进入队列
            if (isEdge(graph->edges[cur][i]) && !MGraphVisited[i])
            {
                // rear先后移一位
                rear = (rear + 1) % MaxNodeNum;
                // 将对应元素i存进队列此时rear的位置
                que[rear] = i;
                // 标记元素已经进入队列
                MGraphVisited[i] = 1;
            }
        }
    }
}

这里的**visitMGraphNode数组,**标识为1的元素已经进入队列,标识为0的元素尚未进入队列

四、邻接表遍历

4.1 邻接表添加边

复制代码
C

static ArcEdge *createArcEdge(int v,int w)
{
    // 申请的是一条边(编号no,权重weight,edge->next指向)
    ArcEdge *edge = malloc(sizeof(ArcEdge));
    if (edge == NULL)
    {
        return NULL;
    }
    edge->no = v;
    edge->weight = w;
    edge->next = NULL;   // 因为是头插法,这句不是非得写,而是更稳妥的编码习惯
    return edge;     // 创建边成功
}

void addAGraph(AGraph* graph, int x, int y, int w)
{
    // 0-n-1范围外排除
    if (x < 0 || x >= graph->nodeNum || y < 0 || y >= graph->nodeNum)
    {
        return;
    }
    if (x == y)return;  // 自己到自己,邻接表实现有向图不用管自环
    // edge 为节点为y,出边权重w
    ArcEdge *edge = createArcEdge(y,w);
    edge->next = graph->nodes[x].firstEdge;  //edge->next = graph->nodes[i].firstEdge = NULL
    // 头插法
    graph->nodes[x].firstEdge = edge;
    graph->edgeNum++;
    // 无向图
    if (graph->directed == 0)   // 表示没有方向,一条边代表两条边
    {
        // x->y->NULL  有向就变成: y->x->NULL
        edge = createArcEdge(x,w);
        edge->next = graph->nodes[y].firstEdge;
        graph->nodes[y].firstEdge = edge;
        graph->edgeNum++;
    }
}

4.2邻接表DFS

复制代码
C

void DFS_AGraph(AGraph* graph,int v)
{
    ArcEdge *p;   // 辅助指针
    graph->visited[v] = 1;
    visitAGraphNode(&graph->nodes[v]);   // 访问完v
    p = graph->nodes[v].firstEdge;    // p = v的下一个节点
    while (p)
    {
        if (graph->visited[p->no] == 0) // eg.原本遍历0,后来发现2,没被访问过开始DFS_AGraph(graph,2);
        {
            DFS_AGraph(graph, p->no);
        } // 发现0 2 3都被访问过,且已经输出,先又有0号的访问p = 0.firstEdge
        // p->no == 2节点,已经被访问,不进入if语句,于是p = p->next,于是p == 4;
        p = p->next;
    }
}

4.3邻接表BFS

复制代码
C

void BFS_AGraph(const AGraph* graph,int v)
{
    // 不知道多少个,所以动态的而不是数组
    int *que = malloc(sizeof(int) * graph->nodeNum);
    int front = 0,rear = 0;
    ArcEdge *p;
    int cur;

    rear = (rear + 1) % graph->nodeNum;
    que[rear] = v;
    // 表示已经放入队列了,终有一天会被访问的
    graph->visited[v] = 1;
    while (rear != front)
    {
        front = (front + 1) % graph->nodeNum;
        cur = que[front];
        // 一个一个访问
        visitAGraphNode(&graph->nodes[cur]);

        p = graph->nodes[cur].firstEdge;
        while (p)
        {
            // 遇见没访问的就存进队列
            if (graph->visited[p->no] == 0)
            {
                rear = (rear + 1)  % graph->nodeNum;
                que[rear] = p->no;
                // 表示已经放入队列了
                graph->visited[p->no] = 1;  
            }
            // 下一个
            p = p->next;
        }
    }
    free(que);
}

能做完这个学习小结是因为一句话"今天放自己一马,明天放自己一马,你是放马的吗"

如果我的内容对你有帮助,请点赞,评论,收藏。创作不易,大家的支持就是我坚持下去的动力!

相关推荐
阿正的梦工坊1 小时前
【Rust】17-Send、Sync 与并发安全抽象
算法·安全·rust
菩提树下的凡夫1 小时前
新版OpenCV5.0在ONNX模型的推理应用
opencv·算法
影寂ldy2 小时前
C# 三大内置委托(Action / Func / Predicate)+ Lambda
c++·算法·c#
机器学习之心2 小时前
小龙虾优化算法(COA)驱动的CNN-LSTM多输出回归模型及其SHAP可解释性分析
算法·cnn·lstm·小龙虾优化算法·cnn-lstm多输出回归·shap可解释性分析
阿正的梦工坊2 小时前
【Rust】13-Trait 系统、动态分发与对象安全
算法·安全·rust
言存2 小时前
力扣热题283 移动零
数据结构·算法·leetcode
字节高级特工2 小时前
智能指针原理与使用场景全解析
开发语言·c++·算法
珊瑚里的鱼2 小时前
【动态规划】买卖股票的最佳时机Ⅲ
算法·动态规划
逻辑星辰2 小时前
x-ds-pow-response逆向分析
开发语言·人工智能·python·深度学习·算法