第六章 图

第六章 图

6.1图的定义和基本术语

6.1.1图的定义

图(Graph) G由两个集合V和E组成,记为G=(V,E) , 其中V是顶点的有穷非空集合
E是V中顶点偶对的有穷集合,这些顶点偶对称为边。V(G)和E(G)通常分别表示图G的顶点集合和边集合,E(G)可以为空集。若E(G)为空,则图G只有顶点而没有边。

V→点,E→边

有向图:<x,y>

无向图:(x,y),和离散数学中的图的概念很相似

6.1.2图的基本术语

和离散数学中图的概念非常非常相似

  1. 子图:点是原图的子集且边是原图的子集,那个图称为原来那个图的子图
  2. 无向完全图,有向完全图:无向图具有n(n-1)/2条边就是无向完全图,有向图具有n(n-1)条弧就是有向完全图,,,,,各顶点都有相连接的边
  3. 稀疏图和稠密图:有很少条边或弧的图称作稀疏图反之为稠密图
  4. 权和网:实际运用中,每条边可以标上具有某种含义的数值,该数值称为该边上的权,这些权可以表示从一个顶点到另一个顶点的距离或耗费。这种带权的图通常称为网。
  5. 邻接点:边上连的两个点就是邻接点,或者说边与这两个点相关联
  6. 度,入度和出度:顶知的度是指和v相关联的边的数目,入度是以顶点v为头的弧的数目; 出度是以顶点 v 为尾的弧的数目
  7. 路径和路径长度:路径是顶点序列,若是有向图,则路径是有向的,路径长度是路径上边或者是弧的数目
  8. 回路或环:第一个顶点和最后一个顶点相同的路径称为回路或环。
  9. 简单路径、 简单回路或简单环:序列中顶点不重复出现的路径称为简单路径。除了第一个顶点和最后一个顶点之外,其余顶点不重复出现的回路,称为简单回路或简单环。
  10. 连通、连通图和连通分量:顶点V到顶点V1有路径就连通,任意顶点都连通就叫做连通图,所谓连通分量, 指的是无向图中的极大连通子图。
  11. 强连通图和强连通分量:在有向图 G 中,如果对于每一对V,V1,有V→V1,V1→V都连通,就叫做强连通图,有向图中的极大强连通子图称作有向图的强连通分量。
  12. 连通图的生成树:一个极小连通子图,它含有图中全部顶点,但只有足以构成一棵树的 n-1条边,这样的连通子图称为连通图的生成树
  13. 有向树和生成树林:有一个顶点的入度为 0, 其余顶点的入度均为 l 的有向图称为有向树。 一个有向图的生成森林是由若干棵有向树组成,含有图中全部顶点,但只有足以构成若干棵不相交的有向树的弧。

6.3图的类型定义

图的抽象数据类型定义

6.4图的存储结构

6.4.1邻接矩阵(表示顶点之间关系)

邻接矩阵表示法

上图中的邻接矩阵如下图

下图是网的邻接矩阵

  • 其中∞表示计算机允许的大于所有边上权值的数

图的邻接矩阵存储表示

#define MaxInt 32767//计算机表示的权值极大值∞
#define MVNum 100//最大顶点数
typedef char VerTexType;//顶点数据类型
typedef int ArcType;边的权值
typedef struct
{
    VerTexType vexs[MVNum];//顶点表
    ArcType arcs[MVNum][MVNum];//邻接矩阵
    int vexnum,arcnum;//图的当前点数和边数
}AMGraph;

采用邻接矩阵创建无向网

