数据结构(C++版)笔记整理————第六章

来源:互联网 发布:淘宝怎么找一件代发 编辑:程序博客网 时间:2024/06/13 09:39

6章 图

 

6.1 图的逻辑结构

 

6.1.1 图的定义和基本用语

在树中常将数据元素称为顶点。

1.定义

★图是由顶点的有穷非空集合和顶点之间边的集合组成,通常表示为:G=(VE)。其中:G表示一个图,V是图G中顶点的集合,E是图G中顶点之间边的集合。 

★注意:在线性表中,元素个数可以为零,称为空表;

      在树中,结点个数可以为零,称为空树;

      在图中,顶点个数不能为零,但可以没有边。

★若顶点vivj之间的边没有方向,则称这条边为无向边,表示为(vi,vj);如果图的任意两个顶点之间的边都是无向边,则称该图为无向图。

★若从顶点vivj的边有方向,则称这条边为有向边,表示为<vi,vj>;如果图的任意两个顶点之间的边都是有向边,则称该图为有向图。

2.基本术语

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

●邻接、依附

无向图中,对于任意两个顶点vi和顶点vj,若存在边(vivj),则称顶点vi和顶点vj互为邻接点,同时称边(vivj)依附于顶点vi和顶点vj

有向图中,对于任意两个顶点vi和顶点vj,若存在弧<vivj>,则称顶点vi邻接到顶点vj,顶点vj邻接自顶点vi,同时称弧<vivj>依附于顶点vi和顶点vj 

●逻辑关系的对比:

(1)在线性结构中,数据元素之间仅具有线性关系;

     在树结构中,结点之间具有层次关系;

     在图结构中,任意两个顶点之间都可能有关系。 

(2)在线性结构中,元素之间的关系为前驱和后继;

    在树结构中,结点之间的关系为双亲和孩子;

在图结构中,顶点之间的关系为邻接。 

●无向完全图:在无向图中,如果任意两个顶点之间都存在边,则称该图为无向完全图。

  有向完全图:在有向图中,如果任意两个顶点之间都存在方向相反的两条弧,则称该图为有向完全图。  

含有n个顶点的无向完全图有n×(n-1)/2条边。 

含有n个顶点的有向完全图有n×(n-1)条边。 

●稀疏图:称边数很少的图为稀疏图;

  稠密图:称边数很多的图为稠密图。

●顶点的度:在无向图中,顶点v的度是指依附于该顶点的边数,通常记为TD (v)

  在具有n个顶点e条边的无向图中,所有顶点的度之和为:2e

  顶点的入度:在有向图中,顶点v的入度是指以该顶点为弧头的弧的数目,记为ID (v)

  顶点的出度:在有向图中,顶点v的出度是指以该顶点为弧尾的弧的数目,记为OD (v)

  在具有n个顶点e条边的有向图中,所有顶点的入度之和与出度之和都等于e

●权:是指对边赋予的有意义的数值量。

  网:边上带权的图,也称网图。

●路径:在无向图G=(V, E)中,从顶点vp到顶点vq之间的路径是一个顶点序列(vp=vi0,vi1,vi2, , vim=vq),其中,(vij-1,vij)E1jm)。若G是有向图,则路径也是有方向的,顶点序列满足<vij-1,vij>E

路径长度:

         非带权图——路径上边的个数

         带权图——路径上各边的权之和

●回路(环):第一个顶点和最后一个顶点相同的路径。

  简单路径:序列中顶点不重复出现的路径。

  简单回路(简单环):除了第一个顶点和最后一个顶点外,其余顶点不重复出现的回路。

 

●子图:若图G=VE),G'=V'E'),如果V'ÍE' Í ,则称图G'G的子图。

●连通图:在无向图中,如果从一个顶点vi到另一个顶点vj(ij)有路径,则称顶点vivj是连通的。如果图中任意两个顶点都是连通的,则称该图是连通图。

  连通分量:非连通图的极大连通子图称为连通分量。

●强连通图:在有向图中,对图中任意一对顶点vivj (ij),若从顶点vi到顶点vj和从顶点vj到顶点vi均有路径,则称该有向图是强连通图。

  强连通分量:非强连通图的极大强连通子图。

