数据结构——浙大版中国大学mooc(week8)

本文介绍了浙大版中国大学MOOC数据结构课程中关于图的部分,包括图的最小生成树(Prim算法和Kruskal算法)和拓扑排序的C语言实现。题目涉及公路村村通、项目活动最早完成时间和关键活动的计算。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

课程给出代码

C语言:邻接矩阵存储 - Prim最小生成树算法

/* 邻接矩阵存储 - Prim最小生成树算法 */
 
Vertex FindMinDist( MGraph Graph, WeightType dist[] )
{ /* 返回未被收录顶点中dist最小者 */
    Vertex MinV, V;
    WeightType MinDist = INFINITY;
 
    for (V=0; V<Graph->Nv; V++) {
        if ( dist[V]!=0 && dist[V]<MinDist) {
            /* 若V未被收录,且dist[V]更小 */
            MinDist = dist[V]; /* 更新最小距离 */
            MinV = V; /* 更新对应顶点 */
        }
    }
    if (MinDist < INFINITY) /* 若找到最小dist */
        return MinV; /* 返回对应的顶点下标 */
    else return ERROR;  /* 若这样的顶点不存在,返回-1作为标记 */
}
 
int Prim( MGraph Graph, LGraph MST )
{ /* 将最小生成树保存为邻接表存储的图MST,返回最小权重和 */
    WeightType dist[MaxVertexNum], TotalWeight;
    Vertex parent[MaxVertexNum], V, W;
    int VCount;
    Edge E;
     
    /* 初始化。默认初始点下标是0 */
       for (V=0; V<Graph->Nv; V++) {
        /* 这里假设若V到W没有直接的边,则Graph->G[V][W]定义为INFINITY */
           dist[V] = Graph->G[0][V];
           parent[V] = 0; /* 暂且定义所有顶点的父结点都是初始点0 */ 
    }
    TotalWeight = 0; /* 初始化权重和     */
    VCount = 0;      /* 初始化收录的顶点数 */
    /* 创建包含所有顶点但没有边的图。注意用邻接表版本 */
    MST = CreateGraph(Graph->Nv);
    E = (Edge)malloc( sizeof(struct ENode) ); /* 建立空的边结点 */
            
    /* 将初始点0收录进MST */
    dist[0] = 0;
    VCount ++;
    parent[0] = -1; /* 当前树根是0 */
 
    while (1) {
        V = FindMinDist( Graph, dist );
        /* V = 未被收录顶点中dist最小者 */
        if ( V==ERROR ) /* 若这样的V不存在 */
            break;   /* 算法结束 */
             
        /* 将V及相应的边<parent[V], V>收录进MST */
        E->V1 = parent[V];
        E->V2 = V;
        E->Weight = dist[V];
        InsertEdge( MST, E );
        TotalWeight += dist[V];
        dist[V] = 0;
        VCount++;
         
        for( W=0; W<Graph->Nv; W++ ) /* 对图中的每个顶点W */
            if ( dist[W]!=0 && Graph->G[V][W]<INFINITY ) {
            /* 若W是V的邻接点并且未被收录 */
                if ( Graph->G[V][W] < dist[W] ) {
                /* 若收录V使得dist[W]变小 */
                    dist[W] = Graph->G[V][W]; /* 更新dist[W] */
                    parent[W] = V; /* 更新树 */
                }
            }
    } /* while结束*/
    if ( VCount < Graph->Nv ) /* MST中收的顶点不到|V|个 */
       TotalWeight = ERROR;
    return TotalWeight;   /* 算法执行完毕,返回最小权重和或错误标记 */
}

C语言:邻接表存储 - Kruskal最小生成树算法

/* 邻接表存储 - Kruskal最小生成树算法 */
 
/*-------------------- 顶点并查集定义 --------------------*/
typedef Vertex ElementType; /* 默认元素可以用非负整数表示 */
typedef Vertex SetName;     /* 默认用根结点的下标作为集合名称 */
typedef ElementType SetType[MaxVertexNum]; /* 假设集合元素下标从0开始 */
 
