数据结构_04_图

在这里插入图片描述

一、基本概念与存储结构

1. 基本概念
  1. 图:由顶点的有穷集合 V 和边的集合 E 所组成(有向图、无向图);
  2. 顶点的与顶点 v 相关的边的条数成为顶点 v 的度(出度、入度);
  3. 若有向图中有 nnn 个顶点,则最多有 n(n−1)n(n-1)n(n1) 条边,则将这样的图称为有向完全图 ,对于无向图,则最多有 n(n−1)/2n(n-1)/2n(n1)/2 条边,称其为无向完全图
  4. 路径、路径长度、简单路径(不含重复顶点);
  5. 无向图中,如果从顶点 viv_ivi 到顶点 vjv_jvj 有路径,则称 viv_ivivjv_jvj 连通。如果图中任意两个顶点之间都连通,则称该图为连通图;否则,图中的极大连通子图称为连通分量
  6. 有向图中,如果从顶点 viv_ivi 到顶点 vjv_jvj 有路径,则称 viv_ivivjv_jvj 连通。如果对于每一对顶点 viv_ivivjv_jvj ,从 vjv_jvjviv_ivi 和从 viv_ivivjv_jvj 都有路径,则称该图为强连通图;否则,图中的极大强连通子图称为强连通分量
2. 存储结构
  1. 顺序存储结构:邻接矩阵
// 图 -- 邻接矩阵
#define MaxVertexNum 10    /* 最大顶点数设为10 */
typedef int Vertex;         /* 用顶点下标表示顶点,为整型 */
typedef int WeightType;		/* 权重值类型 */

/* 边的定义 */
typedef struct ENode *PtrToENode;
struct ENode {
	Vertex V1, V2;      /* 有向边<V1, V2> */
	// DataType Weight  权重值
};
typedef PtrToENode Edge;

/* 图结点的定义 */
typedef struct GNode *PtrToGNode;
struct GNode {
	int Nv;     /* 顶点数 */
	int Ne;     /* 边数   */
	WeightType G[MaxVertexNum][MaxVertexNum];	//二维数组表示邻接矩阵
};
typedef PtrToGNode MGraph; /* 以邻接矩阵方式存储的图类型 */

MGraph CreateGraph(int VertexNum)
{ /* 初始化一个有VertexNum个顶点但没有边的图 */
	Vertex V, W;
	MGraph Graph;

	Graph = (MGraph)malloc(sizeof(struct GNode)); /* 建立图 */
	Graph->Nv = VertexNum;
	Graph->Ne = 0;

	/* 初始化邻接矩阵 */
	for (V = 0; V < Graph->Nv; V++)
		for (W = 0; W < Graph->Nv; W++)
			Graph->G[V][W] = 0;		// 或 无穷大
	return Graph;
}

void InsertEdge(MGraph Graph, Edge E)
{
	/* 插入边 <V1, V2> */
	Graph->G[E->V1][E->V2] = 1;		//或是边的权重值

	/* 若是无向图,还要插入边 <V2, V1> */	// 坑点
	Graph->G[E->V2][E->V1] = 1;
}

MGraph BuildGraph()
{
	MGraph Graph;
	Edge E;
	Vertex V;
	int Nv, i;

	cin >> Nv;   /* 读入顶点个数 */
	Graph = CreateGraph(Nv); /* 初始化有Nv个顶点但没有边的图 */

	cin >> Graph->Ne;   /* 读入边数 */
	if (Graph->Ne != 0) { /* 如果有边 */
		E = (Edge)malloc(sizeof(struct ENode)); /* 建立边结点 */
		/* 读入边,格式为"起点 终点 权重",插入邻接矩阵 */
		for (i = 0; i < Graph->Ne; i++) {
			cin >> E->V1 >> E->V2;
			/* 注意:如果权重不是整型,Weight的读入格式要改 */
			InsertEdge(Graph, E);
		}
	}
	return Graph;
}

bool IsEdge(MGraph Graph, Vertex V, Vertex W)
{
	return Graph->G[V][W];
}
  1. 链式存储结构:邻接表
/* 图 -- 邻接表 */
#define MaxVertexNum 100    /* 最大顶点数设为100 */
typedef int Vertex;         /* 用顶点下标表示顶点,为整型 */
typedef int WeightType;        /* 边的权值设为整型 */
typedef char DataType;        /* 顶点存储的数据类型设为字符型 */

/* 边的定义 */
typedef struct ENode *PtrToENode;
struct ENode {
	Vertex V1, V2;      /* 有向边<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;            /* 存顶点的数据 */
	/* 注意:很多情况下,顶点无数据,此时Data可以不用出现 */
} AdjList[MaxVertexNum];    /* AdjList是邻接表类型 */

/* 图结点的定义 */
typedef struct GNode *PtrToGNode;
struct GNode {
	int Nv;     /* 顶点数 */
	int Ne;     /* 边数   */
	AdjList G;  /* 邻接表 */
};
typedef PtrToGNode LGraph; /* 以邻接表方式存储的图类型 */

