图的拓扑排序、关键路径、最短路径算法 -- C++实现

一:拓扑排序

对一个有向无环图(Directed Acyclic Graph简称DAG)G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若边(u,v)∈E(G),则u在线性序列中出现在v之前。通常,这样的线性序列称为满足拓扑次序(Topological Order)的序列,简称拓扑序列。

拓扑排序就是要找到入度为0的顶点,并依次压栈。首先将栈顶顶点输出,删除以此顶点为弧尾的顶点,并更新其他顶点的入度。若入度为0,则继续压栈。更新完毕继续出栈,直至栈空。元素出栈并输出的顺序即为拓扑排序结果。我们代码中会利用数组模拟一个栈空间。
如图所示:
该图所得到拓扑排序结果为:F A D C  E B。
算法如下:
template <typename T, typename E>
void graph_mtx<T, E>::topological_sort()
{
	int num = this->get_numvertices();
	assert(num != -1);

	int *count = new int[num];
	assert(count != nullptr);

	for(int i=0; i<num; ++i)
		for(int j=0; j<num; ++j){
			if(edge[i][j] != 0 && edge[i][j] != MAX_COST)
				count[j]++;	//统计入度
		}

	int top = -1;
	for(int i=0; i<num; ++i)
		if(count[i] == 0){   //利用数组模拟栈
			count[i] = top;     //相当于压栈,存储之前位置
			top = i;            //存储当前位置
		}
	
	for(int i=0; i<num; ++i){
		if(top == -1)    //如果为-1,说明有环存在,回到了起始位置
			return ;

		int index_tmp = top;
		top = count[top];   //取栈顶元素
		
		cout << get_vertex_symbol(index_tmp) << "  ";   //输出栈顶元素

		int neighbor_index = get_firstneighbor(get_vertex_symbol(index_tmp));
		while(neighbor_index != -1){           
			if(--count[neighbor_index] == 0){       //如果入度减为0,入栈
				count[neighbor_index] = top;
				top = neighbor_index;
			}
			neighbor_index = get_nextneighbor(get_vertex_symbol(index_tmp),
											  get_vertex_symbol(neighbor_index));
		}
	}

	delete []count;
}
二:关键路径
关键路径:从源点到汇点的路径长度最长的路径叫关键路径。
图中a(1..n)表示两活动之间花费的时间。
如图所示:
图中所求关键路径为A B E G I 或 A B E H I。
两个重要概念:
1.最早开始时间
   如图E,从A到E所花费的时间在两条路径上飞别为7和5,那么E活动什么时候开始呢?毫无疑问,由于E活动开始存在B、C时间两个前提,所以在工程起始时间一定的情况下,A到E最少需要花费时间取决于ABE这条较长分支,这条分支如果未完成,那么C就算完成了也只能等待另一分支完成,此时E不能开始。由此可见,最早开始时间即为从源点到目标顶点最长的路径花费的时间。
2.最晚开始时间
   同理,最晚开始时间是从汇点倒着往前计算的。与上面情况相同,最晚开始时间取决于从后往前经过的路径长度最长的分支。