#include<iostream>
using namespace std;
#define MaxInt 32767//计算机表示的权值极大值∞
#define MVNum 100//最大顶点数
#define OK "OK"
#define ERROR "ERROR"
typedef string Status;
typedef char VerTexType;//顶点数据类型
typedef int ArcType; //边的权值
ArcType w;//权值
VerTexType v1, v2;//边的邻接点
typedef struct
{
    VerTexType vexs[MVNum];//顶点表
    ArcType arcs[MVNum][MVNum];//邻接矩阵
    int vexnum, arcnum;//图的当前点数和边数
}AMGraph;
ArcType LocateVex(AMGraph G, VerTexType v)//查找点在顶点表的位置下标
{
    for (int i = 0; i < G.vexnum; i++)
    {
        if (v == G.vexs[i])
            return i;
    }
    return 0;
}
Status CreateUDN(AMGraph& G)//邻接矩阵表示法创建无向网
{
    cin >> G.vexnum >> G.arcnum;//总顶点数总边数
    for (int i = 0; i < G.vexnum; i++)//输入点的信息
    {
        cin >> G.vexs[i];
    }
    for (int i = 0; i < G.vexnum; i++)//初始化矩阵,权值设置为最大值
    {
        for (int j = 0; j < G.vexnum; j++)
        {
            G.arcs[i][j] = MaxInt;
        }
    }
    for (int k = 0; k < G.arcnum; k++)//构造矩阵
    {
        cin >> v1 >> v2 >> w;//输入边的信息
        int i = LocateVex(G, v1);//查找点在顶点表的下标位置
        int j = LocateVex(G, v2);
        G.arcs[i][j] = w;//设置边的权值
        G.arcs[j][i] = G.arcs[i][j];//无向图是对称矩阵
    }
    return OK;
}
int main()
{
    AMGraph G;
    CreateUDN(G);
    return 0;
}
//代码未测试

时间复杂度:O(n²)

邻接矩阵表示法的优缺点

  1. 优点:便于判断是否有边,便于计算边的度
  2. 缺点:空间复杂度高,特别是稀疏图,比较浪费空间,不便于增加删除顶点,不便于统计边的数目

6.4.2邻接表

邻接表表示法

邻接表是图的一种链式存储结构,在邻接表中,对图中每个顶点V建立一个单链表,把与 V相邻接的顶点放在这个链表中。邻接表中每个单链表的第一个结点存放有关顶点的信息, 把这一结点看成链表的表头, 其余结点存放有关边的信息, 这样邻接表便由两部分组成:表头结点表和边表。

表头节点表:

由所有表头结点以顺序结构的形式存储, 以便可以随机访问任一顶点的边链表。表头结点包括数据域 (data) 和链域 (firstarc) 两部分, 如图所示。其中, 数据域用于存储顶点 V的名称或其他有关信息;链域用千指向链表中第一个结点(即与顶点 V邻接的第一个邻接点)。

边表:

由表示图中顶点间关系的 2n个边链表组成。 边链表中边结点包括邻接点域(adjvex)、数据域 (info) 和链域 (nextarc) 三部分, 如图6.11(b)所示。其中, 邻接点域指示与顶点 V邻接的点在图中的位置;数据域存储和边相关的信息, 如权值等;链域指示与顶点V,邻接的下一条边的结点。

下图是图6.1的邻接表,图中用下标表示顶点的位置

逆邻接表:存放进入边的表,用于确定入度

图的邻接表存储表示

#define MVNunm 100//最大顶点
typedef struct ArcNode//边结点,存储的其实也是一个顶点
{
    int adjvex;//邻接点域,指向顶点的位置,也就是下标值
    struct ArcNode *nextarc;//下一条边
    OtherInfo info;//边信息
}ArcNode;
typedef struct VNode//顶点信息
{
    VerTexType data;
    ArcNode *firstarc;//指向第一条依附于该点的指针
}VNode,AdjList[MVNum];
typedef struct//邻接表
{
    AdjList vertices;
    int vexnum,arcnum;//图的当前顶点数和边数
}ALGraph;

邻接表表示法创建无向图