LGraph CreateGraph(int VertexNum)
{ /* 初始化一个有VertexNum个顶点但没有边的图 */
	Vertex V;
	LGraph Graph;

	Graph = (LGraph)malloc(sizeof(struct GNode)); /* 建立图 */
	Graph->Nv = VertexNum;
	Graph->Ne = 0;
	/* 初始化邻接表头指针 */
	/* 注意:这里默认顶点编号从0开始,到(Graph->Nv - 1) */
	for (V = 0; V < Graph->Nv; V++)
		Graph->G[V].FirstEdge = NULL;
	return Graph;
}

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

	/* 插入边 <V1, V2> */
	/* 为V2建立新的邻接点 */
	NewNode = (PtrToAdjVNode)malloc(sizeof(struct AdjVNode));
	NewNode->AdjV = E->V2;
	// NewNode->Weight = E->Weight;
	/* 将V2插入V1的表头 */
	NewNode->Next = Graph->G[E->V1].FirstEdge;	//注意此处的插入方式为:插入头结点
	Graph->G[E->V1].FirstEdge = NewNode;

	/* 若是无向图,还要插入边 <V2, V1> */
	/* 为V1建立新的邻接点 */
	NewNode = (PtrToAdjVNode)malloc(sizeof(struct AdjVNode));
	NewNode->AdjV = E->V1;
	//NewNode->Weight = E->Weight;
	/* 将V1插入V2的表头 */
	NewNode->Next = Graph->G[E->V2].FirstEdge;
	Graph->G[E->V2].FirstEdge = NewNode;
}

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

	cin >> Nv;   /* 读入顶点个数 */
	Graph = CreateGraph(Nv); /* 初始化有Nv个顶点但没有边的图 */
	cin >> Graph->Ne;   /* 读入边数 */
	if (Graph->Ne != 0) { /* 如果有边 */
		E = (Edge)malloc(sizeof(struct ENode)); /* 建立边结点 */
		/* 读入边,格式为"起点 终点 权重",插入邻接矩阵 */
		for (i = 0; i < Graph->Ne; i++) {
			cin >> E->V1 >> E->V2;
			/* 注意:如果权重不是整型,Weight的读入格式要改 */
			InsertEdge(Graph, E);
		}
	}

	/* 如果顶点有数据的话,读入数据 
	for (V = 0; V < Graph->Nv; V++)
		scanf(" %c", &(Graph->G[V].Data));
	*/
	return Graph;
}
  1. 邻接多重表:类似十字链表
3. 图的遍历
  1. 深度优先搜索(DFS)
// 邻接矩阵
/* Visited[]为全局变量,已经初始化为false */
bool Visited[MaxVertexNum];
void DFS(MGraph Graph, Vertex V)
{   /* 以V为出发点对邻接矩阵存储的图Graph进行DFS搜索 */
	Visited[V] = true; /* 标记V已访问 */
	for (int i = 0; i < Graph->Nv; i++)
		if (Graph->G[V][i] == 1 && !Visited[i])
			DFS(Graph, i);
}

// 邻接表
/* Visited[]为全局变量,已经初始化为false */
bool Visited[MaxVertexNum];
void DFS(LGraph Graph, Vertex V)
{   /* 以V为出发点对邻接表存储的图Graph进行DFS搜索 */
	PtrToAdjVNode W;
	Visited[V] = true; /* 标记V已访问 */
	for (W = Graph->G[V].FirstEdge; W; W = W->Next) /* 对V的每个邻接点W->AdjV */
		if (!Visited[W->AdjV])    /* 若W->AdjV未被访问 */
			DFS(Graph, W->AdjV);    /* 则递归访问之 */
}
  1. 广度优先搜索(BFS)
// 邻接矩阵
void BFS(MGraph Graph)
{
	queue<Vertex> q;
	Vertex V;
	for (int i = 0; i < Graph->Nv; i++)
	{
		if (!Visited[i]){	//如果没有访问过
			Visited[i] = true;
			q.push(i);//访问过的入队列

			while (!q.empty()){		//队列不为空时
				V = q.front();
				q.pop();	//先取出队首第一个元素,然后将第一个元素删除
				for(int i = 0; i < Graph->Nv; i++){
					if (Graph->G[V][i] == 1 && !Visited[i]) {
						Visited[i] = true;
						q.push(i);
					}
				}
			}
		}
	}
}

// 邻接表
void BFS(LGraph Graph)
{
	queue<Vertex> q;
	Vertex V;
	for (int i = 0; i < Graph->Nv; i++){
		if (!Visited[i]){	//如果没有访问过
			Visited[i] = true;
			q.push(i);//访问过的入队列

			while (!q.empty())//队列不为空时
			{
				V = q.front();
				q.pop();	//先取出队首第一个元素,然后将第一个元素删除

				PtrToAdjVNode W = Graph->G[V].FirstEdge;
				while (W) {
					if (!Visited[W->AdjV]) {
						Visited[W->AdjV] = true;
						cout << W->AdjV << " ";
						q.push(W->AdjV);
					}
					W = W->Next;
				}
			}
		}
	}
}