void InitializeVSet( SetType S, int N )
{ /* 初始化并查集 */
    ElementType X;
 
    for ( X=0; X<N; X++ ) S[X] = -1;
}
 
void Union( SetType S, SetName Root1, SetName Root2 )
{ /* 这里默认Root1和Root2是不同集合的根结点 */
    /* 保证小集合并入大集合 */
    if ( S[Root2] < S[Root1] ) { /* 如果集合2比较大 */
        S[Root2] += S[Root1];     /* 集合1并入集合2  */
        S[Root1] = Root2;
    }
    else {                         /* 如果集合1比较大 */
        S[Root1] += S[Root2];     /* 集合2并入集合1  */
        S[Root2] = Root1;
    }
}
 
SetName Find( SetType S, ElementType X )
{ /* 默认集合元素全部初始化为-1 */
    if ( S[X] < 0 ) /* 找到集合的根 */
        return X;
    else
        return S[X] = Find( S, S[X] ); /* 路径压缩 */
}
 
bool CheckCycle( SetType VSet, Vertex V1, Vertex V2 )
{ /* 检查连接V1和V2的边是否在现有的最小生成树子集中构成回路 */
    Vertex Root1, Root2;
 
    Root1 = Find( VSet, V1 ); /* 得到V1所属的连通集名称 */
    Root2 = Find( VSet, V2 ); /* 得到V2所属的连通集名称 */
 
    if( Root1==Root2 ) /* 若V1和V2已经连通,则该边不能要 */
        return false;
    else { /* 否则该边可以被收集,同时将V1和V2并入同一连通集 */
        Union( VSet, Root1, Root2 );
        return true;
    }
}
/*-------------------- 并查集定义结束 --------------------*/
 
/*-------------------- 边的最小堆定义 --------------------*/
void PercDown( Edge ESet, int p, int N )
{ /* 改编代码4.24的PercDown( MaxHeap H, int p )    */
  /* 将N个元素的边数组中以ESet[p]为根的子堆调整为关于Weight的最小堆 */
    int Parent, Child;
    struct ENode X;
 
    X = ESet[p]; /* 取出根结点存放的值 */
    for( Parent=p; (Parent*2+1)<N; Parent=Child ) {
        Child = Parent * 2 + 1;
        if( (Child!=N-1) && (ESet[Child].Weight>ESet[Child+1].Weight) )
            Child++;  /* Child指向左右子结点的较小者 */
        if( X.Weight <= ESet[Child].Weight ) break; /* 找到了合适位置 */
        else  /* 下滤X */
            ESet[Parent] = ESet[Child];
    }
    ESet[Parent] = X;
}
 
void InitializeESet( LGraph Graph, Edge ESet )
{ /* 将图的边存入数组ESet,并且初始化为最小堆 */
    Vertex V;
    PtrToAdjVNode W;
    int ECount;
 
    /* 将图的边存入数组ESet */
    ECount = 0;
    for ( V=0; V<Graph->Nv; V++ )
        for ( W=Graph->G[V].FirstEdge; W; W=W->Next )
            if ( V < W->AdjV ) { /* 避免重复录入无向图的边,只收V1<V2的边 */
                ESet[ECount].V1 = V;
                ESet[ECount].V2 = W->AdjV;
                ESet[ECount++].Weight = W->Weight;
            }
    /* 初始化为最小堆 */
    for ( ECount=Graph->Ne/2; ECount>=0; ECount-- )
        PercDown( ESet, ECount, Graph->Ne );
}
 
