一、图的定义

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

  • 线性表中将数据元素称为元素,在树中将数据元素称为结点,在图中数据元素,称为顶点
  • 在图结构中,不允许没有顶点。在定义中,若V是顶点的集合,则强调了顶点集合V有穷非空
  • 在图中,任意两个顶点之间都可能有关系,顶点之间的逻辑关系用边表示,边集可以是空的

(1)各种图定义

无向边:若顶点vi到vj之间的边没有方向,则称这条边为无向边,用无序偶对(vi,vj)表示。

无向图:图中任意两个顶点之间的边都是无向边

在这里插入图片描述

G1=(V1,{E1}),其中顶点集合V1={A,B,C,D},边集合E1={(A,B),(B,C),(C,D),(D,A),(A,C)}

有向边:若从顶点vi到vj的边有方向,则称这条边为有向边,也称为弧。用有序偶<vi,vj>表示,vi称为弧尾,vj称为弧头

有向图:图中任意两个顶点之间的边都是有向边

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

无向完全图:在无向图中,如果任意两个顶点之间都存在边

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

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

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

稀疏图:有很少条边或弧的图称为稀疏图,反正为稠密图

:与图的边或弧相关的数

:带权的图

假设有两个图G=(V,{E})和G’=(V’{E’}),如果V’包含于V且E‘包含于E,则称G’为G的子图

(2)图的顶点与边间关系

  • 对于无向图G=(V,{E}),如果边(V,V’)属于E,则称顶点V与V‘互为邻接点,即V与V‘相邻接。边(V,V’)依附于顶点V和V‘ ,或者说(V,V’)与顶点V和V‘相关联。顶点V的度是和V相关联的边的数目,记为TD(V)
  • 对于有向图G=(V,{E}),如果弧<V,V’>属于E,则称顶点V邻接顶点V’,顶点V‘邻接顶点V。弧<V,V’>和顶点V,V’相关联。以顶点V为头的弧的数目称为V的入度,记为ID(V);以V为尾的弧的数目称为V的出度,记为OD(V);顶点V的度为TD(V)=ID(V)+OD(V)
  • 无向图G=(V,{E})中从顶点V到顶点V‘的路径是一个顶点序列(V=Vi,0,Vi,1,…,Vi,m=V’)
  • 如果G是有向图,则路径也是有向的
  • 路径的长度是路径上的边或弧的数目
  • 第一个顶点到最后一个顶点相同的路径称为回路或环。序列中顶点不重复出现的路径称为简单路径。除了第一个顶点和最后一个顶点之外,其余顶点不重复出现的回路,称为简单回路或简单环

(3)连通图相关术语

连通图:在无向图中G中,如果从顶点V到顶点V’有路径,则称V和V‘是连通的。如果对于图中任意两个顶点Vi、Vj属于V,Vi和Vj都是连通的,则称G是连通图

连通分量:无向图中的极大连通子图

强连通图:在有向图G中,如果对于每一对Vi、Vj属于V,Vi不等于Vj,从Vi到Vj都存在路径,则称G为强连通图

强连通分量:在有向图中的极大强连通子图

连通图生成树:为一个小的连通子图,其含有图的全部n个顶点,但只有足以构成一棵树的n-1条边

  • 如果一个图有n个顶点和小于n-1条边,则为非连通图,多于n-1条边必定构成一个环
  • 如果一个有向图恰有一个顶点的入度为0,其余顶点的入度均为1,则是一棵有向树
  • 一个有向图生成的森林由若干棵有向树组成,含有图中全部顶点,但只有足以构成若干棵不相交的有向树的弧

二、图的抽像数据类型

ADT 图
Data
    顶点的有穷非空集合和边的集合