由图易知,最早开始时间和最晚开始时间相等的顶点,即为关键路径顶点。
算法如下:
template <typename T, typename E>
void graph_mtx<T, E>::critical_path(const T& vert)
{
	int num = this->get_numvertices();
	
	int *early_start = new int[num];   //最早开始时间数组
	int *late_start = new int[num];    //最晚开始时间数组
	assert(early_start != nullptr && late_start != nullptr);

	int index = get_vertex_index(vert);
	assert(index != -1);

	for(int i=0; i<num; ++i)
		early_start[i] = 0;      //初始化为0

	for(int i=0; i<num; ++i){
		int neighbor_index = get_firstneighbor(get_vertex_symbol(i));
		while(neighbor_index != -1){
			int weight = edge[i][neighbor_index];
			if(early_start[i]+weight > early_start[neighbor_index]) //如果某点最早开始时间加上某点到其邻接点的权值大于该邻接点之前的权值,说明新路径长,更新该邻接点权值
				early_start[neighbor_index] = early_start[i]+weight;

			neighbor_index = get_nextneighbor(get_vertex_symbol(i),
										      get_vertex_symbol(neighbor_index));
		}
	}

	for(int i=0; i<num; ++i)
		cout << early_start[i] << "  ";   //打印最早开始时间
	cout << endl;

	for(int i=0; i<num; ++i)
		late_start[i] = MAX_COST;   //初始化为无穷时间
	late_start[num-1] = early_start[num-1];       //汇点的最早开始时间和最晚开始时间相同

	for(int i=num-2; i>=0; --i){    //除开汇点,从num-2开始
		int neighbor_index = get_firstneighbor(get_vertex_symbol(i));
		while(neighbor_index != -1){
			int weight = edge[i][neighbor_index];  
			if(late_start[neighbor_index]-weight < late_start[i])//某点的邻接点的最晚开始时间减去权值小于某点之前的最晚开始时间,说明现有从后往前的路径长,则更新为最晚开始时间
				late_start[i] = late_start[neighbor_index]-weight;

			neighbor_index = get_nextneighbor(get_vertex_symbol(i),
				                              get_vertex_symbol(neighbor_index));
		}
	}

	for(int i=0; i<num; ++i)
		cout << late_start[i] << "  ";  //打印最晚开始时间
	cout << endl;

	for(int i=0; i<num; ++i)
		if(early_start[i] == late_start[i])
			cout << get_vertex_symbol(i) << "  ";  //若最早开始时间和最晚开始时间相等,则为关键路径

	delete []early_start;
	delete []late_start;
}
三:最短路径(迪杰斯特拉算法)
最短路径问题是图论研究中的一个经典算法问题, 旨在寻找图(由结点和路径组成的)中两结点之间的最短路径。
迪杰斯特拉算法用一个dist[]数组存储到达某点的花费,以其下标对应,并用一个path[]数组来存储到达该点前经过的某个顶点的下标。其中lable_incorporated[]数组用来标记已并入路径的顶点。
如图所示:
该图的最短路径为A D C E。
代码如下:
template <typename T, typename E>
void graph_mtx<T, E>::shortest_path(const T& vert)
{
	int num = this->get_numvertices();
	
	int *dist = new int[num];
	int *path = new int[num];
	int *lable_incorporated = new int[num];
	assert(dist != nullptr && path != nullptr 
						   && lable_incorporated != nullptr);

	int index = get_vertex_index(vert);
	assert(index != -1);

	for(int i=0; i<num; ++i){
		lable_incorporated[i] = false;
		dist[i] = edge[index][i];
		if(edge[index][i] != 0 && dist[i] < MAX_COST)
			path[i] = index;   //如果从vert可以到达该点,路径默认为vert,表明到达该点的上一个顶点为vert
		else
			path[i] = -1;      //自身及不可达设为-1
	}
	lable_incorporated[index] = true;  //首先将起始点vert并入

	for(int i=0; i<num-1; ++i){
		int min = MAX_COST;
		int min_index = -1;

		for(int j=0; j<num; ++j){
			if(!lable_incorporated[j] && dist[j] < min){   //在未并入的顶点中找到最短可达的花费最小的顶点
				min = dist[j];
				min_index = j;
			}
		}
		lable_incorporated[min_index] = true;    //并入该顶点

		for(int j=0; j<num; ++j){
			int weight = edge[min_index][j];
			if(!lable_incorporated[j] && weight < MAX_COST //此处注意,如果不加weight<MAX_COST,那么dist[]数组重元素加上MAX_COST数据溢出,编译器默认为赋值,这就导致dist[min_index]+weight永远小于dist[j],出现错误
									  && dist[min_index]+weight < dist[j]){   //weight<MAX_COST
				dist[j] = dist[min_index]+weight;    //更新路径
				path[j] = min_index;
			}
		}
	}
	
	cout << "dist:" << " ";
	for(int i=0; i<num; ++i)
		cout << setw(3) << dist[i] << "  ";
	cout <<endl;

	cout << "path:" << " ";
	for(int i=0; i<num; ++i)
		cout << setw(3) << path[i] << "  ";
	
	delete []dist;
	delete []path;
}
四:全部代码及测试
通用图头文件:
#ifndef _GRAPH_H
#define _GRAPH_H