#include<iostream>
using namespace std;
#define MaxInt 32767//计算机表示的权值极大值∞
#define MVNum 100//最大顶点数
#define OK "OK"
#define ERROR "ERROR"
typedef string Status;
typedef char VerTexType;//顶点数据类型
typedef int ArcType; //边的权值
#define MVNunm 100//最大顶点
VerTexType v1, v2;
typedef struct ArcNode//边结点,存储的其实也是一个顶点
{
    int adjvex;//邻接点域,指向顶点的位置,也就是下标值
    struct ArcNode* nextarc;//下一条边
//    OtherInfo info;//边信息
}ArcNode;
typedef struct VNode//顶点信息
{
    VerTexType data; 
    ArcNode* firstarc;//指向第一条依附于该点的指针
}VNode, AdjList[MVNum];
typedef struct//邻接表
{
    AdjList vertices;
    int vexnum, arcnum;//图的当前顶点数和边数
}ALGraph;
ArcType LocateVex(ALGraph G, VerTexType v)//查找顶点对于数组下标函数
{
    for (int i = 0; i < G.vexnum; i++)
    {
        if (v == G.vertices[i].data)
            return i;
    }
    return 0;
}
Status CreateUDG(ALGraph& G)
{
    cin >> G.vexnum >> G.arcnum;//总顶点数边数
    for (int i = 0; i < G.vexnum; i++)//构造头结点表
    {
        cin >> G.vertices[i].data;
        G.vertices[i].firstarc = nullptr;
    }
    for (int k = 0; k < G.arcnum; k++)//构造邻接表
    {
        cin >> v1 >> v2;//边依附的两个点
        int i = LocateVex(G, v1);//查找点的下标
        int j = LocateVex(G, v2);
        auto p1 = new ArcNode;//生成新的边结点
        p1->adjvex = j;
        p1->nextarc = G.vertices[i].firstarc;
        G.vertices[i].firstarc = p1;//将新结点*p1插入顶点v的边表头部
        auto p2 = new ArcNode;
        p2->adjvex = i;
        p2->nextarc = G.vertices[j].firstarc;
        G.vertices[j].firstarc = p2;//将新结点*p2插入顶点v的边表头部
    }
    return OK;
}
int main()
{
    ALGraph G;
    CreateUDG(G);
    return 0;
}

邻接表表示法的优缺点

  1. 优点:便于增加和删除结点,便于统计边的数目,空间效率高
  2. 缺点:不便于判断顶点之间是否有边,不便于计算有向图各个顶点的度

6.4.3十字链表

十字链表 (Orthogonal List) 是有向图的另一种链式存储结构。可以看成是将有向图的邻接表和逆邻接表结合起来得到的一种链表。 在十字链表中,对应于有向图中每一条弧有一个结点,对应千每个顶点也有一个结点。这些结点的结构如图6.13所示。

弧结点:在弧结点中有 5 个域:其中尾域 (tailvex) 和头域 (headvex) 分别指示弧尾和弧头这两个顶点在图中的位置,链域 hlink 指向弧头相同的下一条弧,而链域 tlink 指向弧尾相同的下一条弧,info域指向该弧的相关信息。弧头相同的弧在同一链表上,弧尾相同的弧也在同一链表上。

头结点:它们的头结点即为顶点结点,它由3个域组成:其中 data 域存储和顶点相关的信息,如顶点的名称等; firstin 和 firstout为两个链域,分别指向以该顶点为弧头或弧尾的第一个弧结点。

有向图的十字链表存储结构表示

#define MAX_VERTEX_NUM 20
typedef struct ArcBox
{
    int tailvex,headvex;//弧的尾头顶点的位置
    struct ArcBox *hlink,*tlink;//弧头相同和弧尾相同的弧的链域
    InfoType *info;//弧信息相关的指针
}ArcBox;
typedef struct VexNode
{
    VertexType data;
    ArcBox *firstin,*firstout;//分别指向该顶点第一条入弧和出弧
}VexNode;
typedef struct
{
    VexNode xlist[MAX_VERTEX_NUM];//表头向量
    int vexnum,arcnum;//有向图当前的顶点数和弧数
}OLGraph;