int GetEdge( Edge ESet, int CurrentSize )
{ /* 给定当前堆的大小CurrentSize,将当前最小边位置弹出并调整堆 */
 
    /* 将最小边与当前堆的最后一个位置的边交换 */
    Swap( &ESet[0], &ESet[CurrentSize-1]);
    /* 将剩下的边继续调整成最小堆 */
    PercDown( ESet, 0, CurrentSize-1 );
 
    return CurrentSize-1; /* 返回最小边所在位置 */
}
/*-------------------- 最小堆定义结束 --------------------*/
 
 
int Kruskal( LGraph Graph, LGraph MST )
{ /* 将最小生成树保存为邻接表存储的图MST,返回最小权重和 */
    WeightType TotalWeight;
    int ECount, NextEdge;
    SetType VSet; /* 顶点数组 */
    Edge ESet;    /* 边数组 */
 
    InitializeVSet( VSet, Graph->Nv ); /* 初始化顶点并查集 */
    ESet = (Edge)malloc( sizeof(struct ENode)*Graph->Ne );
    InitializeESet( Graph, ESet ); /* 初始化边的最小堆 */
    /* 创建包含所有顶点但没有边的图。注意用邻接表版本 */
    MST = CreateGraph(Graph->Nv);
    TotalWeight = 0; /* 初始化权重和     */
    ECount = 0;      /* 初始化收录的边数 */
 
    NextEdge = Graph->Ne; /* 原始边集的规模 */
    while ( ECount < Graph->Nv-1 ) {  /* 当收集的边不足以构成树时 */
        NextEdge = GetEdge( ESet, NextEdge ); /* 从边集中得到最小边的位置 */
        if (NextEdge < 0) /* 边集已空 */
            break;
        /* 如果该边的加入不构成回路,即两端结点不属于同一连通集 */
        if ( CheckCycle( VSet, ESet[NextEdge].V1, ESet[NextEdge].V2 )==true ) {
            /* 将该边插入MST */
            InsertEdge( MST, ESet+NextEdge );
            TotalWeight += ESet[NextEdge].Weight; /* 累计权重 */
            ECount++; /* 生成树中边数加1 */
        }
    }
    if ( ECount < Graph->Nv-1 )
        TotalWeight = -1; /* 设置错误标记,表示生成树不存在 */
 
    return TotalWeight;
}

C语言:邻接表存储 - 拓扑排序算法

/* 邻接表存储 - 拓扑排序算法 */
 
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;
}

08-图7 公路村村通 (30 分)

现有村落间道路的统计数据表中,列出了有可能建设成标准公路的若干条道路的成本,求使每个村落都有公路连通所需要的最低成本。
输入格式:
输入数据包括城镇数目正整数N(≤1000)和候选道路数目M(≤3N);随后的M行对应M条道路,每行给出3个正整数,分别是该条道路直接连通的两个城镇的编号以及该道路改建的预算成本。为简单起见,城镇从1到N编号。
输出格式:
输出村村通需要的最低成本。如果输入数据不足以保证畅通,则输出−1,表示需要建设更多公路。
本章均为非原创,代码来源:https://blog.youkuaiyun.com/xijujie/article/details/53085595


#include <iostream>
#include <queue>
#include <vector>
struct Edge {
	int v;
	int w;
	int cost;
};
struct cmp {
	bool operator()(Edge *e1, Edge *e2){
		return e1->cost > e2->cost;
	}
};
int find(int x, std::vector<int> &disjset);
void setunion(int root1, int root2, std::vector<int> &disjset);
 
int main() {
	std::priority_queue<Edge*, std::vector<Edge*>, cmp> q;
	Edge *e;
	int n, m, i, v, w, cost, root1, root2, count = 0, mincost = 0;
	scanf("%d%d", &n, &m);
	std::vector<int> disjset(n, -1);
	for (i = 0;i < m;i++) {
		scanf("%d%d%d", &v, &w, &cost);
		e = new Edge;
		e->v = v-1;
		e->w = w-1;
		e->cost = cost;
		q.push(e);
	}
	while (count < n - 1 && !q.empty()) {
		e = q.top();
		q.pop();
		root1 = find(e->v, disjset);
		root2 = find(e->w, disjset);
		if (root1 != root2) {
			count++;
			mincost += e->cost;
			setunion(root1, root2, disjset);
		}	
	}
	if (count < n - 1) printf("-1");
	else printf("%d", mincost);
	return 0;
}
 
