拓扑排序和关键路径

AOV网络和拓扑排序

定义

在TopSort函数中,如果外循环还没结束,就已经找不到“未输出的入度为0的顶点”,则说明图中必定存在回路。

容器的影响

分别用队列和堆栈作为容器,对计算机专业课程进行拓扑排序,得到的序列有什么区别?用哪种容器排课更合理?

队列拓扑排序得到的结果是从上到下的顺序,类似于一层一层地拓扑排序。 堆栈拓扑排序得到的结果是从下到上的顺序,类似于逆序的拓扑排序。 选择合理的容器: 如果你希望按照正常的学习顺序,从基础课程开始,逐渐深入,那么队列拓扑排序更合理。 如果你更关注最终课程的先修条件,或者希望从高级课程逆向推导,那么堆栈拓扑排序更合理。

分别用队列和堆栈作为容器,对计算机专业课程进行拓扑排序,得到的序列有什么区别?用哪种容器排课更合理?

在进行拓扑排序时,可以使用队列或堆栈作为辅助容器。这两种方法都能够正确地产生拓扑排序,但得到的序列可能会不同,这取决于容器的数据结构特性。

使用队列进行拓扑排序

当使用队列进行拓扑排序时,通常是实现了所谓的Kahn算法。这种方法是基于入度(指向某个顶点的边的数量)的概念:

首先将所有入度为0的顶点加入队列。

然后依次从队列中取出一个顶点,将其添加到排序结果中,并移除所有从该顶点出发的边,更新相关顶点的入度。

如果某个顶点的入度变为0,则将其加入队列。

重复步骤2和3,直到队列为空。

使用队列的拓扑排序通常产生多种合法的排序结果,对于同一张图,不同的顶点加入队列的顺序可能会产生不同的拓扑排序序列。

使用堆栈进行拓扑排序

使用堆栈进行拓扑排序通常是基于深度优先搜索(DFS):

对每个尚未访问的顶点,执行DFS。

在DFS的过程中,当从某个顶点出发的所有边都被探索过后,将该顶点压入堆栈。

DFS完成后,依次从堆栈中弹出顶点,这个顺序就是拓扑排序的结果。

这种方法也可能产生多种拓扑排序,具体结果取决于DFS的顺序。

哪种方法更合理?

在排课程时,选择哪种方法取决于具体的需求:

如果你想要的是一种可能的顺序,并且希望尽快开始没有先决条件的课程(即入度为0的课程),那么使用基于队列的Kahn算法更合适。

如果你对课程之间的依赖关系有更深入的探究,或者对课程的探索顺序有特定需求,使用基于DFS的堆栈方法可能更合适。

在大多数情况下,使用队列的方法更直观,因为它直接处理了课程之间的依赖关系。然而,最终的选择应该基于具体场景和需求。

邻接表存储——拓扑排序算法

/* 邻接表存储 - 拓扑排序算法 */

bool TopSort( LGraph Graph, Vertex TopOrder[] )
{ /* 对Graph进行拓扑排序,  TopOrder[]顺序存储排序后的顶点下标 */
    int Indegree[MaxVertexNum], cnt;
    Vertex V;
    PtrToAdjVNode W;
       Queue Q = CreateQueue( Graph->Nv );
 
    /* 初始化Indegree[] */
    for (V=0; V<Graph->Nv; V++)
        Indegree[V] = 0;
        
    /* 遍历图,得到Indegree[] */
    for (V=0; V<Graph->Nv; V++)
        for (W=Graph->G[V].FirstEdge; W; W=W->Next)
            Indegree[W->AdjV]++; /* 对有向边<V, W->AdjV>累计终点的入度 */
            
    /* 将所有入度为0的顶点入列 */
    for (V=0; V<Graph->Nv; V++)
        if ( Indegree[V]==0 )
            AddQ(Q, V);
            
    /* 下面进入拓扑排序 */ 
    cnt = 0; 
    while( !IsEmpty(Q) ){
        V = DeleteQ(Q); /* 弹出一个入度为0的顶点 */
        TopOrder[cnt++] = V; /* 将之存为结果序列的下一个元素 */
        /* 对V的每个邻接点W->AdjV */
        for ( W=Graph->G[V].FirstEdge; W; W=W->Next )
            if ( --Indegree[W->AdjV] == 0 )/* 若删除V使得W->AdjV入度为0 */
                AddQ(Q, W->AdjV); /* 则该顶点入列 */ 
    } /* while结束*/
    
    if ( cnt != Graph->Nv )
        return false; /* 说明图中有回路, 返回不成功标志 */ 
    else
        return true;
}

