数据结构与算法·第7章【图】

news2025/2/23 18:01:50

大部分定义都在离散数学II中学过了,所以对于已知或常见的我不多赘述

  • 弧或边带权的图分别称作有向网或无向网。
  • 若边或弧的个数 e<nlogn,则称作稀疏图,否则称作稠密图。
  • 对有向图,若任意两个顶点之间都存在一条有向路径,则称此有向图为强连通图。

基本操作

  • CreatGraph(&G, V, VR):
    // 按定义(V, VR) 构造图
  • DestroyGraph(&G):
    // 销毁图
  • LocateVex(G, u);
    // 若G中存在顶点u,则返回该顶点在图中“位置” ;否则返回其它信息。
  • GetVex(G, v); // 返回 v 的值。
  • PutVex(&G, v, value);
    // 对 v 赋值value。
  • FirstAdjVex(G, v);
    // 返回 v 的“第一个邻接点” 。若该顶点在 G 中没有邻接点,则返回“空”。
  • NextAdjVex(G, v, w);
    // 返回 v 的(相对于 w 的) “下一个邻接点”。若 w 是 v 的最后一个邻接点,则返回“空”。
  • InsertVex(&G, v);
    //在图G中增添新顶点v。
  • DeleteVex(&G, v);
    // 删除G中顶点v及其相关的弧。
  • InsertArc(&G, v, w);
    // 在G中增添弧<v,w>,若G是无向的,则还增添对称弧<w,v>。
  • DeleteArc(&G, v, w);
    //在G中删除弧<v,w>,若G是无向的,则还删除对称弧<w,v>。
  • DFSTraverse(G, v, Visit());
    //从顶点v起深度优先遍历图G,并对每个顶点调用函数Visit一次且仅一次。
  • BFSTraverse(G, v, Visit());
    //从顶点v起广度优先遍历图G,并对每个顶点调用函数Visit一次且仅一次。

存储结构

邻接矩阵:

typedef struct {
    char* vexs[MAX_VERTEX_NUM]; // 顶点列表,每个元素为一个字符串,代表一个顶点
    ArcCell arcs[MAX_VERTEX_NUM][MAX_VERTEX_NUM]; // 邻接矩阵,二维数组表示从一个顶点到另一个顶点的弧,每个弧存储了该弧的相关信息
    int vexnum; // 顶点个数
    int arcnum; // 弧个数
    GraphKind kind; // 图的类型
} MGraph;

邻接表:

typedef struct ArcNode {
    int adjvex; // 该弧所指向的顶点的位置
    struct ArcNode* nextarc; // 指向下一条弧的指针
    char* info; // 该弧相关信息的指针
} ArcNode;

typedef struct VNode {
    char* data; // 顶点信息
    ArcNode* firstarc; // 指向第一条依附该顶点的弧
} VNode, AdjList[MAX_VERTEX_NUM];

十字链表
在这里插入图片描述

<v,w>表示从 v 到 w 的一条弧,并称 v 为弧尾,w 为弧头。

typedef struct ArcBox { // 弧的结构表示
    int tailvex, headvex; // 该弧尾和头顶点的位置
    struct ArcBox* hlink, *tlink; // 分别指向同一头结点和同一尾结点的下一条弧
    char* info; //该弧相关信息的指针
} ArcBox;
typedef struct VexNode { // 邻接多重表的结构表示
    char data; // 存储顶点信息
    ArcBox* firstin, *firstout; // 分别指向以该顶点为终点和起点的第一条弧
} VexNode;

这个应该不是很重要,主要都是邻接矩阵

遍历

深度优先搜索dfs(这个比较熟了,不过多赘述)
一种非递归写法:

