【算法与数据结构】图

1、图的基本概念 G = (V,E)

无向完全图:n个顶点,n(n-1)/2条边;                     有向完全图:n个顶点,n(n-1)条边;

邻接:(vi,vj)=>vi和vj互为邻接点;                             <vi,vj>=>vi邻接vj,vj邻接vi;

顶点的度:与顶点相关联的边的数目,TD(v);        有向图顶点的度 = 入度 + 出度;

路径:接续的边构成的顶点序列;                            路径长度:路径上边或弧的数目/权值之和;

回路:第一个顶点和最后一个顶点相同的路径;      简单路径:除起点和终点其他结点都不相同;

连通图:无向图任意两顶点之间都存在路径;       强连通图:有向图任意两顶点之间都存在路径;

连通分量:无向图的极大连通子图(顶点数目已经最大,再加一个顶点不连通);

强连通分量:有向图的极大连通子图(顶点数目已经最大,再加一个顶点不连通);

极小连通子图:连通子图中删除任一条边子图都不再连通;

生成树:包含所有顶点的极小连通子图;    生成森林:非连通图的各个连通分量的生成树的集合;

2、图的存储结构

逻辑结构:多对多

存储结构

        没有顺序存储,可借助二维数组表示数组元素之间的关系【数组表示法:邻接矩阵

        链式存储:邻接表、邻接多重表、十字链表

(1)邻接矩阵(表示唯一)

无向图:邻接矩阵是对称矩阵;顶点 i 的度 = 第 i 行(列)中1的个数;

有向图:邻接矩阵(不对称)中,vi 出度 = 第 i 行中 1 的个数;vi入度 = 第 i 列中 1 的个数;

(边上带权值):有边记权值,无边记 

结构体定义:用两个数组分别存储顶点表和邻接矩阵;

// 邻接矩阵结构体定义
#define MaxVertexNum 100 // 顶点数目的最大值
#define MaxInt 32767 // 用于邻接矩阵初始化
typedef char VertexType // 设置顶点数据类型
typedef int EdgeType // 设置边数据类型
typedef struct {
    VertexType Vex[MaxVertexNum]; // 顶点数组(一维)
    EdgeType Edge[MaxVertexNum][MaxVertexNum]; // 边数组(二维)
    int vexnum, arcnum; // 图的当前顶点数和弧数
}MGraph;

采用邻接矩阵表示法创建无向网、无向图、有向网、有向图(无向图+有向网)

算法思想:

        输入总顶点数和总边数;

        依次输入顶点信息存入顶点表中;

        初始化邻接矩阵,是每个权值初始化为极大值;

        构造邻接矩阵;

【注意】构造无向图:MaxInt = 0;w = 1;即可

构造有向网:去掉G.Edge[n][m] = G.Edge[m][n]; 对称属性即可

构造有向图:无向图+有向图的修改内容

// 创建无向网
void createUDN(MGraph &G){
    char v1, v2; // 定义边的始终顶点
    int m, n, w; // 定义始终结点在顶点表中的下标m,n; w为权重
    scanf("%d %d", &G.vexnum, &G.arcnum); // 输入总顶点数和总边数
    for(int i = 0; i < G.vexnum; i++)
        scanf("%c", &G.Vex[i]);            // 构造顶点表
    for(int i = 0; i < G.vexnum; i++)
         for(int j = 0; j < G.vexnum; j++)
            G.Edge[i][j] = MaxInt;         // 初始化邻接矩阵
    for(int i = 0; i < G.arcnum; i++){     // 构造邻接矩阵
        scanf("%c %c %d", &v1, &v2, &w);   // 输入始终顶点和边权重
        m = LocateVex(G, v1);              // 定位起始顶点v1的下标
        n = LocateVex(G, v2);              // 定位终点顶点v2的下标
        G.Edge[m][n] = w;                  // 对应边赋予权值w
        G.Edge[n][m] = G.Edge[m][n];       // 无向网邻接矩阵对称
    }
}

// 定位顶点下标
int LocateVex(MGraph G, VertexType v){
    for(int i = 0; i < G.vexnum; i++){
        if(G.Vertex[i] == v) {
            return i;
        }
    }
    return -1;
}      

优点:检查两顶点是否存在边很容易;找一点的邻接点很容易;计算顶点的度很容易;

缺点:不便于增加和删除顶点;

           浪费空间 --- 空间复杂度

,所以之和顶点数有关,适用于稠密图

           浪费时间 --- 统计稀疏图一共有多少条边

(2)邻接表(表示不唯一)