#include <iostream>
#include <string.h>
#include <assert.h>
#include <queue>
#include <iomanip>
using namespace::std;

#define MAX_COST 0x7FFFFFFF

///
//通用图结构
template <typename T, typename E>
class graph{
public:
	bool is_empty()const;
	bool is_full()const;
	
	int get_numvertices()const;    //当前顶点数
	int get_numedges()const;       //当前边数
public:
	virtual bool insert_vertex(const T&) = 0;            //插入顶点
	virtual bool insert_edge(const T&, const T&, E) = 0;    //插入边

	virtual int get_firstneighbor(const T&)const = 0;    //得到第一个邻接顶点
	virtual int get_nextneighbor(const T&, const T&)const = 0;    //某邻接顶点的下一个邻接顶点
	
	virtual void print_graph()const = 0;
	virtual int get_vertex_index(const T&)const = 0;     //得到顶点序号
protected:
	static const int VERTICES_DEFAULT_SIZE = 10;         //默认图顶点数
	int max_vertices;
	int num_vertices;
	int num_edges;
};

template <typename T, typename E>
bool graph<T, E>::is_empty()const
{
	return num_edges == 0;
}

template <typename T, typename E>
bool graph<T, E>::is_full()const
{
	return num_vertices >= max_vertices 
		   || num_edges >= max_vertices*(max_vertices-1)/2;    //判满,分为顶点满和边满
}

template <typename T, typename E>
int graph<T, E>::get_numvertices()const
{
	return num_vertices;
}

template <typename T, typename E>
int graph<T, E>::get_numedges()const
{
	return num_edges;
}

///

#define VERTICES_DEFAULT_SIZE graph<T, E>::VERTICES_DEFAULT_SIZE
#define num_vertices          graph<T, E>::num_vertices   
#define num_edges             graph<T, E>::num_edges
#define max_vertices          graph<T, E>::max_vertices         

///

#endif /*graph.h*/
实现文件:
#pragma once

#include "graph.h"

//图的邻接矩阵表示法
template <typename T, typename E>
class graph_mtx : public graph<T, E>{
public:
	graph_mtx(int);                   
	~graph_mtx();                             
public:
	bool insert_vertex(const T&);
	bool insert_edge(const T&, const T&, E);  

	int get_firstneighbor(const T&)const;
	int get_nextneighbor(const T&, const T&)const;
	
	int get_vertex_index(const T&)const;
	T& get_vertex_symbol(const int)const;
	void print_graph()const;

	void topological_sort();
	void shortest_path(const T&);
	void critical_path(const T&);
private:
	T* vertices_list;                        //顶点线性表
	E **edge;                              //内部矩阵
};

template <typename T, typename E>
graph_mtx<T, E>::graph_mtx(int sz = VERTICES_DEFAULT_SIZE)
{
	max_vertices = sz > VERTICES_DEFAULT_SIZE ? sz 
									: VERTICES_DEFAULT_SIZE;
	vertices_list = new T[max_vertices];

	edge = new int*[max_vertices];                    //动态申请二维数组
	for(int i=0; i<max_vertices; ++i){	
		edge[i] = new int[max_vertices];
	}

	for(int i=0; i<max_vertices; ++i)
		for(int j=0; j<max_vertices; ++j){
			if(i != j)
				edge[i][j] = MAX_COST;
			else
				edge[i][j] = 0;
		}

	num_vertices = 0;
	num_edges = 0;
}

template <typename T, typename E>
graph_mtx<T, E>::~graph_mtx()
{
	for(int i=0; i<max_vertices; ++i)
		delete []edge[i];                     //分别析构,再总析构
	
	delete edge;
	delete []vertices_list;
}