void DFS(Graph G, int v) {
    int w;
    InitStack(&S); // 初始化栈S
    Push(&S, v);
    visited[v] = TRUE; // 标记起点v已访问
    while (!StackEmpty(S)) {
        Pop(&S, &v); // 取出栈顶元素v
        Visit(v); // 访问v
        w = FirstAdjVex(G, v); // 求v的第一个邻接点w
        while (w >= 0) {
            if (!visited[w]) { // w为v的尚未访问的邻接顶点
                Push(&S, w); // 将w入栈
                visited[w] = TRUE; // 标记w为已访问
            }
            w = NextAdjVex(G, v, w); // 求v相对于w的下一个邻接点
        } // end while
    } // end while
}

广度优先搜索bfs(类似 ‘树’ 那章的层次遍历)

void BFSTraverse(Graph G, Status(*Visit)(int)) {
    int v, w;
    Status status; // 初始化一个状态变量
    Queue Q; // 定义辅助队列Q
    for (v = 0; v < G.vexnum; ++v)
        visited[v] = FALSE; // 初始化访问标志
    InitQueue(&Q); // 置空的辅助队列Q
    for (v = 0; v < G.vexnum; ++v) {
        if (!visited[v]) { // v尚未访问
            visited[v] = TRUE; // 标记v为已访问
            status = Visit(v); // 访问v
            if (status == ERROR) return; // 如果访问失败,则返回错误
            EnQueue(&Q, v); // 将v入队列
            while (!QueueEmpty(Q)) { // 队列不为空时执行循环
                DeQueue(&Q, &u); // 队头元素出队并置为u
                for (w = FirstAdjVex(G, u); w >= 0; w = NextAdjVex(G, u, w)) {
                    if (!visited[w]) { // w为u的尚未访问的邻接顶点
                        visited[w] = TRUE; // 标记w为已访问
                        status = Visit(w); // 访问w
                        if (status == ERROR) return; // 如果访问失败,则返回错误
                        EnQueue(&Q, w); // 将w入队列
                    } // if
                } // for
            } // while
        }
    }
}

for (w = FirstAdjVex(G, u); w >= 0; w = NextAdjVex(G, u, w)) 这一条代码仔细看看

最小生成树

  1. 生成树:
    是一个极小连通子图,它含有图中全部顶点,但只有 n-1 条边。

  2. 最小生成树:
    如果无向连通图是一个带权图,那么它的所有生成树中必有一棵边的权值总和为最小的生成树,称这棵生成树为最小代价生成树,简称最小生成树。

Kruskal算法特点:将边归并,适于求稀疏网的最小生成树。

Prime算法特点:将顶点归并,与边数无关,适于稠密网

普里姆算法

在生成树的构造过程中,图中 n 个顶点分属两个集合:已落在生成树上的顶点集 U 和尚未落在生成树上的顶点集 V-U ,则应在所有连通 U 中顶点和 V-U 中顶点的边中选取权值最小的边。

设置一个辅助数组 closedge,对当前 V-U 集中的每个顶点,在辅助数组中存在一个相应分量 closedge[i-1],它包括两个域,其中 lowcost 存储该边上的权。对每个顶点 vi ∈ V-U,有如下公式成立:

closedge[i-1].lowcost = Min { cost(u, v) | u ∈ U }

其中 cost(u, v) 表示边 (u, v) 的权值。显然,lowcost 记录的是当前该节点与已选择的节点中权值最小的边的权值。

struct {
    VertexType adjvex;  // U集中的顶点序号
    VRType lowcost;     // 边的权值
} closedge[MAX_VERTEX_NUM];

算法

void MiniSpanTree_P(MGraph G, VertexType u) {
    int i, j, k;
    k = LocateVex(G, u);
    for (j = 0; j < G.vexnum; ++j) {
        if (j != k) {
            closedge[j].adjvex = u;
            closedge[j].lowcost = G.arcs[k][j].adj;
        }
    }
    closedge[k].lowcost = 0;
    for (i = 1; i < G.vexnum; ++i) {
        k = minimum(closedge);  // 求出加入生成树的下一个顶点(k)
        printf("(%d, %d) ", closedge[k].adjvex, G.vexs[k]);  // 输出生成树上一条边
        closedge[k].lowcost = 0;  // 第k顶点并入U集
        for (j = 0; j < G.vexnum; ++j) { // 修改其它顶点的最小边
            if (G.arcs[k][j].adj < closedge[j].lowcost) {
                closedge[j].adjvex = G.vexs[k];
                closedge[j].lowcost = G.arcs[k][j].adj;
            }
        }
    }
}

