拓扑排序

在施工流程图或产品生产的流程图中,根据施工项目的顺序和产品生产顺序排序的过程,即拓扑序列。有向无环图的拓扑序列指的是:图中v1到v2有一条边,那么在拓扑序列中v1在v2之前,对于所有的顶点和边都适用。下面介绍两种求拓扑序列的方法:
(1)在有向图中选择一个没有前驱的顶点输出。
(2)从图中删除该顶点和所有以它为尾的弧。
重复上述过程,直至全部的顶点输出,或者当前图中不存在无前驱的顶点为止。后一种情况说明该图是带环的。用这种方法可以判断有向图是不是有向无环图。我们采用邻接表作为有向图的存储结构,我们设置一个indegree数组,数组中存储当前节点的入度数。入度为零的顶点即为没有前驱的顶点,为了避免重复检测入度为零的顶点,可另设置一个栈暂时存所有入度为零的顶点。
对于有n个顶点和e条边的有向图而言,建立各个顶点入度的时间复杂度为o(e);建立零入度顶点栈的时间复杂度为o(n);在有向无环图中,则每个顶点入栈一次,出栈一次,入度减一的操作在while循环中总共执行了e次所以总的时间复杂度为o(n + e)。
#include<iostream>
#include<vector>
#include<fstream>
#include<time.h>
#include<stack>
using namespace std;

stack<int> s;//定义一个栈
void readGraph();//读取文件,存储图
void findIndegree();//初始化每个顶点的入度
void topSort();//拓扑排序的函数

int nodeNum;//图中顶点数
int edgeNum;//图中边数
vector<vector<int>> mGraph;//图的存储结构
int *indegree;//存储每个节点的入度数


//从文件中读取数据,存储在图中
void readGraph()
{
	fstream fin("E:\\myData\\cit-Patents.txt");
	fin>>nodeNum>>edgeNum;
	mGraph.resize(nodeNum);
	indegree = new int[nodeNum]();
	int num1, num2, node;
	for(int i = 0; i < nodeNum; ++i)
	{
		fin>>num1>>num2;
		mGraph[i].reserve(num2);
		for(int j = 0; j < num2; ++j)
		{
			fin>>node;
			mGraph[i].push_back(node);
		}
	}
	fin.close();
}

//初始化每个顶点的入度
void findIndegree()
{
	for(int i = 0; i < nodeNum; ++i)
	{
		int count = mGraph[i].size();
		for(int j = 0; j < count; ++j)
		{
			indegree[mGraph[i][j]]++;
		}
	}
}

//拓扑排序
void topSort()
{
	for(int i = 0; i < nodeNum; ++i)
	{
		if(!indegree[i])
		{
			s.push(i);
		}
	}
	int num = 0;//记录入栈元素的个数
	int v = 0;//设置栈顶元素
	int count = 0;//当前顶点邻接点的个数
	int val = 0;//当前元素的邻接点的值
	while(!s.empty())//判断栈是否为空
	{
		v = s.top();//取出栈顶元素
		s.pop();//出栈
		count = mGraph[v].size();
		for(int i = 0; i < count; ++i)
		{
			val = mGraph[v][i];
			if(!(--indegree[val]))//当前元素的入度减一
			{
				s.push(val);//如果入度为零则入栈
			}
		}
		num++;//顶点数加加
	}
	if(num < nodeNum)
	{
		cout<<"有向有环图"<<endl;
	}
}


int main(void)
{	
	clock_t start, finish;
	start = clock(); 
	readGraph();
	findIndegree();
	topSort();
	finish = clock(); 
	cout<<"拓扑排序的时间:"<<(finish - start) / CLOCKS_PER_SEC * 1000<<endl;
	system("pause");
	return 0;
}

实验结果:单位(ms)

数据集为citeseerx

图中顶点的个数:1457057

图中边的条数:3002252

拓扑排序的时间:0

数据集为cit-Patents

图中顶点的个数:3774768

图中边的条数:16518947

拓扑排序的时间:2000

数据集为twitter

图中顶点的个数:18121168

图中边的条数:18359487

拓扑排序的时间:4000

数据集为web-uk

图中顶点的个数:22753644

图中边的条数:38184039

拓扑排序的时间:0

总结:时间主要消耗在stack<int> s中空间的动态开辟的过程。

方法二:

当有向图为无环时,也可以利用深度优先遍历进行拓扑排序,因为图中无环,则由图中某个顶点出发进行深度优先遍历时,最先退出DFS函数的顶点即出度为零的顶点,是拓扑有序序列中最后一个顶点,由此,按退出DFS的先后顺序记录下顶点的序列即为逆向的拓扑有序序列。时间复杂度和方法一的时间复杂度相同。

#include<iostream>
#include<vector>
#include<fstream>
#include<time.h>
#include<stack>
using namespace std;

void readGraph();//读取文件,存储图
void topSort();//拓扑排序
void DFS(int v);//从v顶点进行深度优先遍历


int nodeNum;//图中顶点数
int edgeNum;//图中边数
vector<vector<int>> mGraph;//图的存储结构
int *arr;//存储元素的拓扑序列
int num;
bool *visited;


//从文件中读取数据,存储在图中
void readGraph()
{
	fstream fin("E:\\myData\\1M-3.txt");
	//fstream fin("E:\\myData\\cit-Patents.txt");
	fin>>nodeNum>>edgeNum;
	arr = new int[nodeNum]();
	num = nodeNum - 1;
	mGraph.resize(nodeNum);
	visited = new bool[nodeNum]();
	int num1, num2, node;
	for(int i = 0; i < nodeNum; ++i)
	{
		fin>>num1>>num2;
		mGraph[i].reserve(num2);
		for(int j = 0; j < num2; ++j)
		{
			fin>>node;
			mGraph[i].push_back(node);
		}
	}
	fin.close();
}

void topSort()
{
	for(int i = 0; i < nodeNum; ++i)
	{
		if(!visited[i])
		{
			DFS(i);
		}
	}
}

void DFS(int v)
{
	visited[v] = true;
	int count = mGraph[v].size();
	for(int i = 0; i < count; ++i)
	{
		if(!visited[mGraph[v][i]])
		{
			DFS(mGraph[v][i]);
		}
	}
	arr[num--] = v;//存储元素的拓扑序列
}

int main(void)
{
	readGraph();
	clock_t start, finish;
	start = clock(); 

	DFSTraverse();

	finish = clock();
	cout<<"拓扑排序的时间:"<<(finish - start) / CLOCKS_PER_SEC * 1000<<endl;
	system("pause");
	return 0;
}

实验结果:单位(ms)

数据集为citeseerx

图中顶点的个数:1457057

图中边的条数:3002252

拓扑排序的时间:0

数据集为cit-Patents

图中顶点的个数:3774768

图中边的条数:16518947

拓扑排序的时间:0

数据集为twitter

图中顶点的个数:18121168

图中边的条数:18359487

拓扑排序的时间:0

数据集为web-uk

图中顶点的个数:22753644

图中边的条数:38184039

拓扑排序的时间:0

比较:方法一比方法二耗时,是因为方法一中创建stack<int> s对象时,s的空间是根据栈中元素的个数动态开辟的,这部分的时间比较耗时。拓扑排序的时间复杂度为o(n + e),基本在0ms之内就可以排序完成。

    原文作者:拓扑排序
    原文地址: https://blog.csdn.net/yang20141109/article/details/49158553
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