template <typename T, typename E>
bool graph_mtx<T, E>::insert_vertex(const T& vert)
{
	if(this->is_full())                       //派生类函数调用父类函数,用this或加作用域
		return false;
	vertices_list[num_vertices++] = vert;
	return true;
}

template <typename T, typename E>
bool graph_mtx<T, E>::insert_edge(const T& vert1, const T& vert2, E cost = MAX_COST)
{
	if(this->is_full())                       //判满
		return false;

	int index_v1 = get_vertex_index(vert1);   //得到顶点序号
	int index_v2 = get_vertex_index(vert2);

	if(index_v1 == -1 || index_v2 == -1 )
		return false;
	
	edge[index_v1][index_v2] = cost;    //无向图
	++num_edges;	
	
	return true;
}

template <typename T, typename E>
int graph_mtx<T, E>::get_firstneighbor(const T& vert)const
{
	int index = get_vertex_index(vert);

	if(index != -1){
		for(int i=0; i<num_vertices; ++i){
			if(edge[index][i] != 0 && edge[index][i] != MAX_COST)
				return i;
		}
	}
	return -1;
}

template <typename T, typename E>
int graph_mtx<T, E>::get_nextneighbor(const T& vert1, const T& vert2)const
{
	int index_v1 = get_vertex_index(vert1);
	int index_v2 = get_vertex_index(vert2);

	if(index_v1 != -1 && index_v2 != -1){
		for(int i=index_v2+1; i<num_vertices; ++i){
			if(edge[index_v1][i] != 0 && edge[index_v1][i] != MAX_COST)
				return i;
		}
	}
	return -1;
}

template <typename T, typename E>
int graph_mtx<T, E>::get_vertex_index(const T& vert)const
{
	for(int i=0; i<num_vertices; ++i){
		if(vertices_list[i] == vert)
			return i;
	}
	return -1;
}

template <typename T, typename E>
T& graph_mtx<T, E>::get_vertex_symbol(const int index)const
{
	assert(index >= 0 && index < this->get_numvertices());
	return vertices_list[index];
}

template <typename T, typename E>
void graph_mtx<T, E>::print_graph()const
{
	if(this->is_empty()){
		cout << "nil graph" << endl;                      //空图输出nil
		return;
	}
	
	for(int i=0; i<num_vertices; ++i){
		cout << vertices_list[i] << "  ";
	}
	cout << endl;

	for(int i=0; i<num_vertices; ++i){
		for(int j=0; j<num_vertices; ++j){
			if(edge[i][j] != MAX_COST)
				cout << edge[i][j] << "  ";
			else
				cout << '@' << "  ";
		}
		cout << vertices_list[i] << endl;
	}
}

template <typename T, typename E>
void graph_mtx<T, E>::topological_sort()
{
	int num = this->get_numvertices();
	assert(num != -1);

	int *count = new int[num];
	assert(count != nullptr);

	for(int i=0; i<num; ++i)
		for(int j=0; j<num; ++j){
			if(edge[i][j] != 0 && edge[i][j] != MAX_COST)
				count[j]++;	
		}

	int top = -1;
	for(int i=0; i<num; ++i)
		if(count[i] == 0){
			count[i] = top;
			top = i;
		}
	
	for(int i=0; i<num; ++i){
		if(top == -1)
			return ;

		int index_tmp = top;
		top = count[top];
		
		cout << get_vertex_symbol(index_tmp) << "  ";

		int neighbor_index = get_firstneighbor(get_vertex_symbol(index_tmp));
		while(neighbor_index != -1){
			if(--count[neighbor_index] == 0){
				count[neighbor_index] = top;
				top = neighbor_index;
			}
			neighbor_index = get_nextneighbor(get_vertex_symbol(index_tmp),
											  get_vertex_symbol(neighbor_index));
		}
	}

	delete []count;
}