无向图:n个结点e条边,邻接表需n个头结点和2e个表结点,空间复杂度O(n + 2e),适合稀疏图

              vi的度为第 i 个单链表中的结点数;  

有向图:vi的出度为第 i 个单链表中的结点个数;vi的入度为单链表中邻接域值是 i - 1 的结点个数

结构体定义:顶点表(数组)+ 边表结点(ArcNode)+ 邻接表;

#define MaxVertexNum 100
typedef struct ArcNode {        // 边表结点
    int adjvex;                 // 指向的顶点下标
    struct ArcNode *next;       // 指向下一条弧的指针
    // InfoType info;           // 如果是网的话,可以记录权值
}ArcNode;
typedef struct VexNode {        // 顶点结点
    VertexType data;            // 顶点信息
    ArcNode *first;             // 指向第一条弧的指针
}VexNode, AdjList[MaxVertexNum];
typedef strcut{                 
    AdjList vertices;           // 邻接表
    int vexnum, arcnum;         // 图的顶点数和边数
}ALGraph;

 采用邻接矩阵表示法创建无向网

算法思想:

        输入总顶点数和总边数;

        建立顶点表

                依次输入点的信息存入顶点表中

                使每个表头结点的指针域初始化为NULL

        创建邻接表

                依次输入每条边依附的两个顶点

                确定两个顶点的序号i和j,建立边结点

                将此边结点分别插入到vi和vj对应的两个边链表的头部

// 创建无向网
void createUDN(ALGraph &G){
    scanf("%d %d", &G.vexnum, &G.arcnum);     // 输入顶点个数和边数
    for(int i = 0; i < G.vexnum; i++){        // 建立顶点表
        scanf("%c", &G.vertices[i].data);
        G.vertices[i].first = NULL;
    }
    for(int i = 0; i < G.arcnum; i++){        // 构造邻接表
        scanf("%c %c", &v1, &v2);
        m = LocateVex(G, v1);
        n = LocateVex(G, v2);
        ArcNode p1;                           // 新建边表结点p1
        p1->adjvex = n;
        p1->next = G.vertices[m].first;
        G.vertices[m].first = p1;             // 顶点v1的first指向p1
        ArcNode p2;                           // 新建边表结点p2
        p2->adjvex = m;
        p2->next = G.vertices[n].first;
        G.vertices[n].first = p2;             // 顶点v2的first指向p2
    }
}

优点:方便找任一顶点的邻接点;

           空间复杂度O(n+e);【无向图O(n+2e), 有向图O(n+e) => 数量级O(n+e)】

           适用稀疏图,节约空间;

           无向图计算度容易;有向图计算出度容易,计算入度需要“逆邻接表”;

缺点:不方便检查任意一对顶点之间是否存在边。

(3)十字链表(针对有向图)不唯一  空复O(n+e)

(4)邻接多重表(针对无向图)不唯一  空复O(n+e)

3、图的遍历

(1)广度优先遍历

算法:利用【邻接表 + 队列】表示图的广度优先遍历

void BFS(AlGraph G, int v){
    printf("%d", v);    // 访问初始顶点v
    visit[v] = true;    // 标记v已访问
    InitQueue(Q);       // 初始化队列
    EnQueue(Q, v);      // 将v入队
    while(!isEmpty(Q)) {
        DeQueue(Q, v);    // 顶点v出队
        for(w = FirstNeighbor(G, v); w >= 0; w = NextNeighbor(G, v, w)){ //检测所有邻接点
            if(!visit[w]){
                printf("%d", w); visit[w] = true; EnQueue(Q, w);
            }
        }
    }
}   

算法分析: 

空间复杂度O(V);

若图利用邻接矩阵存储:时间复杂度O(V^{2})       稠密图适合用邻接矩阵进行BFS

若图利用邻接表存储:    时间复杂度O(V + E)   稀疏图适合用邻接表进行BFS 

(2)深度优先遍历

算法:利用【邻接矩阵 + 递归工作栈】表示图的深度优先遍历

void DFS(MGraph G, int v){    // G为邻接矩阵 , 顶点为v
    printf("%d", v); //输出顶点
    visit[v] = true;    // 标记该顶点为true
    for(int w = 0; w < G.vexnum; w++){
        if(G.Edge[v][w] != 0 && !visit[w]){
            DFS(G, w);    // 递归实现
        }
    }
}

算法分析

空间复杂度O(V);

若图利用邻接矩阵存储:时间复杂度O(V^{2})       稠密图适合用邻接矩阵进行DFS