6.4.4 邻接多重表

邻接多重表 (Adjacency Multilist) 是无向图的另一种链式存储结构。

相比邻接表的优势:

虽然邻接表是无向图的一种很有效的存储结构,在邻接表中容易求得顶点和边的各种信息。但是,在邻接表中每一条边(V, Vj )有两个结点,分别在第i个和第丿个链表中,这给某些图的操作带来不便。例如在某些图的应用问题中需要对边进行某种操作,如对已被搜索过的边作记号或删除一条边等,此时需要找到表示同一条边的两个结点。 因此,在进行这一类操作的无向图的问题中采用邻接多重表作存储结构更为适宜

邻接多重表的特点:

邻接多重表的结构和十字链表类似。在邻接多重表中,每一条边用一个结点表示,它由如图所示的6个域组成。其中,mark 为标志域,可用以标记该条边是否被搜索过;ivex 和 jvex为该边依附的两个顶点在图中的位置; ilink 指向下一条依附千顶点 ivex 的边; jlink 指向下一条依附千顶点jvex的边,info为指向和边相关的各种信息的指针域。每一个顶点也用一个结点表示,它由如图所示的两个域组成。其中, data域存储和该顶点相关的信息,firstedge域指示第一条依附于该顶点的边。例如,图 6.16所示为无向图G2的邻接多重表。在邻接多重表中,所有依附千同一顶点的边串联在同一链表中,由于每条边依附千两个顶点,则每个边结点同时链接在两个链表中。可见,对无向图而言,其邻接多重表和邻接表的差别,仅仅在千同一条边在邻接表中用两个结点表示,而在邻接多重表中只有一个结点。因此,除了在边结点中增加一个标志域外,邻接多重表所需的存储量和邻接表相同。

无向图的邻接多重表存储表示

#define MAX_VERTEX_NUM 20
typedef enum{unvisited,visited} VisitIf;
typedef struct EBox
{
    VisitIf mark;//访问标记
    int ivex,jvex;//边依附的两个顶点
    struct EBox *ilink,*jlink;//依附这两个顶点的下一条边
    InfoType *info;//边的信息指针
}EBox;
typedef struct VexBox
{
    VertexType data;
    EBox *firstedge;//指向第一条依附该顶点的边
}VexBox;
typedef struct
{
    VexBox adjmulist[MAX_VERTEX_NUM];
    int vexnum,edgenum;//无向图当前的顶点数和边数
}AMLGraph;

6.5图的遍历

和树的遍历类似,图的遍历也是从某一顶点出发,按照某种方法对所有顶点有且访问一次

通常有两种遍历图的路径,深度优先搜索和广度优先搜索

6.5.1深度优先搜索

深度优先搜索遍历的过程

深度优先搜索(DepthFirst Search, DFS)遍历类似千树的先序遍历,是树的先序遍历的推广。

深度优先搜索的搜索遍历过程如下:

  1. 从图中某个顶点v出发, 访问v。
  2. 找出刚访问过的顶点的第一个未被访问的邻接点, 访问该顶点。 以该顶点为新顶点,重复此步骤, 直至刚访问过的顶点没有未被访问的邻接点为止。
  3. 返回前一个访问过的且仍有未被访问的邻接点的顶点,找出该顶点的下一个未被访问的邻接点, 访问该顶点。
  4. 重复步骤 (2) 和(3), 直至图中所有顶点都被访问过,搜索结束。

深度优先搜索的算法实现

深度优先搜索遍历连通图是一个递归的过程。 为了在遍历过程中便千区分顶点是否已
被访问,需附设访问标志数组 visited[n] , 其初值为 "false", 一旦某个顶点被访问,则其相应的分量置为 "true"

深度优先搜索遍历连通图