k = minimum(closedge);
if (G.arcs[k][j].adj < closedge[j].lowcost) { closedge[j].adjvex = G.vexs[k]; closedge[j].lowcost = G.arcs[k][j].adj; }
这2条代码值得看看
时间复杂度是 O ( n 2 ) O(n^2) O(n2)

克鲁斯卡尔算法

  1. 构造只含 n 个顶点的子图 SG

  2. 从权值最小的边开始,如果添加这条边不会在 SG 中产生回路,则在 SG 上加上这条边。

  3. 重复以上步骤,直至 SG 上加上 n-1 条边为止。

  4. 再次重复以上步骤,构造另一个最小生成树。

具体参考
时间复杂度 O ( e l o g e ) O(eloge) O(eloge)

拓扑排序、关键路径

拓扑排序

适用于有向无环图

拓扑排序是一种特殊的排序方式,它基于有向图中各个节点之间的依赖关系,通过一系列的操作得到拓扑有序序列。具体实现方式是:按照有向图给出的次序关系,将图中顶点排成一个线性序列,对于有向图中没有限定次序关系的顶点,则可以人为加上任意的次序关系。由此所得顶点的线性序列称之为拓扑有序序列。

在这里插入图片描述

拓扑排序的实现步骤如下:

  1. 从有向图中选取一个没有前驱的顶点,并输出之;
  2. 从有向图中删除此顶点以及所有以它为尾的弧;
  3. 重复步骤 1 和 2,直至图为空,或者图不空但找不到无前驱的顶点为止。后一种情况说明有向图中存在环。

关键路径

在关键路径的分析中,我们需要计算每个事件(顶点)的最早发生时间和最迟发生时间:

  1. 事件(顶点)的最早发生时间 ve(j) 等于从源点到顶点 Vj 的最长路径长度;
    这个时间决定了所有以 Vj 为尾的弧所表示的活动的最早开始时间。

  2. 事件(顶点)的最迟发生时间 vl(k) 表示在不推迟整个工程完成的前提下,事件最迟发生的时间;
    vl(k) 等于工程完成时间减去从顶点 Vk 到汇点的最长路径长度。

在关键路径分析中,计算事件发生时间的公式如下:

事件发生时间的计算公式:

- ve(源点) = 0;
- ve(k) = Max {ve(j) + dut(<j, k>)}

   其中,dut(<j,k>) 表示从顶点 j 到顶点 k 的活动所需的时间或耗费。

- vl(汇点) = ve(汇点);
- vl(j) = Min {vl(k) - dut(<j, k>)}

   其中,vl(j) 表示从顶点 j 开始到工程结束所能容忍的最大延迟时间。

在关键路径分析中,求解最早发生时间 ve 和最迟发生时间 vl 的顺序如下:

  1. 求 ve 顺序:按拓扑有序的次序,从源点开始,依次计算每个顶点的最早发生时间。

  2. 求 vl 顺序:按拓扑逆序的次序,从汇点开始,依次计算每个顶点的最迟发生时间。

最短路径

  1. 单源最短路径算法 - Dijkstra(迪杰斯特拉)算法:
    该算法可以求解从源点到其它所有顶点的最短路径,但要求图中不存在负权边。

  2. 所有顶点间的最短路径算法 - Floyd(弗洛伊德)算法:
    该算法可以求解图中任意两个顶点之间的最短路径,可以处理带有负权边的图。

在图论中,最短路径和最短路径长度的概念如下:

  1. 对于一个无权图,从一个结点到另一个结点可能存在多条路径,路径长度最短的那条路径称为最短路径。其长度称为最短路径长度。

  2. 对于一个带权图,从一个结点到另一个结点可能存在多条路径,带权路径长度最短的那条路径称为最短路径。其带权路径长度称为最短路径长度。

