【算法与数据结构】图

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)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值