int find(int x, std::vector<int> &disjset) {
	if (disjset[x] < 0)
		return x;
	else
		return disjset[x] = find(disjset[x], disjset);
}
 
void setunion(int root1, int root2, std::vector<int> &disjset) {
	if (root1 < root2)
		disjset[root2] = root1;
	else {
		if (root1 == root2)
			root2--;
		disjset[root1] = root2;
	}
}

08-图8 How Long Does It Take (25 分)

Given the relations of all the activities of a project, you are supposed to find the earliest completion time of the project.
Input Specification:
Each input file contains one test case. Each case starts with a line containing two positive integers N (≤100), the number of activity check points (hence it is assumed that the check points are numbered from 0 to N−1), and M, the number of activities. Then M lines follow, each gives the description of an activity. For the i-th activity, three non-negative numbers are given: S[i], E[i], and L[i], where S[i] is the index of the starting check point, E[i] of the ending check point, and L[i] the lasting time of the activity. The numbers in a line are separated by a space.
Output Specification:
For each test case, if the scheduling is possible, print in a line its earliest completion time; or simply output “Impossible”.
本章均为非原创,代码来源:https://www.cnblogs.com/ch122633/p/9007751.html

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

#define ERROR -1
#define false  0
#define true   1
#define MaxVertexNum 100
#define INFINITY     65535
#define MaxQueue 100
typedef int Vertex;
typedef int WeightType;
typedef int bool;

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

//邻接点
typedef struct AdjVNode *PtrToAdjVNode;
struct AdjVNode {
    Vertex AdjV;            //下标
    WeightType Weight;      //边权重
    PtrToAdjVNode Next;     //指向下一个邻接点
};

//顶点
typedef struct VNode {
    PtrToAdjVNode FirstEdge;    //边表头指针
    // DataType Data;              //存顶点的户数据
}AdjList[MaxVertexNum];

//图结点
typedef struct GNode *PtrToGNode;
struct GNode {
    int Nv;
    int Ne;
    AdjList G;
};
typedef PtrToGNode LGraph;

struct QNode {
    Vertex Data[MaxQueue];
    int rear;
    int front;
};
typedef struct QNode *Queue;

int IsEmptyQ(Queue PtrQ)
{
    return (PtrQ->front == PtrQ->rear);
}

void AddQ(Queue PtrQ, Vertex item)
{
    if((PtrQ->rear+1)%MaxQueue == PtrQ->front) {
        printf("Queue full");
        return;
    }
    PtrQ->rear = (PtrQ->rear+1)%MaxQueue;
    PtrQ->Data[PtrQ->rear] = item;
}

Vertex DeleteQ(Queue PtrQ)
{
    if(PtrQ->front == PtrQ->rear) {
        printf("Queue Empty");
        return -1;
    } else {
        PtrQ->front = (PtrQ->front+1)%MaxQueue;
        return PtrQ->Data[PtrQ->front];
    }
}

LGraph CreateGraph(int VertexNum)
{
    Vertex V;
    LGraph Graph;

    Graph = (LGraph)malloc(sizeof(struct GNode));
    Graph->Nv = VertexNum;
    Graph->Ne = 0;

    for(V=0;V<Graph->Nv;V++)
        Graph->G[V].FirstEdge = NULL;

    return Graph;
}

void InsertEdge(LGraph Graph, Edge E)
{
    PtrToAdjVNode NewNode;

    //有向边
    NewNode = (PtrToAdjVNode)malloc(sizeof(struct AdjVNode));
    NewNode->AdjV = E->V2;
    NewNode->Weight = E->Weight;
    //向V1插入V2
    NewNode->Next = Graph->G[E->V1].FirstEdge;
    Graph->G[E->V1].FirstEdge = NewNode;
}