具体参考

习题

深度优先搜索、邻接表是否存在路径

试基于图的深度优先搜索策略写一算法,判别以邻接表方式存储的有向图中是否存在由顶点 v   i   v~i~ v i 到顶点 v   j   v~j~ v j 的路径( i ≠ j i≠j i=j)注意:算法中涉及的图的基本操作必须在此存储结构上实现。

#define MAX 100
typedef struct Arc {
    int vex;
    struct Arc *next;
    int info;
} Arc;
typedef struct Vertex {
    int info;
    Arc *first;
} Vertex;
Vertex vertex[MAX]; // 图的顶点集合,最多储存 MAX 个顶点
int visited[MAX]; // 记录每个顶点是否被访问过
int DFS(int i, int j) { // 从第 i 个顶点开始,寻找是否有一条路径连接到第 j 个顶点
    Arc* p; 
    if(i == j) return 1; // 如果 i 和 j 相同,返回 1 表示有路径连接
    else {
        visited[i] = 1; // 标记 i 为已访问
        for(p = vertex[i].first; p != NULL; p = p->next) { // 遍历顶点 i 的所有邻居
            int k = p->vex; // 取出邻居的编号
            if(!visited[k] && DFS(k, j)) return 1; // 如果邻居没有被访问过且与 j 有路径连接,返回 1
        }
        return 0; // 没有找到从 i 到 j 的路径,返回 0
    }
}

仍然上一题的要求、但是广搜

#define MAX 100
typedef struct Arc {
    int vex;
    struct Arc *next;
    int info;
} Arc;
typedef struct Vertex {
    int info;
    Arc *first;
} Vertex;
Vertex vertex[MAX]; // 图的顶点集合,最多储存 MAX 个顶点
bool visited[MAX]; // 记录每个顶点是否被访问过
int BFS(int i, int j) {
    Arc* p; 
    Queue Q;
    initQueue(&Q);
    visited[i] = 1; 
    enQueue(&Q, i); 
    
    while(!isEmpty(Q)) {
        int cur = deQueue(&Q); 
        if(cur == j)  return 1;
        for(p = vertex[cur].first; p != NULL; p = p->next) {
            int k = p->vex; 
            if(!visited[k]) { 
                visited[k] = 1; 
                enQueue(&Q, k); 
            }
        }
    }
    return 0;
}

基于邻接表存储结构的Dijkstra算法