邻接表存储——拓扑排序算法实例

#include <stdio.h>
#include <stdlib.h>

#define MaxVertexNum 100
typedef int WeightType;
typedef int Vertex;

typedef struct ENode *PtrToENode;
struct ENode
{
    Vertex V1;
    Vertex V2;
    WeightType Weight;
};
typedef PtrToENode Edge;

typedef struct AdjVNode *PtrToAdjVNode;
struct AdjVNode
{
    PtrToAdjVNode Next;
    Vertex AdjV;
    WeightType Weight;
};

typedef struct VNode
{
    PtrToAdjVNode FirstEdge;
}AdjList[MaxVertexNum];


typedef struct GNode *PtrToGNode;
struct GNode
{
    int Ne;
    int Nv;
    AdjList G;
};
typedef PtrToGNode LGraph;

void InsertEdge(LGraph Graph, Edge E)
{
    PtrToAdjVNode newNode = (PtrToAdjVNode)malloc(sizeof(struct AdjVNode));
    newNode->AdjV = E->V2;
    newNode->Weight = E->Weight;
    newNode->Next = Graph->G[E->V1].FirstEdge;
    Graph->G[E->V1].FirstEdge = newNode;

    //newNode = (PtrToAdjVNode)malloc(sizeof(struct AdjVNode));
    //newNode->AdjV = E->V1;
    //newNode->Weight = E->Weight;
    //newNode->Next = Graph->G[E->V2].FirstEdge;
    //Graph->G[E->V2].FirstEdge = newNode;
}

LGraph CreateGraph(int VertexNum)
{
    LGraph Graph = (LGraph)malloc(sizeof(struct GNode));
    Graph->Nv = VertexNum;
    Graph->Ne = 0;
    for(int i=0; i<Graph->Nv; i++)
        Graph->G[i].FirstEdge = NULL;
    return Graph;
}

LGraph BuildGraph()
{
    int N;
    Edge E;
    scanf("%d",&N);
    LGraph Graph = CreateGraph(N);
    scanf("%d",&Graph->Ne);
    if(Graph->Ne != 0)
    {
        for(int i=0; i<Graph->Ne; i++)
        {
            E = (Edge)malloc(sizeof(struct ENode));
            scanf("%d %d %d",&E->V1,&E->V2,&E->Weight);
            InsertEdge(Graph, E);
        }
    }
    return Graph;
}

void TopSort(LGraph Graph, int TopOrder[])
{
    int Indegree[MaxVertexNum], count;
    Vertex V;
    PtrToAdjVNode W;
    //Queue Q = CreateQueue( Graph->Nv );
    Vertex queue[Graph->Nv];
    int front = 0;
    int rear = 0;
    for (V=0; V<Graph->Nv; V++)
        Indegree[V] = 0;

    for(V=0; V<Graph->Nv; V++)
    {
        for(W=Graph->G[V].FirstEdge; W; W=W->Next)
        {
            Indegree[W->AdjV]++;
        }
    }
    for(V=0; V<Graph->Nv; V++)
    {
        if(Indegree[V] == 0)
        {
            queue[rear++] = V;
        }
    }
    //下面进入拓扑排序
    count = 0;
    while(front != rear)
    {
        V = queue[front++];
        TopOrder[count++] = V;
        for(W=Graph->G[V].FirstEdge; W; W=W->Next)
        {
            if(--Indegree[W->AdjV] == 0)
            {
                queue[rear++] = W->AdjV;
            }
        }
    }
    if(count != Graph->Nv)
    {
        printf("图中存在循环路径!\n");
    }
    else
    {
        for(int i=0; i<count; i++)
            printf("%d ",TopOrder[i]);
    }

}

int main()
{
    LGraph Graph = BuildGraph();
    Vertex TopOrder[MaxVertexNum];
    TopSort(Graph, TopOrder);
    return 0;
}

输入数据:

6  
7  
0 2 1  
1 2 1  
1 0 1  
2 5 1  
2 3 1  
3 5 1  
4 5 1  

输出结果:

1 4 0 2 3 5

说明一下,拓扑排序可能有多种结果。

另一种结果可以是4 1 0 2 3 5

#include <stdio.h>
#include <stdlib.h>

#define MaxVertexNum 100
typedef int WeightType;
typedef int Vertex;

typedef struct ENode *PtrToENode;
struct ENode
{
    Vertex V1;
    Vertex V2;
    WeightType Weight;
};
typedef PtrToENode Edge;