template <typename T, typename E>
void graph_mtx<T, E>::shortest_path(const T& vert)
{
	int num = this->get_numvertices();
	
	int *dist = new int[num];
	int *path = new int[num];
	int *lable_incorporated = new int[num];
	assert(dist != nullptr && path != nullptr 
						   && lable_incorporated != nullptr);

	int index = get_vertex_index(vert);
	assert(index != -1);

	for(int i=0; i<num; ++i){
		lable_incorporated[i] = false;
		dist[i] = edge[index][i];
		if(edge[index][i] != 0 && dist[i] < MAX_COST)
			path[i] = index;
		else
			path[i] = -1;
	}
	lable_incorporated[index] = true;

	for(int i=0; i<num-1; ++i){
		int min = MAX_COST;
		int min_index = -1;

		for(int j=0; j<num; ++j){
			if(!lable_incorporated[j] && dist[j] < min){
				min = dist[j];
				min_index = j;
			}
		}
		lable_incorporated[min_index] = true;

		for(int j=0; j<num; ++j){
			int weight = edge[min_index][j];
			if(!lable_incorporated[j] && weight < MAX_COST 
									  && dist[min_index]+weight < dist[j]){   //weight<MAX_COST
				dist[j] = dist[min_index]+weight;
				path[j] = min_index;
			}
		}
	}
	
	cout << "dist:" << " ";
	for(int i=0; i<num; ++i)
		cout << setw(3) << dist[i] << "  ";
	cout <<endl;

	cout << "path:" << " ";
	for(int i=0; i<num; ++i)
		cout << setw(3) << path[i] << "  ";
	
	delete []dist;
	delete []path;
}

template <typename T, typename E>
void graph_mtx<T, E>::critical_path(const T& vert)
{
	int num = this->get_numvertices();
	
	int *early_start = new int[num];
	int *late_start = new int[num];
	assert(early_start != nullptr && late_start != nullptr);

	int index = get_vertex_index(vert);
	assert(index != -1);

	for(int i=0; i<num; ++i)
		early_start[i] = 0;

	for(int i=0; i<num; ++i){
		int neighbor_index = get_firstneighbor(get_vertex_symbol(i));
		while(neighbor_index != -1){
			int weight = edge[i][neighbor_index];
			if(early_start[i]+weight > early_start[neighbor_index])
				early_start[neighbor_index] = early_start[i]+weight;

			neighbor_index = get_nextneighbor(get_vertex_symbol(i),
										      get_vertex_symbol(neighbor_index));
		}
	}

	for(int i=0; i<num; ++i)
		cout << early_start[i] << "  ";
	cout << endl;

	for(int i=0; i<num; ++i)
		late_start[i] = MAX_COST;
	late_start[num-1] = early_start[num-1];

	for(int i=num-2; i>=0; --i){
		int neighbor_index = get_firstneighbor(get_vertex_symbol(i));
		while(neighbor_index != -1){
			int weight = edge[i][neighbor_index];
			if(late_start[neighbor_index]-weight < late_start[i])
				late_start[i] = late_start[neighbor_index]-weight;

			neighbor_index = get_nextneighbor(get_vertex_symbol(i),
				                              get_vertex_symbol(neighbor_index));
		}
	}

	for(int i=0; i<num; ++i)
		cout << late_start[i] << "  ";
	cout << endl;

	for(int i=0; i<num; ++i)
		if(early_start[i] == late_start[i])
			cout << get_vertex_symbol(i) << "  ";

	delete []early_start;
	delete []late_start;
}
测试文件:
#include "graph.h"
#include "graph_mtx.h"