LGraph BuildGraph()
{
    LGraph Graph;
    Edge E;
    int Nv, i;

    scanf("%d", &Nv);
    Graph = CreateGraph(Nv);

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

    return Graph;
}

void PrintGraph(LGraph Graph)
{
    Vertex V;
    PtrToAdjVNode W;
    for(V=0;V<Graph->Nv;V++) {
        printf("%d:", V);
        for(W=Graph->G[V].FirstEdge;W;W=W->Next) {
            printf("[%3d %3d] ", W->AdjV, W->Weight);
        }        
        printf("\n");
    }
}



/* 邻接表存储 - 拓扑排序算法 */ 
bool TopSort( LGraph Graph, Vertex TopOrder[], Vertex Earliest[])
{ /* 对Graph进行拓扑排序,  TopOrder[]顺序存储排序后的顶点下标 */
    int Indegree[MaxVertexNum], cnt;
    Vertex V;
    PtrToAdjVNode W;
       
    Queue Q = (Queue)malloc(sizeof(struct QNode)*( 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);
            Earliest[V] = 0;         //起点为0
        }
             
    /* 下面进入拓扑排序 */ 
    cnt = 0; 
    while( !IsEmptyQ(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); /* 则该顶点入列 */ 
                Earliest[W->AdjV] = Earliest[V] + W->Weight;
                // if((Earliest[V]+W->Weight)>Earliest[W->AdjV] && Earliest[W->AdjV])
                    // Earliest[W->AdjV] = Earliest[V] + W->Weight;
            }
    } /* while结束*/
     
    if ( cnt != Graph->Nv )
        return false; /* 说明图中有回路, 返回不成功标志 */ 
    else
        return true;
}

int main()
{
    LGraph Graph;
    WeightType Earliest[MaxVertexNum];
    Vertex TopOrder[MaxVertexNum],V;
    int ret;

    Graph = BuildGraph();
    // PrintGraph(Graph);
    ret = TopSort(Graph, TopOrder, Earliest);
    if(ret == false) {
        printf("Impossible");
    } else if(ret == true) {
        int max = Earliest[0];
        for(int i=0;i<Graph->Nv;i++) {
            // printf("%d: [%d]\n", i, Earliest[i]);
            if(max < Earliest[i])
                max = Earliest[i];
        }
        printf("%d", max);
    }
    // printf("TopOrder:");
    // for(V=0;V<Graph->Nv;V++)
    //     printf("%d ", TopOrder[V]);
    // printf("\n");
    return 0;
}

参考代码二:https://blog.youkuaiyun.com/qq_37513086/article/details/80079312

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<queue>
using namespace std;
const int N = 102;
queue<int> q;
int graph[N][N] = { 0 };
int inDegree[N] = { 0 };
int dist[N];
bool topsort(int n) {
	//初始化dist 
	for (int i = 0; i < n; i++) {
		dist[i] = -1;
	}
	int cur = 0;
	for (int i = 0; i < n; i++) {
		if (inDegree[i] == 0) {
			q.push(i);
			dist[i] = 0;
			for (int j = 0; j < n; j++) {
				if (graph[i][j] > 0) {
					dist[j] = graph[i][j];
				}
			}	
		}		
	}

	int cnt = 0;
	while (!q.empty()) {
		cur = q.front();
		q.pop();
		cnt++;
		for (int i = 0; i < n; i++) {
			if (graph[cur][i] >= 0) {
				inDegree[i]--;
				if (dist[cur] + graph[cur][i] > dist[i]) {
					dist[i] = graph[cur][i] + dist[cur];
				}
				if (inDegree[i] == 0) {
					q.push(i);
				}
			}
		}
	}
	if (cnt != n) {
		return false;
	}
	return true;
}
int main() {
	int n, m;
	cin >> n >> m;
	for (int i = 0;  i < n; i++) {
		for (int j = 0; j < n; j++) {
			graph[i][j] = -1;
		}
	}
	while (m--) {
		int s, e, l;
		cin >> s >> e >> l;
		graph[s][e] = l;
		inDegree[e]++;
	}
	
	if (!topsort(n))
		cout << "Impossible" << endl;
	else {
		int max = -1;
		for (int i = 0; i < n; i++) {
			max = max > dist[i] ? max : dist[i];
		}
		cout << max << endl;
	}
	return 0;
}

