图算法入门3:活动网络-AOV网络和拓扑排序

AOV网络

通常一个工程可以分成若干个子工程,这些子工程被称为活动(activity),完成这些活动,整个工程就完成了。给一个简单的例子,如下图,大学专业课程存在依赖关系,对于一些课程必须选修其他课程,完成整个工程就是学习所有的课程,每门课程的学习都是一个活动。

图1

整个工程可以通过工程图表示:

图2

 工程图为有向图,顶点表示活动,例如有向边<u,v>表示活动u必须先于活动v,这种有向图称为顶点表示活动的网络Activity On Vertices),通常叫作AOV网络。活动u必须在活动v之前进行,u被称为v的直接前驱(immediate predecessor),v是u的直接后继(immediate successor)。如果是有向路径<u,u1,u2,...,un,v>,则称u是v的前驱(predecessor),v是u的后继(successor) 。前驱后继的关系具有传递性(transitivity),比如v2是v1的后继,v3是v2的后继,那么v3也是v1的后继。任何活动不能以自己为前驱或者后继。

由上面分析可以看到,AOV网络不能出现有向回路(回环),不含有向回路的有向图称为有向无环图Directed Acyclic Graph, DAG)。对于AOV网络是不允许出现回路的,因为这样会陷入死循环,导致工程无法进行,故对于给定的AOV网络,必须要判断它是否是有向无环图。

拓扑排序

基本概念

判断有向无环图的方法是对AOV网络构造它的拓扑有序序列(topological order sequence),即所有的顶点排列成一个线性有序的序列,使得AOV网络中所有前驱和后继关系都能满足,如下图:

图3

 

这种构造AOV网络全部顶点的拓扑有序序列的运算称为拓扑排序(topological sort)。如果能够通过拓扑排序将AOV网络的所有顶点都排入一个拓扑有序的序列中,那么该AOV网络必定不存在有向环;反之,如果得不到所有顶点的拓扑有序序列,则说明该AOV网络存在有向环,此AOV网络所代表的工程是不可行的。例如对于图2的拓扑排序结果为:

C1,C2,C3,C4,C5,C6,C8,C9,C7 或者 C1,C8,C9,C2,C5,C3,C4,C7,C6。

可见一个AOV网络的拓扑有序序列可能并不是唯一的。

拓扑排序实现方法

AOV网络进行拓扑排序的算法如下:

1)从AOV网络中选择一个入度为0(没有直接前驱)的顶点并输出;

2)从AOV网络中删除该顶点及该顶点发出的所有边;

3)重复步骤1)和2),直到找不到入度为0的顶点。

结果:1.所有顶点都被输出,即整个拓扑排序完成;2.仍有顶点没有输出,且剩下的图再也没有入度为0的顶点,那么说明此图有环。

代码实现

整个代码具体流程如下:

1)建立存放入度为0的顶点的栈,初始时将所有入度为0的顶点存入栈;

2)若当前栈不为空,重复执行:

        (1)栈中弹出栈顶,将该顶点存入拓扑排序序列;

        (2)删除弹出的顶点和它发出的每一条边,并且每一个边的终点节点入度减1

        (3)如果顶点的入度减为0,则将该顶点压栈。

3)拓扑顶点个数小于实际顶点个数则存在环,否则可得到拓扑排序。 

下面给出基于之前表示方法给出的数据结构和文件的完整代码:

#include<iostream>
#include<vector>
#include<fstream>
#include<sstream>
#include<string>
#include<unordered_map>
#include<stack>
using namespace std;
typedef struct Vertex{
	string name;
	int index;
	Vertex(string inputName, int inputIndex):name(inputName),index(inputIndex){}
	Vertex():name(""),index(0){}
} VertexNode;
// graph表示邻接表,id表示每个节点入度统计,topSortId存储拓扑排序的索引。
bool topSort(const vector<vector<VertexNode> >& graph, vector<int>& id, vector<int>& topSortId) {
	topSortId.clear();
	topSortId.reserve(graph.size());
	stack<int> S; // 栈,存放入度为0的顶点
	for(int i = 0; i < graph.size(); i++) {
		if (id[i] == 0) {
			S.push(i);
		}
	}
	for (int i = 0; i < graph.size(); i++) {
		if(S.empty()) {
			return false;
		}
		int j = S.top();
		S.pop(); //弹出栈顶存储的顶点j
		topSortId.push_back(j); //存入拓排序序列
		for(int k = 0; k < graph[j].size(); k++) {
			int currentIndex = graph[j][k].index;
			if(--id[currentIndex] == 0) {
				S.push(currentIndex);
			}
		}
	}
	return true; // 如果前面所有顶点全部循环没问题,那么说明可以拓扑排序故返回true.
}
int main() {
	unordered_map<string ,int> graphMap; // 图节点名和编号的Map
	vector<vector<VertexNode> > adjGraph; // 图的连接表表示法
	ifstream graphRdFile("graph_struct.txt");
	if(!graphRdFile.good()) {
		cout << "open graph file failed!" << endl;
		return -1;
	}
	string line;
	int index = 0;
	string vertexName;
	// 首先对Vertex Name进行编码
	while(graphRdFile >> vertexName) {
		if (graphMap.find(vertexName) == graphMap.end()) {
			graphMap.insert(make_pair(vertexName, index++));
		}
	}
	// 编码与Vertex的反映射
	vector<string> indexName = vector<string>(graphMap.size(),"");
	for(auto itr=graphMap.begin();itr!=graphMap.end();itr++) {
		indexName[itr->second] = itr->first;
	}
	// 重新读
	graphRdFile.clear();
	graphRdFile.seekg(0,std::ios::beg);
	adjGraph.resize(graphMap.size());
	int currentIndex = 0; // 当前图节点的编号
	vector<int> id(adjGraph.size(), 0);
	while (getline(graphRdFile, line)) { //按行读,每一行是一个图节点的连接情况
		istringstream ss(line);
		string tmp;
		bool firstFlag = true;
		while(ss >> tmp) {
			if (firstFlag) {
				if (graphMap.find(tmp) != graphMap.end()) {
					currentIndex = graphMap[tmp];
				} else {
					break;
				}
				firstFlag = false;
				continue;
			}
			if (graphMap.find(tmp) != graphMap.end()) {
				adjGraph[currentIndex].emplace_back(VertexNode(tmp,graphMap[tmp]));
				id[graphMap[tmp]]++;
			}
		}
	}
	// 拓扑排序测试:
	vector<int> topSortId;
	if(topSort(adjGraph, id, topSortId)) {
		for(int i = 0; i < topSortId.size(); i++) {
			cout << indexName[topSortId[i]] << " ";
		}
		cout << endl;
	} else {
		cout << "Network has a cycle!" << endl;
	}
	return 0;
}

测试图结构文件graph_struct.txt内容为:

1 2 4
2 6
3 2 6
4
5 1 2 6

对应的有向无环图为:

输出结果为:

5 1 4 3 2 6
  • 3
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值