bool visited[MVNum];//标志数组
void DFS(Graph G,int v)//从v开始递归遍历
{
    cout << v;
    visited[v]=true;//访问标志为真
    for(int w=FirstAdjVeex(G,v);w>0;w>=NextAdjVex(G,v,w))//检查v的所有邻接点
    {
        if(!visited[w])
        DFS(G,w);//对v的尚未访问的邻接顶点递归调用DFS
    }
}

深度优先搜索遍历非连通图

void DFSTraverse(Graph G)
{
    for(int v=0;v<G.vexnum;v++)//访问标志数组初始化
    visited[v]=false;//访问标志改变
    for(int v=0;v<G.vexnum;v++)循环调用算法
    {
        if(!visited[v])
        DFS(G,v);//对尚未访问的顶点调用DFS
    }
}

采用邻接矩阵表示图深度优先搜索遍历

void DFS_AM(AMGraph G,int v)//G为邻接矩阵类型
{
    cout << v;
    visited[v]=true;//访问结点
    for(int w=0;w<vexnum;w++)//一次检查邻接矩阵所在行
    {
        if((G.arcs[v][w]!=0)&&(visited[w]))
        DFS(G,w);//表示w是v的邻接点,w未访问则递归调用DFS
    }
}

采用邻接表表示图的深度优先搜索遍历

void DFS_AL(ALGraph G,int v)
{
    cout << v;
    visited[v]=true;//访问v
    p=G.vertices[v].firstarc;//p指向v的边链表的第一个边结点
    while(p!=NULL)//边结点非空
    {
        w=p->adjvex;//表示w是v的邻接点
        if(!visited[w])//如果w未被访问
        DFS(G,w);
        p=p->nextarc;//p指向下一结点
    }
}

时间复杂度分析

当用邻接矩阵表示图时,查找每个顶点的邻接点的时间复杂度为 O(n2), 其中 n为图中顶点数。而当以邻接表做图的存储结构时,查找邻接点的时间复杂度为O(e), 其中e为图中边数。由此, 当以邻接表做存储结构时,深度优先搜索遍历图的时间复杂度为 O(n + e)。

6.5.2广度优先搜索

广度优先搜索遍历的过程

广度优先搜索(Breadth First Search, BFS)遍历类似于树的按层次遍历的过程。

  1. 从图中某个顶点出发,访问v
  2. 依次访问v未被访问的邻接点
  3. 分别从这些邻接点出发依次访问他们的邻接点并使 “先被访问的顶点的邻接点“ 先于”后被访问的顶点的邻接点” 被访问。重复步骤(3), 直至图中所有已被访问的顶点的邻接点都被访间到。

广度优先搜索遍历的算法实现

广度优先搜索遍历的特点是:尽可能先对横向进行搜索。设 x 和y是两个相继被
访间过的顶点,若当前是以x为出发点进行搜索,则在访问x的所有未曾被访问过的邻接点之后,紧接着是以y为出发点进行横向搜索,并对搜索到的y的邻接点中尚未被访问的顶点进行访问。也就是说, 先访问的顶点其邻接点亦先被访问。为此,算法实现时需引进队列保存已被访问过的顶点。

广度优先搜索遍历连通图

void BFS(Graph G,int v)
{
    cout << v;
    visited[v]=true;
    InitQueue(Q);//队列Q初始化,置空
    EnQueue(Q,v);//v进队
    while(!QueueEmpty(Q))//队列非空
    {
        DeQueue(Q,u);//队头元素出队并且置u
        for(int w=FirstAdjVex(G,u);w>=0;w=NextAdjVex(G,u,w))//依次检查u的所有邻接点
        if(!visited(w))//w为u未被访问的邻接结点
        {
            cout << w;
            visited[w]=true;
            EnQueue(Q,w);//w进队
        }
    }
}

算法分析