注:

  1. DFS 类似于二叉树的先序遍历,若将 DFS 过程中所经历的边保留,而将其他的边删掉,则会形成一棵树,称作深度优先搜索生成树
  2. BFS 类似于二叉树的层序遍历
  3. 一个无向图是一棵树的条件是有 n−1n-1n1 条边的连通图,nnn 为图的顶点数;

二、最短路径

  1. 无权图的单源最短路径:BFS 改编
/* 邻接表存储 -- 无权图的单源最短路算法 */
/* dist[]和path[]全部初始化为-1 */
void Unweighted(LGraph Graph, int dist[], int path[], Vertex S)
{
	queue<Vertex> Q;
	Vertex V;
	PtrToAdjVNode W;

	dist[S] = 0; /* 初始化源点 */
	Q.push(S);

	while (!Q.empty()) {
		V = Q.front();
		Q.pop();
		for (W = Graph->G[V].FirstEdge; W; W = W->Next) /* 对V的每个邻接点W->AdjV */
			if (dist[W->AdjV] == -1) { /* 若W->AdjV未被访问过 */
				dist[W->AdjV] = dist[V] + 1; /* W->AdjV到S的距离更新 */
				path[W->AdjV] = V; /* 将V记录在S到W->AdjV的路径上 */
				Q.push(W->AdjV);
			}
	}
}
  1. 有权图的单源最短路径:Dijkstra 算法
/* 邻接矩阵存储 - 有权图的单源最短路算法 */
#define ERROR -1
Vertex FindMinDist(MGraph Graph, int dist[], int collected[])
{ /* 返回未被收录顶点中dist最小者 */
	Vertex MinV, V;
	int MinDist = INFINITY;

	for (V = 0; V < Graph->Nv; V++) {
		if (collected[V] == false && dist[V] < MinDist) {
			/* 若V未被收录,且dist[V]更小 */
			MinDist = dist[V]; /* 更新最小距离 */
			MinV = V; /* 更新对应顶点 */
		}
	}
	if (MinDist < INFINITY) /* 若找到最小dist */
		return MinV; /* 返回对应的顶点下标 */
	else return ERROR;  /* 若这样的顶点不存在,返回错误标记 */
}

bool Dijkstra(MGraph Graph, int dist[], int path[], Vertex S)
{
	int collected[MaxVertexNum];
	Vertex V, W;

	/* 初始化:此处默认邻接矩阵中不存在的边用INFINITY表示 */
	for (V = 0; V < Graph->Nv; V++) {
		dist[V] = Graph->G[S][V];
		if (dist[V] < INFINITY)
			path[V] = S;
		else
			path[V] = -1;
		collected[V] = false;
	}
	/* 先将起点收入集合 */
	dist[S] = 0;
	collected[S] = true;

	while (1) {
		/* V = 未被收录顶点中dist最小者 */
		V = FindMinDist(Graph, dist, collected);
		if (V == ERROR) /* 若这样的V不存在 */
			break;      /* 算法结束 */
		collected[V] = true;  /* 收录V */
		for (W = 0; W < Graph->Nv; W++) /* 对图中的每个顶点W */
			/* 若W是V的邻接点并且未被收录 */
			if (collected[W] == false && Graph->G[V][W] < INFINITY) {
				if (Graph->G[V][W] < 0) /* 若有负边 */
					return false; /* 不能正确解决,返回错误标记 */
				/* 若收录V使得dist[W]变小 */
				if (dist[V] + Graph->G[V][W] < dist[W]) {
					dist[W] = dist[V] + Graph->G[V][W]; /* 更新dist[W] */
					path[W] = V; /* 更新S到W的路径 */
				}
			}
	} /* while结束*/
	return true; /* 算法执行完毕,返回正确标记 */
}
  1. 多源最短路径:Floyd 算法
/* 邻接矩阵存储 - 多源最短路算法 */
bool Floyd(MGraph Graph, WeightType D[][MaxVertexNum], Vertex path[][MaxVertexNum])
{
	Vertex i, j, k;
	/* 初始化 */
	for (i = 0; i < Graph->Nv; i++)
		for (j = 0; j < Graph->Nv; j++) {
			D[i][j] = Graph->G[i][j];	// 有向图
			path[i][j] = -1;
		}
	
	// k 是 中间点,遍历所有顶点对(i, j)
	for (k = 0; k < Graph->Nv; k++)
		for (i = 0; i < Graph->Nv; i++)
			for (j = 0; j < Graph->Nv; j++)
				if (D[i][k] + D[k][j] < D[i][j]) {
					D[i][j] = D[i][k] + D[k][j];
					if (i == j && D[i][j] < 0) /* 若发现负值圈 */
						return false; /* 不能正确解决,返回错误标记 */
					path[i][j] = k;
				}
	return true; /* 算法执行完毕,返回正确标记 */
}

三、最小生成树

  1. 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;   /* 算法执行完毕,返回最小权重和或错误标记 */
}
  1. 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;
}

四、拓扑排序

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

  1. AOE 网:关键活动关键路径(最长路径,最短时间)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值