int main()
{
	graph_mtx<char, int> gm;

//critical_path
	gm.insert_vertex('A');	
	gm.insert_vertex('B');	
	gm.insert_vertex('C');	
	gm.insert_vertex('D');	
	gm.insert_vertex('E');	
	gm.insert_vertex('F');	
	gm.insert_vertex('G');	
	gm.insert_vertex('H');	
	gm.insert_vertex('I');

	gm.insert_edge('A', 'B', 6);
	gm.insert_edge('A', 'C', 4);
	gm.insert_edge('A', 'D', 5);
	gm.insert_edge('B', 'E', 1);
	gm.insert_edge('C', 'E', 1);
	gm.insert_edge('D', 'F', 2);
	gm.insert_edge('E', 'G', 9);
	gm.insert_edge('E', 'H', 7);
	gm.insert_edge('G', 'I', 2);
	gm.insert_edge('H', 'I', 5);
	gm.insert_edge('F', 'H', 4);

	gm.critical_path('A');
	cout << endl;
#if 0
//shortest_path
	gm.insert_vertex('A');
	gm.insert_vertex('B');
	gm.insert_vertex('C');
	gm.insert_vertex('D');
	gm.insert_vertex('E');

	gm.insert_edge('A', 'B', 10);
	gm.insert_edge('A', 'D', 30);
	gm.insert_edge('A', 'E', 100);
	gm.insert_edge('B', 'C', 50);
	gm.insert_edge('C', 'E', 10);
	gm.insert_edge('D', 'C', 20);
	gm.insert_edge('D', 'E', 60);

	cout << "shortest_path:" << endl;
	gm.shortest_path('A');
	cout << endl;
#endif
#if 0
//topological_sort
	gm.insert_vertex('A');
	gm.insert_vertex('B');
	gm.insert_vertex('C');
	gm.insert_vertex('D');
	gm.insert_vertex('E');
	gm.insert_vertex('F');
	
	gm.insert_edge('A', 'B', 6);
	gm.insert_edge('A', 'C', 1);
	gm.insert_edge('A', 'D', 5);
	gm.insert_edge('C', 'B', 5);
	gm.insert_edge('C', 'E', 3);
	gm.insert_edge('D', 'E', 5);
	gm.insert_edge('F', 'E', 4);
	gm.insert_edge('F', 'D', 2);
	
	cout << "topological_sort:" << endl;
	gm.topological_sort();
	cout << endl;

#endif

	return 0;
}
部分测试结果:


  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
拓扑排序关键路径算法是一种用于确定有向无环关键路径算法。其基本思想是通过拓扑排序来确定每个节点的最早开始时间和最晚开始时间,从而计算出每个活动的最早开始时间、最晚开始时间和总时差,从而确定关键路径算法流程如下: 1. 对有向无环进行拓扑排序,得到每个节点的最早开始时间。 2. 从起点开始,按照拓扑序列依次计算每个节点的最晚开始时间。 3. 计算每个活动的最早开始时间、最晚开始时间和总时差。 4. 根据总时差为0的活动确定关键路径。 下面是一个简单的例子,假设有以下有向无环: ``` A --> B --> C --> D \ / --> E --> ``` 其中,A、B、C、D、E分别表示节点,箭头表示有向边。假设每个节点的执行时间如下: ``` A: 2 B: 3 C: 4 D: 2 E: 1 ``` 则按照上述算法流程,可以得到以下结果: 1. 进行拓扑排序,得到拓扑序列为A -> E -> B -> C -> D。 2. 从起点A开始,按照拓扑序列依次计算每个节点的最晚开始时间,得到以下结果: - A: 最晚开始时间为2 - E: 最晚开始时间为2 - B: 最晚开始时间为5 - C: 最晚开始时间为9 - D: 最晚开始时间为11 3. 计算每个活动的最早开始时间、最晚开始时间和总时差,得到以下结果: - A -> B: 最早开始时间为2,最晚开始时间为5,总时差为3 - A -> E: 最早开始时间为2,最晚开始时间为2,总时差为0 - B -> C: 最早开始时间为5,最晚开始时间为9,总时差为4 - C -> D: 最早开始时间为9,最晚开始时间为11,总时差为2 - E -> B: 最早开始时间为2,最晚开始时间为5,总时差为3 - E -> C: 最早开始时间为3,最晚开始时间为6,总时差为3 4. 根据总时差为0的活动确定关键路径,即A -> E。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值