每个顶点至多进一次队列。遍历图的过程实质上是通过边找邻接点的过程,
因此广度优先搜索遍历图的时间复杂度和深度优先搜索遍历相同,即当用邻接矩阵存储时,时间复杂度为O(n2); 用邻接表存储时,时间复杂度为O(n+ e)。两种遍历方法的不同之处仅仅在于对顶点访问的顺序不同。

6.6图的应用

6.6.1最小生成树

定义:在一个连通网的所有生成树中,各边的代价之和最小的那棵生成树称为该连通网的最小代价生成树 (Minimum Cost Spanning Tree), 简称为最小生成树。

最小生成树有一个叫做MST的性质,用来构造最小生成树,这里不再赘述

普里姆 (Prim) 算法和克鲁斯卡尔 (Kruskal) 算法是两个利用 MST 性质构造最小生成树的算法。

普里姆算法

struct
{
    VerTexType adjvex;//最小边在U中的那个顶点
    ArcType lowcost;//最小边上面的权值
    
}closedge[MVNum];

普里姆算法实现

void MiniSpanTree_Prim(AMGraph G,VerTexType u) 
{//无向网G以邻接矩阵形式存储, 从顶点u出发构造G的最小生成树T, 输出T的各条边
    k=LocateVex(G,u); Ilk 为顶点 u 的下标
    for(j=O;j<G.vexnum;++j) //对v-u 的每一个顶点 Vj, 初始化 closedge[j]
        if(j!=k) closedge[j]={u,G.arcs[k][j]}; //{adjvex, lowcost} 
    closedge[k].lowcost=O; //初始, U={u}
    for(i=l;i<G.vexnum;++i) 
    {//选择其余 n-1 个顶点,生成 n-1 条边(n=G.vexnum)
        k=Min(closedge); 
        //求出 T 的下一个结点:第 K 个顶点, closedge[k]中存有当前最小边
        uO=closedge[k].adjvex; 
        vO=G.vexs[k]; 
        cout<<uO<<vO; //输出当前的最小边(uO, vO) 
        closedge[k] .lowcost=O; //第k个顶点并入u集
        for(j=O;j<G.vexnum;++j) 
            if(G.arcs[k] [j]<closedge[j].lowcost) //新顶点并入u 后重新选择最小边
                closedge [j l={G.vexs[k],G.arcs[k][j]}; 
    }
}

普里姆算法的时间复杂度为O(n²),与网中的边数无关。适用于稠密网的最小生成树

克鲁斯卡尔算法

可以看出,克鲁斯卡尔算法逐步增加生成树的边,与普里姆算法相比,可称为“加边法”与普里姆算法一样, 每次选择最小边时, 可能有多条同样权值的边可选, 可以任选其一。

算法实现

//辅助数组Edges的定义
struct
{
    VeraTexType Head;//边的始点
    VeraTexType Tail;//边的终点
    ArcType lowcost;//边上的权值
}Edge[arcnum];
//辅助数组Vexset定义
int Vexset[MVNum];
void MiniSpanTree_ Kruskal(AMGraph G) 
{//无向网G以邻接矩阵形式存储,构造G的最小生成树T, 输出T的各条边
    Sort (Edge); //将数组 Edge 中的元素按权值从小到大排序
    for(i=O;i<G.vexnum;++i) //辅助数组,表示各顶点自成一个连通分址
        Vexset[i]=i; 
    for(i=O;i<G.arcnum;++i)//依次查看数组 Edge 中的边
    { 
        vl=LocateVex(G,Edge[i] .Head); //vl 为边的始点 Head 的下标
        v2=LocateVex(G,Edge[i] .Tail); //v2 为边的终点 Ta过的下标
        vsl=Vexset[vl]; //获取边 Edge[i]的始点所在的连通分量 vsl
        vs2=Vexset[v2]; //获取边 Edge[i]的终点所在的连通分量 vs2
        if(vsl!=vs2) //边的两个顶点分属不同的连通分量
        {
            cou<<Edge[i] .Head<<Edge[i] .Tail;//输出此边
            for(j=O;j<G.vexnurn;++j) //合并 VS1 和 VS2 两个分益, 即两个集合统一编号
            if(Vexset[j]==s2) Vexset[j]=vsl; //集合编号为 vs2 的都改为 vsl
        }
    }
}

6.6.2最短路径

在带权的有向网中,习惯上称路径上的第一个顶点为源点,最后一个顶点为终点

从某个源点到其余各顶点的最短路径

迪杰斯特拉算法步骤

迪杰斯特拉算法实现

void ShortestPath_DIJ(AMGraph G, int v0)//用Dijkstra算法求有向网G的vO顶点到其余顶点的最短路径
{
    int n = G.vexnum;//n为G 中顶点的个数
    for (int v = 0; v < n; v++)//初始化
    {
        S[v] = false;
        D[v] = G.arcs[v0][v];///将vO到各个终点的最短路径长度初始化为弧上的权值
        if(D(v)<MaxInt) Path[v]=v0;//如果vO和v之间有弧, 则将v的前驱置为vO
        else Path[v] = -1;//如果vO 和v之间无弧, 则将v的前驱置为一1
    }
    S[v0] = true;//将vO加人 S
    D[v0] = 0;//源点到源点的距离为 0
    /*初始化结束, 开始主循环, 每次求得vO到某个顶点v的最短路径, 将v加到s集*/
    for (int i = 0; i < n; i++)//对其余 n-1个顶点,依次进行计算
    {
        min = MaxInt;
        for (w = 0; w < n; w++)
            if (!S[w] && D[w] < min)
            {
                v = w;
                min = D[w];//选择一条当前的最短路径,终点为v
            }
        S[v] = true;//将v加入S
        for (int w = 0; w < n; w++)
        {
            if (!S[w] && (D[v] + G.arcs[v][w] < D[w]))
            {
                D[w] = D[v] + G.arcs[v][w];//更新
                Path[w] = v;
            }
        }
    }
}

每一对顶点之间的最短路径

弗洛伊德算法步骤

弗洛伊德算法实现

void ShortestPath_Floyd(AMGraph G)
{
    for(int i=0;i<G.vexnum;i++)//各对结点之间初始已知路径及距离
        for (int j = 0; j < G.vexnum; j++)
        {
            D[i][j] = G.arcs[i][j];
            if (D[i][j] < MaxInt) Path[i][j] = i;//如果 l.和]之间有弧,则将j的前驱置为l
            else Path[i][j] = -1;//如果 l.和]之间无弧,则将j的前驱置为-1
        }
    for(int k=0;k<G.vexnum;k++)
        for(int i=0;i< G.vexnum; i++)
            for (int j = 0; j < G.vexnum; j++)
            {
                if (D[i][j] + D[k][j] < D[i][j])//从1经k到]的一条路径更短
                {
                    D[i][j] = D[i][k] + D[k][j];//更新D[i) [j J
                    Path[i][j] = Path[k][j];//更改]的前驱为K
                }
            }
}

6.6.3拓扑排序

一个无环的有向图称作有向无环图( DirectedAcycline Graph), 简称DAG图。有向无环图是描述一项工程或系统的进行过程的有效工具。

拓扑排序的实现

算法的实现需要引入一下辅助的数据结构

  1. 一维数组 indegree[i]: 存放各顶点入度,没有前驱的顶点就是入度为零的顶点。
  2. 栈S: 暂存所有入度为零的顶点
  3. 一维数组topo(i]: 记录拓扑序列的顶点序号。

算法步骤

算法实现

Status TopologicalSort(ALGraph G, int topo[])
{//若 G 无回路,则生成 G 的一个拓扑序列 topo []并返回 OK, 否则 ERROR
    FindinDegree(G, indegree);//求出各顶点的入度存入数组 indegree中
    InitStack(S);//栈 s初始化为空
    for (i = O; i < G.vexnum; ++i)
        if (!indegree[i)) Push(S, i);//入度为0者进栈
    m = O;
    while (!StackEmpty(S))//栈s非空
    {
        Pop(S, i);//将栈顶顶点Vi出栈
        topo[m) = i;//将Vi保存在拓扑序列数组 topo中
        ++m;//对输出顶点计数
        p = G.vertices[i).firstarc;//p指向Vi的第一个邻接点
        while (p != NULL)
        {
            p->adjvex;
            --indegree[k);
            if (indegree[k) == O) Push(S, k);
            p = p->nextarc;
        }
    }
if (m < G.vexnum) return ERROR;//该有向图有回路
else return OK;
}

6.6.4关键路径

与AOV-网相对应的是AOE-网 (Activity On Edge) , 即以边表示活动的网。 AOE-网是一个带权的有向无环图, 其中, 顶点表示事件, 弧表示活动, 权表示活动持续的时间。

关键路径算法实现

算法的实现需要引入一下辅助的数据结构

  1. 一维数组 ve[i]: 事件 V; 的最早发生时间。
  2. 一维数组 vl[i]: 事件 V;的最迟发生时间。
  3. 一维数组 topo[i]: 记录拓扑序列的顶点序号。

算法步骤

算法实现

Status CriticalPath(ALGraph G)
{
    if (!TopologicalOrder(G, topo)) return ERROR;//调用拓扑排序算法,使拓扑序列保存在topo中,若调用失败, 则存在有向环, 返回ERROR
    n = G.vexnum;//n为顶点个数
    for (int i = 0i < n; i++)//给每个事件的最早发生时间置初值0
    {
        ve[i] = 0;
    }
    /* 按拓扑次序求每个事件的最早发生时间*/
    for (int i = 0; i < n; i++)
    {
        k = topo[i];//取得拓扑序列中的顶点序号K
        p = G.vertices[k].firstarc;//p指向k的第一个邻接顶点
        while (p != NULL)//依次更新k的所有邻接顶点的最早发生时间
        {
            j = p->adjvex;
            if (ve[j] < ve[k] + p->weight)//更新顶点 J 的最早发生时间 ve[j]
                ve[j] = ve[k] + p->weight;
            p = p->nextarc;//p指向k的下一个邻接顶点
        }
    }
    for (i = O; i < n; i++)//给每个事件的最迟发生时间置初值 ve[n-1]
        vl[i] = ve[n - 1];
        /*按逆拓扑次序求每个事件的最迟发生时间*/
    for (int i = n - 1; i >= 0; i--)
    {
        k = topo[i];//取得拓扑序列中的顶点序号K
        p== G.vertices[k].firstarc;//p指向k的第一个邻接顶点
        while (p != NULL)//根据k的邻接点,更新k的最迟发生时间
        {
            j = p->adjvex;
            if(v1[k]>v1[j]-p->weight)//更新顶点 K 的最迟发生时间 vl [k]
                vl[k) = vl[j) - p->weight;
                p = p->nextarc;///p指向k的下一个邻接顶点
        }
    }
    /*判断每一活动是否为关键活动*/
    for (int i = 0; i < n; i++)
    {
        p= G.vertices[i].firstarc;//p指向i的下一个邻接顶点
        while (p != NULL)
        {
            j = p->adjvex;//j为i的邻接顶点的序号
            e = ve[i];//计算活动<Vi, Vj>的最早开始时
            l = vl[j] - p->weight;//计算活动<Vi, Vj>的最迟开始时间
            if(w==l)//若为关键活动,则输出<vi, Vj>
                cout << < G.vertices[i].data << G.vertices[j].data;
            p = p->nextarc;//p指向 i 的下一个邻接顶点
        }
    }
}
posted @   艾洋mhduiy  阅读(225)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
点击右上角即可分享
微信分享提示