Operation
    CreateGraph(*G,V,VR);//按照顶点集V和边弧集VR的定义构造图G
	DestropGraph(*G);//图G存在则销毁
	LocateVex(G,u);//若图G中存在顶点u,则返回图中位置
	GetVex(G,V);//返回图G中顶点v的值
	PutVex(G,v,value);//将图G中顶点v赋值value
	FirstAdjVex(G,v,*w);//返回顶点v的一个邻接顶点,若顶点在图G中无邻接顶点则返回空
	NextadjVex(G,v *w);//返回顶点v相对于顶点w的下一个邻接顶点,若w是v的最后一个邻接顶点则返回空
	InsertArc(*G,v,w)//在图G中增添弧<v,w>,若G是无向图,则还需要增添对称弧<w,v>
 	Deletearc(*G,v,w);//在图G中删除弧<v,w>,若G是无向图,还需要删除对称弧<w,v>
	DFSTraverse(G);//对图G中进行深度优先遍历,在遍历过程中对每一个顶点调用
	HFSTraverse(G);//对图G中进行广度优先遍历,在遍历过程中对每个顶点调用
endADT

三、图的存储结构

(1)邻接矩阵

图的邻接矩阵存储方式是用两个数组来表示图。一个一维数组存储图中顶点信息,一个二维数组(称为邻接矩阵)存储图中的边或弧的信息。

设图G有n个顶点,则邻接矩阵是一个n*n的方阵,定义为

  • arc[i] [j]=1,若(Vi,Vj)属于E或<Vi,Vj>属于E
  • arc[i] [j]=0,反之

顶点数组: | V0 |V1|V2| V3|

边数组

在这里插入图片描述

无向图的边数组是一个对称矩阵,有向图的边数组不一定对称

设图G是一个网图,有n个顶点,则邻接矩阵是一个n*n的方阵,定义为:

  • arc[i] [j]=Wij,若(Vi,Vj)属于E或<Vi,Vj>属于E(Wij表示权值)
  • arc[i] [j]=0,若i=j
  • arc[i] [j]=无穷大,反之

邻接矩阵存储的结构代码

typedef char VertexType;//顶点类型由用户定义
typedef int EdgeType;//边上的权值类型由用户定义
#define MAXVEX 100//最大顶点数,由用户定义
#define INFINITY 65535//用65535来代表无穷大
typedef struct
{
    VertexType vexs[MAXVEX];//顶点表
    EdgeType arc[MAXVEX][MAXVEX];//邻接矩阵,可作边表
    int numVertexes,numEdges;//图中当前的顶点数和边数
}MGraph;

无向网图的创建代码

//建立无向网图的邻接矩阵表示
void CreateMGraph(MGraph *G)
{
    int i,j,k,w;
    printf("请输入顶点数和边数:\n");
    scanf("%d,%d",&G->numVertexes,&G->numEdges);//输入顶点数和边数
    for(i=0;i<G->numVertexes;i++)//读入顶点信息,建立顶点表
    {
        scanf("&G->vexs[i]");
    }
    for(i=0;i<G->numVertexes;i++)
    {
        for(j=0;j<G->numVertexes;j++)
        {
            G->arc[i][j]=INFINITY;//邻接矩阵初始化
        }
    }
    for(k=0;k<G->numEdges;k++)//读入numEdges条边,建立邻接矩阵
    {
        printf("输入边(Vi,Vj)上的下标i,下标j和权值w:\n");
        scanf("%d,%d,%d",&i,*j,&w);
        G->arc[i][j]=w;
        G->arc[i][j]=G->arc[i][j];//因为为无向图,矩阵对称
    }
}

(2)邻接表

邻接表:数组与链表相结合的存储方式

邻接表的处理方法

  1. 图中顶点用一个一维数组存储,顶点也可以用单链表存储。对于顶点数组中,每个数据元素还需要存储一个指向第一个邻接点的指针,以便于查找该顶点的边信息
  2. 图中每个顶点Vi的所有邻接点构成一个线性表,由于邻接点的个数不确定,所以用单链表存储,无向图称为顶点Vi的边表,有向图称为顶点Vi作为弧尾的出边表

顶点表: data | firstedge

  • data是数据域,存储顶点信息
  • firstedge是指针域,指向边表的第一个结点,即此顶点的第一个邻接点

边表结点: adjvex | next

  • adjvex是邻接点域,存储某顶点的邻接点在顶点表中的下标
  • next则存储指向边表中下一个结点的指针

对于有向图,可以建立一个有向图的逆邻接表,即对每个顶点Vi都建立一个链接为Vi为弧头的表

对于带权值的网图,可以在边表结点定义中在增加一个weight的数据域,存储权值信息