●生成树:n个顶点的连通图G的生成树是包含G中全部顶点的一个极小连通子图。

  生成森林:在非连通图中,由每个连通分量都可以得到一棵生成树,这些连通分量的生成树就组成了一个非连通图的生成森林。 

 

6.1.2 图的抽象数据类型定义

ADT  Graph

Data

    顶点的有穷非空集合和边的集合

Operation

 InitGraph

     前置条件:图不存在

     输入:无 

     功能:图的初始化

     输出:无

     后置条件:构造一个空的图

 DestroyGraph 

     前置条件:图已存在

     输入:无 

     功能:销毁图

     输出:无

     后置条件:释放图所占用的存储空间

  DFSTraverse

     前置条件:图已存在

     输入:遍历的起始顶点v

     功能:从顶点v出发深度优先遍历图

     输出:图中顶点的一个线性排列

     后置条件:图保持不变

  BFSTraverse

     前置条件:图已存在

     输入:遍历的起始顶点v

     功能:从顶点v出发广度优先遍历图

     输出:图中顶点的一个线性排列

     后置条件:图保持不变

endADT

 

6.1.3 图的遍历操作

图的遍历是在从图中某一顶点出发,对图中所有顶点访问一次且仅访问一次。 

※深度优先遍历 

◆基本思想 :

⑴ 访问顶点v

⑵ 从v的未被访问的邻接点中选取一个顶点w,从w出发进行深度优先遍历;

⑶ 重复上述两步,直至图中所有和v有路径相通的顶点都被访问到。

◆伪代码:

1. 访问顶点v; visited[v] = 1;

2. w =顶点v的第一个邻接点;