#define MAXN 100010
#define INF 0x3f3f3f3f
typedef struct Edge {
    int v; // 边的终点
    int w; // 边的权值
    int next; // 指向下一条边的指针
} Edge;
Edge edge[MAXN]; // 存储所有边的数组
int head[MAXN]; // 邻接表头指针
int dist[MAXN]; // 存储源点到各个顶点的最短距离
bool vis[MAXN]; // 标记顶点是否被访问过
// Dijkstra算法求解最短路径
void dijkstra(int s) {
    memset(vis, false, sizeof(vis));
    memset(dist, INF, sizeof(dist));
    dist[s] = 0;
    for (int k = 1; k < n; k++) { // 重复n-1次
        int u = -1;
        for (int i = 1; i <= n; i++) { // 找到一个未被访问过的距离最小的顶点u
            if (!vis[i] && (u == -1 || dist[i] < dist[u])) u = i;
        }
        vis[u] = true; // 标记u已经被访问过
        // 遍历与u相邻的所有顶点v,更新源点到v之间的最短距离
        for (int i = head[u]; i != -1; i = edge[i].next) {
            int v = edge[i].v;
            int w = edge[i].w;
            if (!vis[v] && dist[u] + w < dist[v]) dist[v] = dist[u] + w;
        }
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/625361.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【深蓝学院】手写VIO第3章--基于优化的 IMU 与视觉信息融合--笔记

0. 内容 1. 基于BA的VIO融合 优化的方法学会之后&#xff0c;滤波的方法也就会了。 具体的求解BA问题参考的是SBA的论文&#xff0c;使用的是LM算法&#xff08;里面有个关于权重μ的计算方法&#xff0c;不同人的实现可能不一样&#xff0c;这些都是实现细节&#xff09; …

全新 FinClip Studio 现已上线

FinClip IDE &#xff08;FinClip Integrated Development Environment&#xff0c;简称为 FIDE&#xff09;是面向开发者推出的「小程序桌面端集成开发环境」。自 2021 年起就一直陪伴来自不同平台的小程序开发者完成小程序开发、调试、预览、上传等各类功能。 随着 FinClip …

【MySQL】初识数据库

数据库基础知识 一、什么是数据库二、mysql与mysqld三、服务器、数据库、表关系四、数据库的连接、创建与使用4.1 安装地址4.2 连接服务器4.3 数据库的基本使用4.4 数据逻辑存储 五、MySQL架构六、SQL分类七、存储引擎 一、什么是数据库 存储数据用文件就可以了&#xff0c;为…

一文即可了解!Web测试(性能测试 / 界面测试 / 兼容性测试 / 安全性测试)

目录 前言&#xff1a; 一、Web性能测试&#xff1a;&#xff08;压力测试、负载测试、连接速度测试&#xff09; 二、Web界面测试&#xff1a;&#xff08;导航测试、图形测试、内容测试、整体界面测试&#xff09; 三、Web兼容性测试&#xff1a;&#xff08;平台&#x…

Linux搭建Java环境——安装MySQL5.7(CentOS7.6)

一、使用Xftp上传MySQL安装包&#xff0c;并解压 这里可以直接使用安装包也可以使用命令直接在Xshell运行 wget http://dev.mysql.com/get/mysql-5.7.26-1.el7.x86_64.rpm-bundle.tar 当然要提前创建好文件夹/opt/mysql&#xff0c;并cd进去&#xff0c;运行tar -xvf mysql-…

通义千问写的高考作文你觉得怎么样?

目录 一、全国卷-全国甲卷 二、北京卷 三、上海卷 今天带大家使用通义千问来体验一下2023年高考作文&#xff0c;大家一起来一睹为快吧&#xff01; 一、全国卷-全国甲卷 阅读下面的材料&#xff0c;根据要求写作。&#xff08;60分&#xff09; 人们因技术发展得以更好地掌控…

计算机网络管理-使用SNMPc开展网管活动

一、实验目的 全面学习SNMPc网络管理软件业务服务监控功能&#xff0c;了解如何使用网管软件从事网络管理工作 二、实验内容与设计思想 1&#xff09;操作映射数据库。 2&#xff09;查看管理对象的MIB数据。 3&#xff09;创建、保存长期统计数据&#xff08;要求一定时长…

django传统全栈开发一个ChatGPT应用

根据客户需求&#xff0c;开发一个能多人使用的ChatGPT平台&#xff0c;背后使用的是ChatGPT的api_key。 需求 1、可多轮对话 2、可删除对话 3、流式显示对话 4、可多人使用 5、多个api_key均衡使用 技术分析 第一次接触openai的二次开发&#xff0c;看文档、看文章&…

软件测试-金融银行项目怎么测?系统业务测试总结分析...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 银行测试人员的组…

《计算机组成原理》期末考试手写笔记——模块三:运算方法与运算器(变形补码计算+原码补码一位乘法计算+浮点运算)

本课程的期末考试复习笔记与你分享&#xff0c;祝你考试成功~ 目录 &#xff08;一&#xff09;知识点总结 知识点1&#xff1a;计算机中的运算&#xff08;不考&#xff09; 知识点2&#xff1a;定点加减法运算&#xff08;很简单&#xff09; 知识点3&#xff1a;定点乘法…

IDE/在VS2015中集成Qt开发环境

文章目录 概述安装VS2015安装Qt VS Tools 插件Qt相关配置应用到所有项目配置增加配置删除 VS项目属性下Qt工程配置&#xff08;Qt Project Settings&#xff09;Qt VersionsQt Modules编辑器找不到Qt的类 QtDesigner在VS下使用无法打开UI/打开失败找不到UI编译中间文件UI添加的…

package.json与package-lock.json区别需不需要被.gitignore文件忽略

前言 先要搞清楚package.json与package-lock.json文件分别是什么作用 .gitignore文件是干什么的-主页文章有 介绍 package.json 1.项目名称版本号&#xff0c;描述&#xff0c;以及运行命令和一些配置的node环境命令 2.记录那些包会在开发环境中用到&#xff0c;哪些包会…

【项目】从零实现一个高并发内存池

目录 一、项目介绍 1、该项目的原型 2、该项目所涉及到的技术及博主往期参考文章 3、池化技术 4、内存池的内碎片和外碎片 二、先来看一个定长内存池设计 三、高并发内存池的三层框架设计 1、thread cache的实现 1.1thread cache整体框架 1.2哈希桶映射对齐规则 1.3…

【送书福利-第九期】Web安全攻防从入门到精通

大家好&#xff0c;我是洲洲&#xff0c;欢迎关注&#xff0c;一个爱听周杰伦的程序员。关注公众号【程序员洲洲】即可获得10G学习资料、面试笔记、大厂独家学习体系路线等…还可以加入技术交流群欢迎大家在CSDN后台私信我&#xff01; 本文目录 一、前言二、内容介绍三、作者介…

【LeetCode】768. 最多能完成排序的块 II

768. 最多能完成排序的块 II&#xff08;困难&#xff09; 思路 对于已经分好块的数组&#xff0c;若块数大于 1&#xff0c;则可以得到以下结论&#xff1a;「 右边的块的所有数字均大于或等于左边的块的所有数字」。考虑这个问题&#xff1a;对于已经分好块的数组&#xff0…

Linux——多线程互斥

多线程互斥 抢票问题互斥锁锁的接口 理解锁锁的背景概念如何看待锁&#xff1a;加锁和解锁的原理锁的封装 可重入与线程安全死锁死锁的概念与条件 抢票问题 这里用上一篇&#xff1a; https://blog.csdn.net/qq_63580639/article/details/131054847?spm1001.2014.3001.5501 的…

LVGL lv_color_t 像素定义详解

更多源码分析请访问&#xff1a;LVGL 源码分析大全 目录 1、概述2、颜色格式详解2.1、LV_IMG_CF_RAW_X2.2、LV_IMG_CF_TRUE_COLOR_X2.3、LV_IMG_CF_INDEXED_XBIT2.4、LV_IMG_CF_ALPHA_XBIT2.5、LV_IMG_CF_RGBX 3、送显函数&#xff08;flush_cb&#xff09;中的 lv_color_t附录…

【P54】JMeter 生成概要结果(Generate Summary Results)

文章目录 一、生成概要结果&#xff08;Generate Summary Results&#xff09;参数说明二、准备工作三、测试计划设计 一、生成概要结果&#xff08;Generate Summary Results&#xff09;参数说明 可以将测试结果在客户端模式下输出&#xff0c;同时能美化压测输出的结果 使…

申请Let‘s Encrypt免费SSL证书、自动化续签证书

一、环境 安装证书的环境为Centos Nginx&#xff0c;如果没有安装Nginx则需要先安装。 二、申请流程 1、开放80和443端口 firewall-cmd --permanent --add-port80/tcp firewall-cmd --permanent --add-port443/tcp firewall-cmd --reload2、安装 certbot 使用certbot工具能…

Doris动态表使用快速入门实战

1. 动态表构功能概述 半结构化数据&#xff0c;是介于结构化和非结构化之间的数据。和普通纯文本相比&#xff0c;半结构化数据具有一定的结构性。和结构化数据相比&#xff0c;其结构变化复杂&#xff0c;我们又不能方便的使用结构化的方式去描述它。 半结构的数据中通常即包…