08-图9 关键活动 (30 分)

假定一个工程项目由一组子任务构成,子任务之间有的可以并行执行,有的必须在完成了其它一些子任务后才能执行。“任务调度”包括一组子任务、以及每个子任务可以执行所依赖的子任务集。
比如完成一个专业的所有课程学习和毕业设计可以看成一个本科生要完成的一项工程,各门课程可以看成是子任务。有些课程可以同时开设,比如英语和C程序设计,它们没有必须先修哪门的约束;有些课程则不可以同时开设,因为它们有先后的依赖关系,比如C程序设计和数据结构两门课,必须先学习前者。
但是需要注意的是,对一组子任务,并不是任意的任务调度都是一个可行的方案。比如方案中存在“子任务A依赖于子任务B,子任务B依赖于子任务C,子任务C又依赖于子任务A”,那么这三个任务哪个都不能先执行,这就是一个不可行的方案。
任务调度问题中,如果还给出了完成每个子任务需要的时间,则我们可以算出完成整个工程需要的最短时间。在这些子任务中,有些任务即使推迟几天完成,也不会影响全局的工期;但是有些任务必须准时完成,否则整个项目的工期就要因此延误,这种任务就叫“关键活动”。
请编写程序判定一个给定的工程项目的任务调度是否可行;如果该调度方案可行,则计算完成整个工程项目需要的最短时间,并输出所有的关键活动。
输入格式:
输入第1行给出两个正整数N(≤100)和M,其中N是任务交接点(即衔接相互依赖的两个子任务的节点,例如:若任务2要在任务1完成后才开始,则两任务之间必有一个交接点)的数量。交接点按1N编号,M是子任务的数量,依次编号为1M。随后M行,每行给出了3个正整数,分别是该任务开始和完成涉及的交接点编号以及该任务所需的时间,整数间用空格分隔。
输出格式:
如果任务调度不可行,则输出0;否则第1行输出完成整个工程项目需要的时间,第2行开始输出所有关键活动,每个关键活动占一行,按格式“V->W”输出,其中V和W为该任务开始和完成涉及的交接点编号。关键活动输出的顺序规则是:任务开始的交接点编号小者优先,起点编号相同时,与输入时任务的顺序相反。
本章均为非原创,代码来源:https://blog.youkuaiyun.com/chcnsn/article/details/72782722