3. while (w存在

    3.1 if (w未被访问从顶点w出发递归执行该算法;

    3.2 w = 顶点v的下一个邻接点;

※广度优先遍历 

◆基本思想:

⑴ 访问顶点v

⑵ 依次访问v的各个未被访问的邻接点v1, v2, , vk

⑶ 分别从v1v2,…,vk出发依次访问它们未被访问的邻接点,并使“先被访问顶点的邻接点”先于“后被访问顶点的邻接点”被访问。直至图中所有与顶点v有路径相通的顶点都被访问到。

◆伪代码: 

1. 初始化队列Q;

2. 访问顶点v; visited [v]=1; 顶点v入队列Q;

3. while (队列Q非空

    3.1 v=队列Q的队头元素出队;

    3.2 w=顶点v的第一个邻接点;

    3.3 while (w存在

          3.3.1 如果未被访问,则

                   访问顶点w; visited[w]=1; 顶点w入队列Q

          3.3.2 w=顶点v的下一个邻接点;

 

6.2 图的存储结构及实现

 

6.2.1 邻接矩阵

▲基本思想:用一个一维数组存储图中顶点的信息,用一个二维数组(称为邻接矩阵)存储图中各顶点之间的邻接关系。

▲假设图G(VE)n个顶点,则邻接矩阵是一个n×n的方阵,那么:若(vi, vj)E(或<vi, vj>E),arc[i][j]=1;否则,arc[i][j]=0

▲对于无向图,主对角线为 且一定是对称矩阵;

  顶点i的度为邻接矩阵的第i行(或第i列)非零元素的个数;  

  判断顶点 和 之间是否存在边 :测试邻接矩阵中相应位置的元素arc[i][j]是否为1。            

  求顶点 的所有邻接点:将数组中第 行元素扫描一遍,若arc[i][j]1,则顶点 为顶点 的邻接点。

▲对于有向图:

  求顶点 的出度:邻接矩阵的第 行元素之和。

  求顶点 的入度:邻接矩阵的第 列元素之和。

  判断从顶点 到顶点 是否存在边:测试邻接矩阵中相应位置的元素arc[i][j]是否为1

▲网图的邻接矩阵可定义为: 

(vi, vj)E(或<vi, vj>E,arc[i][j]=wij;若i=j,w[i][j]=0;否则,w[i][j]=∞  

▲邻接矩阵的类的声明:

const int MaxSize=10; 

template <class DataType>

class Mgraph

{

   public:

      MGraph(DataType a[ ], int n, int e );   

       ~MGraph( )

       void DFSTraverse(int v); 

       void BFSTraverse(int v); 

   private:

       DataType vertex[MaxSize]; 

       int arc[MaxSize][MaxSize]; 

       int vertexNum, arcNum; 

};

▲邻接矩阵中图的基本操作

✪构造函数

◆伪代码:

1.确定图的顶点个数和边的个数;

2. 输入顶点信息存储在一维数组vertex中;

3. 初始化邻接矩阵;

4. 依次输入每条边存储在邻接矩阵arc中;

     4.1 输入边依附的两个顶点的序号i, j

     4.2 将邻接矩阵的第i行第j列的元素值置为1

     4.3 将邻接矩阵的第j行第i列的元素值置为1

◆算法MGraph

template <class DataType>

MGraph<DataType> :: MGraph(DataType a[ ], int n, int e) 

{

    vertexNum = n; arcNum = e;

    for (i = 0; i < vertexNum; i++) 

        vertex[i] = a[i];

    for (i = 0; i < vertexNum; i++)          //初始化邻接矩阵

        for (j = 0; j < vertexNum; j++)

            arc[i][j] = 0;             

    for (k = 0; k < arcNum; k++)           //依次输入每一条边

    {

        cin >> i >> j;                      //输入边依附的两个顶点的编号

        arc[i][j] = 1; arc[j][i] = 1;             //置有边标志

    }

}

✪深度优先遍历

◆算法DFSTraverse

template <class DataType>

void MGraph<DataType> :: DFSTraverse(int v)  

{

    cout << vertex[v]; visited[v] = 1;

    for (j = 0; j < vertexNum; j++)

        if (arc[v][j] == 1 && visited[j] == 0) DFSTraverse( j );

}

✪广度优先遍历

◆算法BFSTraverse

template <class DataType>

void MGraph<DataType> :: BFSTraverse(int v) 

{

    front = rear = -1;   //初始化顺序队列

    cout << vertex[v]; visited[v] = 1;  Q[++rear] = v; 

    while (front != rear)                   //当队列非空时

    {

         v = Q[++front];                   //将队头元素出队并送到v

        for (j = 0; j < vertexNum; j++)

            if (arc[v][j] == 1 && visited[j] == 0 ) {

                 cout << vertex[j]; visited[j] = 1; Q[++rear] = j;

            }

    }

}

 

6.2.2 邻接表

邻接表存储的基本思想:对于图的每个顶点vi,将所有邻接于vi的顶点链成一个单链表,称为顶点vi的边表(对于有向图则称为出边表),所有边表的头指针和存储顶点信息的一维数组构成了顶点表。 

◆邻接表有两种结点结构:顶点表结点和边表结点。

vertex:数据域,存放顶点信息。 

firstedge:指针域,指向边表中第一个结点。 

adjvex:邻接点域,边的终点在顶点表中的下标。

next:指针域,指向边表中的下一个结点。 

✪定义邻接表的结点

struct ArcNode

{   

    int adjvex; 

    ArcNode *next;

};

template <class DataType>

struct VertexNode 

{

    DataType vertex;

    ArcNode *firstedge;

};

✪每个结点对应图中的一条边,邻接表的空间复杂度为O(n+e)。

  求顶点 i 的度:顶点i的边表中结点的个数。

  判断顶点 i 和顶点 j之间是否存在边:测试顶点 i 的边表中是否存在终点为 j 的结点。

  求顶点 i 的出度:顶点 i 的出边表中结点的个数。

  求顶点 i 的入度:各顶点的出边表中以顶点 i 为终点的结点个数。

  求顶点 i 的所有邻接点:遍历顶点 i 的边表,该边表中的所有终点都是顶点 i 的邻接点。

✪类的声明:

const int MaxSize = 10;    //图的最大顶点数

template <class DataType>

class ALGraph

{    

   public:

       ALGraph(DataType a[ ], int n, int e);   

       ~ALGraph;    

       void DFSTraverse(int v);      

       void BFSTraverse(int v);      

  private:

       VertexNode adjlist[MaxSize];   

       int vertexNum, arcNum;       

};

★邻接表中图的基本操作 

●构造函数ALGraph

◆伪代码:

1. 确定图的顶点个数和边的个数;

2. 输入顶点信息,初始化该顶点的边表;

3. 依次输入边的信息并存储在边表中;

     3.1  输入边所依附的两个顶点的序号ij

     3.2  生成邻接点序号为j的边表结点s

   3.3 将结点s插入到第i个边表的头部;

●算法

template <class DataType>

ALGraph<DataType> :: ALGraph(DataType a[ ], int n, int e)

{

    vertexNum = n; arcNum = e;

    for (i = 0; i < vertexNum; i++)

   {                                             //输入顶点信息,初始化顶点表

        adjlist[i].vertex = a[i];

        adjlist[i].firstedge = NULL;      

}

 for (k = 0; k < arcNum; k++) //输入边的信息存储在边表中

    {

         cin>>i>>j;    

         s = new ArcNode; s->adjvex = j;           

         s->next = adjlist[i].firstedge;    

         adjlist[i].firstedge = s;

     }

}

◆深度优先遍历DFSTraverse

●算法

template <class DataType>

void ALGraph<DataType> :: DFSTraverse(int v)

{

    cout << adjlist[v].vertex;  visited[v] = 1;

    p = adjlist[v].firstedge;       //工作指针p指向顶点v的边表

    while (p != NULL)              //依次搜索顶点v的邻接点j

    {

        j = p->adjvex;

        if (visited[j] == 0) DFSTraverse(j);

        p = p->next;           

    }

}

◆广度优先遍历BFSTraverse

●算法

template <class DataType>

void ALGraph<DataType> :: BFSTraverse(int v) 

{

    front = rear = -1;   //初始化顺序队列

    cout << adjlist[v].vertex; visited[v] = 1; Q[++rear] = v;

    while (front != rear)           //当队列非空时

    {

        v = Q[++front];

        p = adjlist[v].firstarc;       //工作指针p指向顶点v的边表

        while (p != NULL) 

        {

            j = p->adjvex;

            if (visited[j] == 0) {

               cout << adjlist[j].vertex; visited[j] = 1;Q[++rear] = j;

           }

           p=p->next;

        }

    }

}

 

6.2.3十字链表

十字链表的结点结构:

vertex:数据域,存放顶点信息;

  firstin:入边表头指针;

  firstout:出边表头指针;

tailvex:弧的起点在顶点表中的下标;

  headvex:弧的终点在顶点表中的下标;

  headlink:入边表指针域;

  taillink:出边表指针域。

 

6.2.4 邻接多重表

邻接多重表主要用于存储无向图。

vertex:数据域,存储有关顶点的数据信息;

firstedge:边表头指针,指向依附于该顶点的边表;

ivexjvex:与某条边依附的两个顶点在顶点表中的下标;

ilink:指针域,指向依附于顶点ivex的下一条边;

jlink:指针域,指向依附于顶点jvex的下一条边。

 

6.3 最小生成树

  生成树的代价:设G = (V, E)是一个无向连通网,生成树上各边的权值之和称为该生成树的代价。 

  最小生成树:在图G所有生成树中,代价最小的生成树称为最小生成树。  

 

6.3.1 MST性质

假设G=(V, E)是一个无向连通网,U是顶点集V的一个非空子集。若(u, v)是一条具有最小权值的边,其中uUvVU,则必存在一棵包含边(u, v)的最小生成树。

 

6.3.2 Prim算法

✪基本思想:设G=(V, E)是具有n个顶点的连通网,T=(U, TE)G的最小生成树, T的初始状态为U={u0}u0V),TE={ },重复执行下述操作:在所有uUvV-U的边中找一条代价最小的边(u, v)并入集合TE,同时v并入U,直至U=V

✪基本思想的伪代码为:

1. 初始化:U = {v0}; TE={ }; 

2. 重复下述操作直到U = V: 

    2.1 在E中寻找最短边(u,v),且满足u∈U,v∈V-U;

    2.2 U = U + {v};

    2.3 TE = TE + {(u,v)};

✪Prim算法——伪代码 

1. 初始化两个辅助数组lowcostadjvex

2. 输出顶点u0,将顶点u0加入集合U中;

3. 重复执行下列操作n-1

   3.1 lowcost中选取最短边,取adjvex中对应的顶点序号k

   3.2 输出顶点k和对应的权值;

   3.3 将顶点k加入集合U中;

   3.4 调整数组lowcostadjvex

✪Prim算法

void Prim(MGraph G)

{

for(i=1;i<G.vertexNum;i++)

{

shortEdge[i].lowcast=G.arc[0][i];

shortEdge[i].adjvex=0;

}

shortEdge[0].lowcast=0;

for(i=1;i<G.vertexNum;i++)

{

k=MinEdge(shortEdge,G.vertexNum);

cout<<"("<<k<<shortEdge[k].adjvex<<")"<<shortEdge[k].lowcast;

shortEdge[k].lowcast=0;

for(j=1;j<G.vertexNum;j++)

if(G.arc[k][j]<shortEdge[j].lowcast)

{

shortEdge[j].lowcast=G.arc[k][j];

shortEdge[j].adjvex=k;

}

}

}

 

6.3.3 Kruskal算法

✪基本思想:设无向连通网为G=(V, E),令G的最小生成树为T=(U, TE),其初态为U=V,TE={ },然后,按照边的权值由小到大的顺序,考察G的边集E中的各条边。若被考察的边的两个顶点属于T的两个不同的连通分量,则将此边作为最小生成树的边加入到T中,同时把两个连通分量连接为一个连通分量;若被考察边的两个顶点属于同一个连通分量,则舍去此边,以免造成回路,如此下去,当T中的连通分量个数为1时,此连通分量便为G的一棵最小生成树。

✪Kruskal算法的基本思想用伪代码描述如下:

1. 初始化:U=V;TE={ }; 

2. 重复下述操作直到T中的连通分量个数为1:

    2.1 在E中寻找最短边(u,v);

    2.2 如果顶点u、v位于T的两个不同连通分量,则

          2.2.1 将边(u,v)并入TE;

          2.2.2 将这两个连通分量合为一个;

    2.3 标记边(u,v),使得(u,v)不参加后续最短边的选取;

✪Kruskal算法用伪代码进一步描述为:

1. 初始化辅助数组parent[n]num = 0

2. 依次考查每一条边for (i = 0; i < arcNum; i++)

    2.1 vex1 = edge[i].from所在生成树的根结点;

    2.2 vex2 = edge[i].to所在生成树的根结点;

    2.3 如果vex1 != vex2,执行下述操作:

          2.3.1 parent[vex2] = vex1;

          2.3.2 num++;

          2.3.3 if (num == n-1) 算法结束;

✪Kruskal算法

void Kruskal(EdgeGraph G)

{

for(i=0;i<G.vertexNum;i++)

parent[i]=-1;

for(num=0,i-0;i<G.edgeNum;i++)

{

vex1=FindRoot(parent,G.edge[i].from);

vex2=FindRoot(parent,G.edge[i].to);

if(vex1!=vex2)

{

cout<<"("<<G.edge[i].from<<G.edge[i].to<<")"<<endl;

parent[vex2]=vex1;

num++;

if(num==n-1) return;

}

}

}

 

int FindRoot(int parent[],int v)

{

t=v;

if(parent[t]>-1) t=parent;

return t;

}

 

6.4 最短路径

在网图中,最短路径是指两顶点之间经历的边上权值之和最短的路径。 

 

6.4.1 Dijkstra算法(单源点最短路径问题)

※问题描述:给定带权有向图G(V, E)和源点vV,求从vG中其余各顶点的最短路径。 

Dijkstra算法基本思想:

设置一个集合S存放已经找到最短路径的顶点,S的初始状态只包含源点v,对viV-S,假设从源点vvi的有向边为最短路径。以后每求得一条最短路径v, , vk,就将vk加入集合S中,并将路径v, , vk , vi与原来的假设相比较,取路径长度较小者为最短路径。重复上述过程,直到集合V中全部顶点加入到集合S中。

●图的存储结构:带权的邻接矩阵存储结构  

●数组dist[n]:每个分量dist[i]表示当前所找到的从始点v到终点vi的最短路径的长度。初态为:若从vvi有弧,则dist[i]为弧上权值;否则置dist[i]为∞。

●数组path[n]path[i]是一个字符串,表示当前所找到的从始点v到终点vi的最短路径。初态为:若从vvi有弧,则path[i]vvi;否则置path[i]空串。

●数组s[n]:存放源点和已经生成的终点,其初态为只有一个源点v。 

Dijkstra算法——伪代码

1. 初始化数组distpaths

2. while (s中的元素个数<n)

     2.1 dist[n]中求最小值,其下标为k

     2.2 输出dist[j]path[j]

     2.3 修改数组distpath

     2.4 将顶点vk添加到数组s中;

Dijkstra算法

void Dijkstra(MGraph G,int v)

{

for(i=0;i<G.vertexNum;i++)

{

dist[i]=G.arc[v][i];

if(dist[i]!=) path[i]=G.vertex[v]+G.vertex[i];

else path[i]=" ";

}

s[0]=v;

dist[v]=0;

num=1;

while(num<G.vertexNum)

{

for(k=0,i=0;i<G.vertexNum;i++)

if((dist[i]!=0)&&(dist[i]<dist[k])) k=i;

cout<<dist[k]<<path[k];

s[num++]=k;

for(i=0;i<G.vertexNum;i++)

if(dist[i]>dist[k]+G.arc[k][i])

{

dist[i]=dist[k]+G.arc[k][i];

path[i]=path[k]+G.vertex[i];

}

dist[k]=0;

}

}

 

6.4.2 Floyd算法(每一对顶点之间的最短路径)

※问题描述:给定带权有向图G(V, E),对任意顶点vi,vjVij),求顶点vi到顶点vj的最短路径。 

 解决办法1:每次以一个顶点为源点调用Dijkstra算法。显然,时间复杂度为O(n3)

 解决办法2:弗洛伊德提出的求每一对顶点之间的最短路径算法——Floyd算法,其时间复杂度也是O(n3),但形式上要简单些。

Floyd算法基本思想:

对于从vivj的弧,进行n次试探:首先考虑路径vi,v0,vj是否存在,如果存在,则比较vi,vjvi,v0,vj的路径长度,取较短者为从vivj的中间顶点的序号不大于0的最短路径。在路径上再增加一个顶点v1,依此类推,在经过n次比较后,最后求得的必是从顶点vi到顶点vj的最短路径。

●图的存储结构:带权的邻接矩阵存储结构  

●数组dist[n][n]:存放在迭代过程中求得的最短路径长度。

●数组path[n][n]:存放从vivj的最短路径,初始为path[i][j]="vivj"

▲ Floyd算法——C++描述

void Floyd(MGraph G)

{

    for (i=0; i<G.vertexNum; i++)        

       for (j=0; j<G.vertexNum; j++)

       {

          dist[i][j]=G.arc[i][j];

          if (dist[i][j]!=

               path[i][j]=G.vertex[i]+G.vertex[j];

          else path[i][j]=""; 

       }

    for (k=0; k<G.vertexNum; k++)         

        for (i=0; i<G.vertexNum; i++)       

           for (j=0; j<G.vertexNum; j++)

               if (dist[i][k]+dist[k][j]<dist[i][j]) {

                    dist[i][j]=dist[i][k]+dist[k][j];

                    path[i][j]=path[i][k]+path[k][j];

              }

}

 

6.5 有向无环图及其应用

 

6.5.1 AOV网与拓扑排

▲AOV网:

在一个表示工程的有向图中,用顶点表示活动,用弧表示活动之间的优先关系,称这样的有向图为顶点表示活动的网,简称AOV网。 

▲AOV网特点:

1.AOV网中的弧表示活动之间存在的某种制约关系。 

2.AOV网中不能出现回路 。

▲拓扑序列:设G=(VE)是一个具有n个顶点的有向图,V中的顶点序列v1, v2, , vn称为一个拓扑序列,当且仅当满足下列条件:若从顶点vivj有一条路径,则在顶点序列中顶点vi必在顶点vj之前。

▲拓扑排序:对一个有向图构造拓扑序列的过程称为拓扑排序。

拓扑序列使得AOV网中所有应存在的前驱和后继关系都能得到满足。

※拓扑排序基本思想:

⑴ 从AOV网中选择一个没有前驱的顶点并且输出;

⑵ 从AOV网中删去该顶点,并且删去所有以该顶点为尾的弧;

⑶ 重复上述两步,直到全部顶点都被输出,或AOV网中不存在没有前驱的顶点。

※拓扑排序算法——伪代码

1. S初始化;累加器count初始化;

2. 扫描顶点表,将没有前驱的顶点压栈;

3. 当栈S非空时循环

       3.1 vj=退出栈顶元素;输出vj;累加器加1

       3.2 将顶点vj的各个邻接点的入度减1

       3.3 将新的入度为0的顶点入栈;

4. if (count<vertexNum) 输出有回路信息;

※拓扑排序算法TopSort

void TopSort(ALGraph G)

{

top=-1;

count=0;

for(i=0;i<G.vertexNum;i++)

if(G.adjlist[i].in==0) S[++top]=i;

while(top!=-1)

{

j=S[top--];

cout<<G.addjlist[j].vertex;

count++;

p=G.adjlist[j].firstedge;

while(p!=NULL)

{

k=p->adjvex;

G.adjlist[k].in--;

if(G.adjlist[k].in==0) S[++top]=k;

p=p->next;

}

}

if(count<G.vertexNum) cout<<"有回路";

}

 

6.5.2 AOE网与关键路径

AOE网:

在一个表示工程的带权有向图中,用顶点表示事件,用有向边表示活动,边上的权值表示活动的持续时间,称这样的有向图叫做边表示活动的网,简称AOE网。AOE网中没有入边的顶点称为始点(或源点),没有出边的顶点称为终点(或汇点)。 

AOE网的性质:

⑴ 只有在某顶点所代表的事件发生后,从该顶点出发的各活动才能开始;

⑵ 只有在进入某顶点的各活动都结束,该顶点所代表的事件才能发生。

AOE网可以回答下列问题:

1. 完成整个工程至少需要多少时间?

2. 为缩短完成工程所需的时间应当加快哪些活动?   

▲关键路径:在AOE网中,从始点到终点具有最大路径长度(该路径上的各个活动所持续的时间之和)的路径称为关键路径。

  关键活动:关键路径上的活动称为关键活动。

  关键路径可能不只一条,重要的是找到关键活动

▲要找出关键路径,必须找出关键活动即不按期完成就会影响整个工程完成的活动。

首先计算以下与关键活动有关的量:

⑴ 事件的最早发生时间ve[k] 

⑵ 事件的最迟发生时间vl[k] 

⑶ 活动的最早开始时间e[i] 

⑷ 活动的最晚开始时间l[i]

最后计算各个活动的时间余量 l[k] - e[k],时间余量为0者即为关键活动。

▲事件的最早发生时间ve[k] 

ve[k]是指从始点开始到顶点vk的最大路径长度。这个长度决定了所有从顶点vk发出的活动能够开工的最早时间。

ve[1]=0;ve[k]=max{ve[j]+len<vj, vk>} (<vj, vk>p[k])                       

p[k]表示所有到达vk的有向边的集合 

▲事件的最迟发生时间vl[k] 

vl[k]是指在不推迟整个工期的前提下,事件vk允许的最晚发生时间。

vl[n]=ve[n];vl[k]=min{vl[j]-len<vk , vj>}<vk, vj>s[k]

s[k]为所有从vk发出的有向边的集合

▲活动的最早开始时间e[i] 

若活动ai是由弧<vk , vj>表示,则活动ai的最早开始时间应等于事件vk的最早发生时间。因此,有:e[i]=ve[k]    

▲活动的最晚开始时间l[i]

ai由弧<vkvj>表示,则ai的最晚开始时间要保证事件vj的最迟发生时间不拖后。因此,有: l[i]=vl[j]-len<vk, vj>           

 

0 0