typedef struct AdjVNode *PtrToAdjVNode;
struct AdjVNode
{
    PtrToAdjVNode Next;
    Vertex AdjV;
    WeightType Weight;
};

typedef struct VNode
{
    PtrToAdjVNode FirstEdge;
}AdjList[MaxVertexNum];


typedef struct GNode *PtrToGNode;
struct GNode
{
    int Ne;
    int Nv;
    AdjList G;
};
typedef PtrToGNode LGraph;

void InsertEdge(LGraph Graph, Edge E)
{
    PtrToAdjVNode newNode = (PtrToAdjVNode)malloc(sizeof(struct AdjVNode));
    newNode->AdjV = E->V2;
    newNode->Weight = E->Weight;
    newNode->Next = Graph->G[E->V1].FirstEdge;
    Graph->G[E->V1].FirstEdge = newNode;

    //newNode = (PtrToAdjVNode)malloc(sizeof(struct AdjVNode));
    //newNode->AdjV = E->V1;
    //newNode->Weight = E->Weight;
    //newNode->Next = Graph->G[E->V2].FirstEdge;
    //Graph->G[E->V2].FirstEdge = newNode;
}

LGraph CreateGraph(int VertexNum)
{
    LGraph Graph = (LGraph)malloc(sizeof(struct GNode));
    Graph->Nv = VertexNum;
    Graph->Ne = 0;
    for(int i=0; i<Graph->Nv; i++)
        Graph->G[i].FirstEdge = NULL;
    return Graph;
}

LGraph BuildGraph()
{
    int N;
    Edge E;
    scanf("%d",&N);
    LGraph Graph = CreateGraph(N);
    scanf("%d",&Graph->Ne);
    if(Graph->Ne != 0)
    {
        for(int i=0; i<Graph->Ne; i++)
        {
            E = (Edge)malloc(sizeof(struct ENode));
            scanf("%d %d %d",&E->V1,&E->V2,&E->Weight);
            InsertEdge(Graph, E);
        }
    }
    return Graph;
}

void PrintList(LGraph Graph)
{
    for(int i=0; i<Graph->Nv; i++)
    {
        PtrToAdjVNode W = Graph->G[i].FirstEdge;
        printf("G[%d]:", i);
        int IsFirst = 1;
        while(W)
        {
            if(IsFirst)
            {
                printf("%d", W->AdjV);
                IsFirst = 0;
            }
            else
                printf("->%d", W->AdjV);
            W = W->Next;
        }
        printf("\n");
    }
}

void TopSort(LGraph Graph, int TopOrder[])
{
    int Indegree[MaxVertexNum], count;
    Vertex V;
    PtrToAdjVNode W;
    //Queue Q = CreateQueue( Graph->Nv );
    Vertex queue[Graph->Nv];
    int front = 0;
    int rear = 0;
    for (V=0; V<Graph->Nv; V++)
        Indegree[V] = 0;

    for(V=0; V<Graph->Nv; V++)
    {
        for(W=Graph->G[V].FirstEdge; W; W=W->Next)
        {
            Indegree[W->AdjV]++;
        }
    }
    for(V=0; V<Graph->Nv; V++)
    {
        if(Indegree[V] == 0)
        {
            queue[rear++] = V;
        }
    }
    //下面进入拓扑排序
    count = 0;
    while(front != rear)
    {
        V = queue[front++];
        TopOrder[count++] = V;
        for(W=Graph->G[V].FirstEdge; W; W=W->Next)
        {
            if(--Indegree[W->AdjV] == 0)
            {
                queue[rear++] = W->AdjV;
            }
        }
    }
    if(count != Graph->Nv)
    {
        printf("图中存在循环路径!\n");
    }
    else
    {
        for(int i=0; i<count; i++)
            printf("%d ",TopOrder[i]);
    }

}

int main()
{
    LGraph Graph = BuildGraph();
    PrintList(Graph);
    Vertex TopOrder[MaxVertexNum];
    TopSort(Graph, TopOrder);
    return 0;
}

输入数据:

6  
7  
0 2 1   
1 0 1  
2 1 1 
2 5 1  
2 3 1  
3 5 1  
4 5 1  

输出结果:

G[0]:2
G[1]:0
G[2]:3->5->1
G[3]:5
G[4]:5
G[5]:
图中存在循环路径!

AOE网络和关键路径

下图给定了一个项目的AOE。整个项目最早完工需要的时间是

  • A.17
  • B.19
  • C.20
  • D.23

正确答案:D你选对了

2在上图中,如果<0,2>组能加快进度,整个项目就能提前完工。

正确答案:A你选对了