结点定义代码

typedef char VertexType;//顶点类型由用户定义
typedef int EdgeType;//边上的权值类型由用户定义

typedef struct EdgeNode//边表结点
{
    int adjvex;//邻接点域,存储该顶点对于的下标
    EdgeType weight;//用于存储权值,对于非网图可以不需要
    struct EdgeNode *next;//链域,指向下一个邻接点
}EdgeNode;

typedef struct VertexNOde//顶点表结点
{
    VertexType data;//顶点域,指向顶点信息
    EdgeNode *firstedge;//边表头指针
}VertexNode,AdjList[MAXVEX];

typedef struct
{
    AdjList adjList;
    int numVertexes,numEdges;//图中当前的顶点数和边数
}GraphAdList;

无向图的邻接表创建代码

//建立图的邻接表结构
void CreateALGraph(GraphAdjList *G)
{
    int i.j,k;
    EdgeNode *e;
    printf("请输入顶点数和边数:\n");
    scanf("%d,%d",&G->numVertexes,&G->numEdges);//输入顶点数和边数
    for(i=0;i<G->numVertexes;i++)//读入顶点信息,建立顶点表
    {
        scanf(&G->adjList[i].data);//输入顶点信息
        G->adjList[i].firstedge=NULL;//将边表置为空
    }
    for(k=0;k<G->numEdges;k++)//建立边表
    {
        printf("输入边(vi,vj)上的顶点序号:\n");
        scanf("%d.%d",&i,&j);
        e=(EdgeNode*)malloc(sizeof(EdgeNode));//向内存中申请空间,生成边表结点
        e->adjvex=j;//邻接序号为j
        e->next=G->adjList[i].firstedge;//将e指针指向当前顶点指向的结点
        G->adjList[i].firstedge=e;//将当前顶点的指针指向e
        e=(EdgeNode*)malloc(sizeof(EdgeNode));//向内存中申请空间,生成边表结点
        e->adjvex=i;//邻接序号为i
        e->adjvex=i;//邻接序号为i
        e->next=G->adjList[j].firstedge;//将e指针指向当前顶点指向的结点、
        G->adjList[j].firstedge=e;//将当前顶点的指针指向e
    }
}

(3)十字链表

十字链表:将邻接表与逆邻接表结合起来

顶点表结构: data|firstin|firstout|

  • firstin表示入边表头指针,指向该顶点的入边表中第一个结点
  • firstout表示出边表头指针,指向该顶点的出边表的第一个结点

边表结点结构:tailex|headvex|headlink|taillink|

  • tailvex是指弧起点在顶点表的下标
  • headvex是指弧终点在顶点表的下标
  • headlink是指入边表指针域,指向终点相同的下一条边
  • taillink是指边表指针域,指向起点相同的下一条边
  • 如果是网,还需再增加一个weight域来存储权值

(4)邻接多重表

重新定义十字链表中的边表结点:ivex|ilink|jvex|jlink|

  • ivex与jvex是与某条边依附的两个顶点在顶点表的下标
  • ilink指向依附顶点ivex的下一条边,jlink指向依附顶点jvex的下一条边

(5)边集数组

边集数组:由两个一维数组构成。一个存储顶点信息,另一个存储边的信息,这个边数组每个数据元素由一条边的起点下标、终点下标和权组成。

边数组结构:begin|end|weight|

四、图的遍历

图的遍历:从图中某一顶点出发访遍图中其余顶点,且使每一个顶点仅被访问一次

(1)深度优先遍历(深度优先搜索)DFS

从图中某个顶点V出发,访问此顶点,然后从V的未被访问的邻接点出发深度优先遍历图,直到图中所有和V有路径相通的顶点都被访问到

采用邻接矩阵的方式

typedef int Boolean//Boolean是布尔类型,其值为TRUE或FALSE
BOolean visited[MAX];//访问标志的数组
//邻接矩阵的深度优先递归算法
void DFS(MGraph G,int i)
{
    int j;
    visited[i]=TRUE;
    printf("%c",G.vexs[i]);//打印顶点,也可以是其他操作
    for(j=0;j<G.numVertexes;j++)
    {
        if(G.arc[i][j]==1&&!visited[j])
        {
            DFS(G,j);//对访问的邻接顶点递归调用
        }
    }
}