若图利用邻接表存储:    时间复杂度O(V + E)   稀疏图适合用邻接表进行DFS

4、图的应用

对应无向图

(1)最小生成树(各边权值之和最小)要会画算法流程

对应有向图

(2)最短路径(典型问题----交通网络)

Dijkstra算法【时间复杂度O(V^{2})

【注意】边上带有负权值时,该算法不适用

例题 

 

Floyd算法【时间复杂度O(V^{3})

【注意】带有负权值的回路时,该算法不适用

例题

(3)拓扑排序(AOV)

        1)依次输出入读为0的序列即可;若最后为空则为有向无环图,否则图中必有环。

        2)时间复杂度:邻接表O(V + E);邻接矩阵O(V^{2})。

        3)若每个顶点有唯一的前驱关系,则拓扑序列唯一,否则不唯一。

(4)关键路径(AOE)

【用v表示事件开始或结束;用e表示活动】

四个量:ve(i) —— 事件最早开始事件(取Max);vl(i) —— 事件最迟开始时间(取Min);

              e(i) —— 活动最早开始时间;   l(i) —— 活动最迟开始时间;

           最后:e(i) = ve(i);l(i) = vl(i) - w;若l(i) - e(i) = 0,则为关键活动。

例题

 【注意】

        1)可以通过加快关键活动缩短工期,但不能任意缩短

        2)关键路径不唯一,对于有多个关键路径的网(如例题),缩短工期必须缩短包含在所有关键路径上的活动(如例题中的a4)

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


学习编程是顺着互联网的发展潮流,是一件好事。新手如何学习编程?其实不难,不过在学习编程之前你得先了解你的目的是什么?这个很重要,因为目的决定你的发展方向、决定你的发展速度。
IT行业是什么工作做什么?IT行业的工作有:产品策划类、页面设计类、前端与移动、开发与测试、营销推广类、数据运营类、运营维护类、游戏相关类等,根据不同的分类下面有细分了不同的岗位。
女生学Java好就业吗?女生适合学Java编程吗?目前有不少女生学习Java开发,但要结合自身的情况,先了解自己适不适合去学习Java,不要盲目的选择不适合自己的Java培训班进行学习。只要肯下功夫钻研,多看、多想、多练
Can’t connect to local MySQL server through socket \'/var/lib/mysql/mysql.sock问题 1.进入mysql路径
oracle基本命令 一、登录操作 1.管理员登录 # 管理员登录 sqlplus / as sysdba 2.普通用户登录
一、背景 因为项目中需要通北京网络,所以需要连vpn,但是服务器有时候会断掉,所以写个shell脚本每五分钟去判断是否连接,于是就有下面的shell脚本。
BETWEEN 操作符选取介于两个值之间的数据范围内的值。这些值可以是数值、文本或者日期。
假如你已经使用过苹果开发者中心上架app,你肯定知道在苹果开发者中心的web界面,无法直接提交ipa文件,而是需要使用第三方工具,将ipa文件上传到构建版本,开...
下面的 SQL 语句指定了两个别名,一个是 name 列的别名,一个是 country 列的别名。**提示:**如果列名称包含空格,要求使用双引号或方括号:
在使用H5混合开发的app打包后,需要将ipa文件上传到appstore进行发布,就需要去苹果开发者中心进行发布。​
+----+--------------+---------------------------+-------+---------+
数组的声明并不是声明一个个单独的变量,比如 number0、number1、...、number99,而是声明一个数组变量,比如 numbers,然后使用 nu...
第一步:到appuploader官网下载辅助工具和iCloud驱动,使用前面创建的AppID登录。
如需删除表中的列,请使用下面的语法(请注意,某些数据库系统不允许这种在数据库表中删除列的方式):
前不久在制作win11pe,制作了一版,1.26GB,太大了,不满意,想再裁剪下,发现这次dism mount正常,commit或discard巨慢,以前都很快...
赛门铁克各个版本概览:https://knowledge.broadcom.com/external/article?legacyId=tech163829
实测Python 3.6.6用pip 21.3.1,再高就报错了,Python 3.10.7用pip 22.3.1是可以的
Broadcom Corporation (博通公司,股票代号AVGO)是全球领先的有线和无线通信半导体公司。其产品实现向家庭、 办公室和移动环境以及在这些环境...
发现个问题,server2016上安装了c4d这些版本,低版本的正常显示窗格,但红色圈出的高版本c4d打开后不显示窗格,
TAT:https://cloud.tencent.com/document/product/1340