/*
这题就是拓扑排序找关键路径,按照陈老师所讲的算法,先建立一个正向链接的邻接表,在建立一个反向链接的邻接表
来记录出度与入读,注意关键路径是指决不允许延误的活动D<i, j> = Lt[j] - Et[i] - C<i, j>
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define Maxn 1005
#define INF -100005
#define M 100005
//建立邻接表所需的结构体
typedef struct AdjNode* AdjList;
struct AdjNode {
    int index;
    int Weight;
    AdjList Next;
};
 
typedef struct HNode {
    AdjList FirstEdge; 
}List[Maxn];
 
typedef struct GNode* LGraph;
struct GNode {
    int Nv;
    List Head;
};
 
typedef struct ENode* Edge;
struct ENode {
    int V1, V2;
    int Weight;
};
//建一个G2用于反向链接邻接表,便于记录出度,方便反向递推求各点的最迟时间
LGraph G2;
 
void InsertEdge(LGraph G, Edge E) {
    AdjList NewNode;
    NewNode = (AdjList)malloc(sizeof(struct AdjNode));
    NewNode->index = E->V2;
    NewNode->Weight = E->Weight;
    NewNode->Next = G->Head[E->V1].FirstEdge;
    G->Head[E->V1].FirstEdge = NewNode;
 
    NewNode = (AdjList)malloc(sizeof(struct AdjNode));
    NewNode->index = E->V1;
    NewNode->Weight = E->Weight;
    NewNode->Next = G2->Head[E->V2].FirstEdge;
    G2->Head[E->V2].FirstEdge = NewNode;
}
 
LGraph CreateLGraph(int n, int m) {
    int v;
    LGraph G = (LGraph)malloc(sizeof(struct GNode));
    Edge E = (Edge)malloc(sizeof(struct ENode));
    G2 = (LGraph)malloc(sizeof(struct GNode));
    G->Nv = n; G2->Nv = n;
    for(v = 1; v <= n; v++)
        { G->Head[v].FirstEdge = NULL; G2->Head[v].FirstEdge = NULL; }
    for(v = 1; v <= m; v++) {
        scanf("%d%d%d", &E->V1, &E->V2, &E->Weight);
        InsertEdge(G, E);
    }
    return G;
}
 
void TopSort(LGraph G) {
    int Indegree[Maxn], Outdegree[Maxn], ETime[Maxn], LTime[Maxn], cnt = 0, t = INF;
    int Q[Maxn], front, rear;
    int v;
    AdjList w;
    //计算最早时间
    front = rear = 0;
    //初始化入度数
    for(v = 1; v <= G->Nv; v++) {
        ETime[v] = 0;
        Indegree[v] = 0;
    }
    for(v = 1; v <= G->Nv; v++)
        for(w = G->Head[v].FirstEdge; w; w = w->Next)
            ++Indegree[w->index];
    //入度为零的节点入队,并应初始化起始时间为0
    for(v = 1; v <= G->Nv; v++) if(!Indegree[v]) Q[++rear] = v;
    while(rear != front) {
        v = Q[++front];
        cnt++;
        for(w = G->Head[v].FirstEdge; w; w = w->Next) {
            //Et[w] = max(Et[v] + c<i, j>)
            if(ETime[v] + w->Weight > ETime[w->index]) ETime[w->index] = ETime[v] + w->Weight;
            if(--Indegree[w->index] == 0) Q[++rear] = w->index;
        }
    }//printf("%d\n", cnt);  0
    //若多个终点,找最大时间作为整个过程最早时间
    for(v = 1; v <= cnt; v++)
            if(t < ETime[v]) t = ETime[v];
    //计算每个节点最迟时间,注意先将每个终点的最早时间改成计算出的t
    front = rear = 0;
    for(v = 1; v <= G2->Nv; v++) {
        LTime[v] = M;
        Outdegree[v] = 0;
    }
    for(v = 1; v <= G2->Nv; v++) {
        for(w = G2->Head[v].FirstEdge; w; w = w->Next)
            ++Outdegree[w->index];
    }
    for(v = 1; v <= G2->Nv; v++)
        if(!Outdegree[v]) {
            //printf("%d\n", v);
            LTime[v] = t;
            Q[++rear] = v;
        }//for(v = 1; v <= G->Nv; v++) printf("E = %d, L = %d\n", ETime[v], LTime[v]);
    while(rear != front) {
        v = Q[++front];
        for(w = G2->Head[v].FirstEdge; w; w = w->Next) {
            if(--Outdegree[w->index] == 0) Q[++rear] = w->index;
            if(LTime[w->index] > LTime[v] - w->Weight) LTime[w->index] = LTime[v] - w->Weight;
        }
    }//for(v = 1; v <= G->Nv; v++) printf("E = %d, L = %d\n", ETime[v], LTime[v]);
    if(cnt == G->Nv) {
        printf("%d\n", t);
        for(v = 1; v <= cnt; v++)
            for(w = G->Head[v].FirstEdge; w; w = w->Next)
                if(LTime[w->index] - w->Weight == ETime[v]) printf("%d->%d\n", v, w->index);
    }
    else printf("0\n");
}
 
int main() {
    int n, m;
    LGraph G;
    scanf("%d%d", &n, &m);
    G = CreateLGraph(n, m);
    TopSort(G);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值