//邻接矩阵的深度遍历操作
void DDFSTraverse(MGraph G)
{
    int i;
    for(i=0;i<G.numVertexes;i++)
    {
        visited[i]=FALSE;//初始所有顶点状态都是未访问过状态
    }
    for(i=0;i<G.numVertexes;i++)
    {
        if(!visited[i])//对未访问过的顶点调用DFS,若是连通图,只会执行一次
        {
            DFS(G,i);
        }
    }
}

采用邻接表结构的方式

//邻接表的深度优化递归算法
void DFS(GraphAdjList GL,int i)
{
    EdgeNode *p;
    visited[i]=TRUE;
    printf("%c",GL->adjList[i].data);//打印顶点,也可以是其他操作
    p=GL->adjList[i].firstedge;
    while(p)
    {
        if(!visited[p->adjvex])
        {
            DFS(GL,p->adjvex);//对访问的邻接顶点递归调用
        }
        p=p->next;
    }
}

//邻接表的深度遍历操作
void DFSTraverse(GraphAdjList GL)
{
    int i;
    for(i=0;i<GL->numVertexes;i++)
    {
        visited[i]=FALSE;//初始所有顶点状态都是未访问过状态
    }
    for(i=0;i<GL->numVertexes;i++)
    {
        if(!visited[i])//对未访问过的顶点调用DFS,若是连通图,只会执行一次
        {
            DFS(GL,i);
        }
    }
}

(2)广度优先遍历(广度优先搜索)BFS

邻接矩阵的广度优先遍历算法

void BFSTraverse(MGraph G)
{
    int i.j;
    Queue Q;
    for(i0;i<G.numVertexes;i++)
    {
        visited[i]=FALSE;
    }
    InitQueue(&Q);//初始化一辅助用的队列
    for(i=0;i<G.numVertexes;i++)//对每个顶点做循环
    {
        if(!visited[i])//若是未访问过就处理
        {
            visited[i]=TRUE;//设置当前顶点访问过
            printf("%c",G.vexs[i]);//打印顶点,也可以是其他操作
            EnQueue(&Q,i);//将此顶点入队列
            while(!QueueEmpty(Q))//若当前队列不为空
            {
                DeQueue(&Q,&i);//将队中元素出队列,赋值给i
                for(j=0;j<G.numVertexses;j++)
                {
                    if(G.arc[i][j]==1&&!visited[j])//判断其他顶点若与当前顶点存在边且未被访问过
                    {
                        visited[j]==TRUE;//将找到的此顶点标记为已访问
                        printf("%c",G.vexs[j]);//打印顶点
                        EnQueue(&Q.j);//将找到的此顶点入队列
                    }
                }
            }
        }
    }
}

邻接表的广度遍历算法

void BFSTraverse(GraphAdjList GL)
{
    int i;
    EdgeNode *p;
    Queue Q;
    for(i=0;i<GL->numVertexes;i++)
    {
        visited[i]=FALSE;
    }
    InitQueue(*Q);
    for(i=0;i<GL->numVertexes;i++)
    {
        if(!visited[i])
        {
            visited[i]=TRUE;
            printf("%c",GL->adjList[i].data);//打印顶点,也可以是其他操作
            EnQueue(&Q,i);
            while(!QUeueEmpty(Q))
            {
                DeQueue(&Q,&i);
                p=GL->adList[i].firstedge;//找到当前顶点边表链表头指针
                while(p)
                {
                    if(!visited[p->adjvex])//若此顶点未被访问
                    {
                        visited[p->adjvex]=TRUE;
                        printf("%c",GL->adjList[p->adjvex].data);
                        EnQueue(&Q,p->adjvex);//将此顶点入队列
                    }
                    p=p->next;//指针指向下一个邻接点
                }
            }
        }
    }
}

五、最小生成树

最小生成树:构造连通网的最小代价生成树

(1)普里姆算法

  • INFINITY为权值极大值,记为65535
  • MAXVEX为顶点个数最大值

Prim算法生成最小生成树

void MiniSpanTree_Prim(MGraph G)
{
    int min,i,j,k;
    int adjvex[MAXVEX];//保存相关顶点下标
    int lowcost[MAXVEX];//保存相关顶点间边的权值
    lowcost[0]=0;//初始化第一个权值为-,即v0加入生成树,lowcost的值为0,在这里就是此下标的顶点已经加入生成树
    adjvex[0]=0;//初始化第一个顶点下标为0
    for(i=1;i<G.numVertexes;i++)//循环除下标为0外的全部顶点
    {
        loecost[i]=G.arc[0][i];//将v0顶点与之有边的权值存入数组
        adjvex[i]=0;//初始化都为V0的下标
    }
    for(i=1;i<G.numVertexes;i++)
    {
        min=INFINITY;//初始化最小权值为无穷大。通常设置为不可能的大数字如32767、65335等
        j=1;k=0;
        while(j<G.numVertexes)//循环全部顶点
        {
            if(lowcost[j]!=0&&lowcost[j]<min)//如果权值不为0且权值小于min
            {
                min=lowcost[j];//则让当前权值成为最小值
                k=j;//将当前最小值的小标存入k
            }
            j++;
        }
        printf("(%d,%d)",adjvex[k],k);//打印当前顶点边中权值最小边
        lowcost[k]=0;//将当前顶点的权值设置为0,表示此顶点已经完成任务
        for(j=1;j<G.numVertexes;j++)//循环所有顶点
        {
            if(lowcost[j]!=0&&G.arc[k][j]<lowcost[j]//若下标为k顶点各边权值小于当前这些顶点未被加入生成树权值
               {
                   lowcost[j]=G.arc[k][j];//将较小权值存入lowcost
                   adjvex[j]=k;//将下标为k的顶点存入adjvex
               }
        }
    }
}

(2)克鲁斯卡尔算法

edge边集数组结构的定义代码

typedef struct
{
    int begin;
    int end;
    int weight;
}Edge;

Kruskal算法生成最小生成树

void MiniSpanTree_Kruskal(MGraph G)//生成最小生成树
{
    int i,n,m;
    Edge edges[MAXEDGE];//定义边集数组
    int parent[MAXVEX]//定义一数组用来判断边与边是否形成环路
    //此处省略将邻接矩阵G转化为边集数组edges并按权由小到大排列的代码
    for(i=0;i<G.numVertexes;i++)
    {
        parent[i]=0;//初始化数组值为0
    }
    for(i=0;j<G.numEdges;i++)//循环每一条边
    {
        n=Find(parent,edges[i].begin);
        m=Find(parent,edges[i].end);
        if(n!=m)//假设n与m不等,说明此边没有与现有生成树形成环路
        {
            parent[m]=m;//将此边的结尾顶点放入下标为起点的parent中,表示此顶点已经在生成树集合中
            printf("(%d,%d)%d",edges[i].begin,edges[i].end,edges[i].weight);
        }
    }
}

int Find(int *parent,int f)//查找连线顶点的尾部下标
{
    while(parent[f]>0)
    {
        f=parent[f];
    }
    return f;
}

六、最短路径

最短路径:对于网图,是指两顶点之间经过的边上权值之和最小的路径,并称路径上的第一个顶点为源点,最后一个顶点为终点。

(1)迪杰斯特拉算法

#define MAXVEX 9
#define INDINITY 65535
typedef int Patharc[MAXVEX];//用于存储最短路径下标的数值
typedef int ShortPathTable[MAXVEX];//用于存储到各点最短路径的权值和
//Dijkatra算法,求有向网G的V0顶点到其余顶点V的最短路径P[v]及带权长度D[v]
//P[v]的值为前驱顶点下标,D[v]表示V0到V的最短路径长度和
void shortestPath_Dijkatra(MGraph G,int v0,Patharc *p,shortPathTable *D)
{
    int v,w,k,min;
    int final[MAXVEX];//final[w]=1表示求得顶点V0到Vw的最短路径
    for(V=0;V<G.numVertexes;v++)//舒适化数据
    {
        finial[v]=0;//全部顶点初始化为未知最短路径状态
        (*D)[v]=G.arc[v0][v];//将与V0有连续的顶点加上权值
        (*p)[v]=0;//初始化路径数组p为0
    }
    (*D)[V0]=0;//V0到V0路径为0
    final[V0]=1//V0至V0不需要求路径
        //开始主循环,每次求得V0到某个V顶点的最短路径
    for(v=1;V<G.numVertexes;v++)
    {
        min=INFINITY;//当前所离V0顶点的最近距离
        for(w=0;w<G.numVertexes;w++)//寻找离V0最近的顶点
        {
            if(!final[w]&&(*D)[w<min])
            {
                k=w;
                min=(*D)[w];//w顶点离V0顶点更近
            }
        }
        final[k]=1;//将目前找到的最近的顶点置为1
        for(w=0;w<G.numVertexes;w++)//修正当前最短路径及距离
        {
            //如果经过V顶点的路径比现在这条路径的长度短的话
            if(!final[w]&&(min+G.arc[k][w]<(*D)[w]))
            {
                //说明找到了更短的路径,修改D[w]和P[w]
                (*D)[w]=min+G.arc[k][w;]//修改当前路径长度
                (*p)[w]=k;
            }
        }
    }
}

弗洛伊德算法

typedef int Pathmatirx[MAXVEX][MAXVEX];
typedef int ShortPathTable[MAXVEX][MAXVEX];
//Floyd算法,求网图G中各顶点V到其余顶点w最短路径P[v][w]及带权长度D[v][w]
void ShorttestPath_Floyd(MGraph G,Pathmatirx *p,ShortPathTable *D)
{
    int v,w,k;
    for(v=0;v<G.numVertexes;++v)//初始化D与p
    {
        for(w=0;w<G.numVertexes;++w)
        {
        	(*D)[v][w]=G.matirx[v][w];//D[v][w]值即为对于点间的权值
        	(*p)[v][w]=w;//初始化p
        }
    }
    for(k=0;k<G.numVertexes;++k)
    {
        for(v=0;v<G.numVertexes;++v)
        {
            for(w=0;w<G.numVertexes;++w)
            {
                if((*D)[v][w]>(*D)[v][k]+(*D)[k][w])
                {
                    //如果经过下标为k顶点路径比原两点间路径更短
                    //将当前两点间权值设为更小的一个
                    (*D)[v][w]=(*D)[v][k]+(*D)[k][w];
                    (*p)[v][w]=(*p)[v][k];//路径设置经过下标为k的顶点
                }
            }
        }
    }
        
}

七、拓扑排序

AOV网:在一个表示工程的有向图中,用顶点表示活动,用弧表示活动之间的优先关系

拓扑序列:设G=(V,E)是一个具有n个顶点的有向图,V中的顶点序列V1,V2,…,Vn,满足若从顶点Vi到vj有一条路径,则在顶点序列中顶点Vi必在顶点Vj之前。

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

//拓扑排序,若GL无回路,则输出拓扑排序序列并返回OK,若有回路返回ERROR
Status TopologicalSort(GraphAdjList GL)
{
    EdgeNOde *e;
    int i,k,gettop;
    int top=0;//用于栈指针下标
    int count=0;//用于统计输出顶点的个数
    int *stack;//建栈存储入度为0的顶点
    stack=(int*)malloc(GL->numVertexes*siezof(int));
    for(i=0;i<GL->numVertexes;i++)
    {
        if(GL->adjList[i].in=0)
        {
            stack[++top]=i;//将入度为0的顶点入栈
        }
    }
    while(top!=0)
    {
        gettop=stack[top--];//出栈
        printf("%d->",GL->adjList[gettop].data);//打印此顶点
        count++;//统计输出顶点数
        for(e=GL->adjList[gettop].firstedge;e;e=e->next)
        {
            //对此顶点弧表遍历
            k=e->adjvex;
            if(!(--GL->adjList[k].in))//将k号顶点邻接点的入度减1
            {
                stack[++top]=k;//若为0则入栈,以便于下次循环输出
            }
        }
    }
    if(count<GL->numVertexes)//如果count小于顶点数,则说明存在环
    {
        return ERROR;
    }
    else
    {
        return OK;
    }
}

八、关键路径

在路径上各个活动所持续的时间之和称为路径长度,从源点到汇点具有最大的长度的路径叫关键路径,在关键路径上的活动叫关键活动

(1)关键路径算法原理

  • 事件的最早发生时间etv:顶点Vk最早发生时间
  • 事件的最晚发生时间ltv:顶点Vk的最晚发生时间,即每个顶点对应的事件最晚需要开始的时间,超出此时间将会延误整个工期
  • 活动的最早开工时间ete:即弧ak的最早发生时间
  • 活动的最晚开关时间lte:即弧ak的最晚发生时间,即不推迟工期的最晚开工时间

(2)关键路径算法

首先声明全局变量

int *etv,*ltv;//事件最早发生时间和最迟发生时间数组
int *stack2;//用于存储拓扑序列的栈
int top2;

改进的求拓扑序列算法

Status TopologicalSort(GraphAdjList GL)
{
    EdgeNOde *e;
    int i,k,gettop;
    int top=0;//用于栈指针下标
    int count=0;//用于统计输出顶点的个数
    int *stack;//建栈存储入度为0的顶点
    stack=(int*)malloc(GL->numVertexes*siezof(int));
    for(i=0;i<GL->numVertexes;i++)
    {
        if(GL->adjList[i].in=0)
        {
            stack[++top]=i;//将入度为0的顶点入栈
        }
    }
    top2=0;//初始化为0
    etv=(int*)malloc(GL->numVertexes*sizeof(int));//事件最早发生时间
    for(i=0;i<GL->numVertexes;i++)
    {
        etv[i]=0;//初始化为0
    }
    stack2=(int*)malloc(GL->numVertexes*sizeof(int));//初始化
    while(top!=0)
    {
        gettop=stack[top--];//出栈
        printf("%d->",GL->adjList[gettop].data);//打印此顶点
        count++;//统计输出顶点数
        stack2[++top2]=gettop;//将弹出的顶点序号压入拓扑序列的栈
        for(e=GL->adjList[gettop].firstedge;e;e=e->next)
        {
            //对此顶点弧表遍历
            k=e->adjvex;
            if(!(--GL->adjList[k].in))//将k号顶点邻接点的入度减1
            {
                stack[++top]=k;//若为0则入栈,以便于下次循环输出
            }
            if((etv[gettop]+e->weight)>etv[k])//求各顶点事件最早发生时间
            {
                etv[k]=etv[gettop]+e->weight;
            }
        }
    }
    if(count<GL->numVertexes)//如果count小于顶点数,则说明存在环
    {
        return ERROR;
    }
    else
    {
        return OK;
    }
}

关键路径的算法代码

//求关键路径,GL为有向网,输出GL的各项关键活动
void CriticalPath(GraphAdjList GL)
{
    EdgeNode *e;
    int i,gettop,k,j;
    int ete,lte;//声明活动最早发生时间和最迟发生时间变量
    TopologicalSort(GL);//求拓扑序列,计算数组etv和stack2的值
    ltv=(int*)malloc(GL->numVertexes*sizeof(int));//事件最晚发生时间
    for(i=0;i<GL->numVertexes;i++)
    {
        ltv[i]=etv[GL->numVertexes-1];//初始化ltv
    }
    while(top2!=0)//计算ltv
    {
        gettop=stack2[top2--];//将拓扑序列出栈,后进先出
        for(e=GL->adjList[gettop].firstedge;e;e->next)
        {
            //求各顶点事件的最迟发生时间ltv值
            k=e->adjvex;
            if(ltv[k]-e->weight<ltv[gettop]);//求各顶点事件最晚发生时间ltv
            ltv[gettop]=ltv[k]-e->weight;
        }
    }
    for(j=0;j<GL->numVertexes;j++)//求ete,lte和关键活动
    {
        for(e=GL->adjList[j].firstedge;e;e=e->next)
        {
            k=e->adjvex;
            ete=etv[j];//活动最早发生时间
            lte=ltv[k]-e->weight;//活动最迟时间
            if(ete==lte)//两者相等即在关键路径上
            {
                printf("<v%d,v%d>length:%d,",GL->adjList[j].data,GL->adjList[k].data,e->weight);
            }
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值