LeetCode 207. Course Schedule


原题地址

题目要求

There are a total of n courses you have to take, labeled from 0 to n-1.

Some courses may have prerequisites, for example to take course 0 you have to first take course 1, which is expressed as a pair: [0,1]

Given the total number of courses and a list of prerequisite pairs, is it possible for you to finish all courses?

Example 1:

Input: 2, [[1,0]] 
Output: true
Explanation: There are a total of 2 courses to take. 
             To take course 1 you should have finished course 0. So it is possible.

Example 2:

Input: 2, [[1,0],[0,1]]
Output: false
Explanation: There are a total of 2 courses to take. 
             To take course 1 you should have finished course 0, and to take course 0 you should
             also have finished course 1. So it is impossible.

Note:

  1. The input prerequisites is a graph represented by a list of edges, not adjacency matrices. Read more about how a graph is represented.
  2. You may assume that there are no duplicate edges in the input prerequisites.

题目解释:

输入包括一个整数代表图的大小,还有一个vector保存有向图的边,要求如果一个节点A指向另一个节点B,那么节点B就不能出现在节点A的前面。

这是一个很经典的拓扑排序问题。

拓扑排序

首先,先介绍一下拓扑排序的含义;

拓扑排序是图论中一个很经典的算法;对于一个有向无环图G进行拓扑排序,就是将G中的所有节点排成一个线性序列;对于图G中的每一条边,如边(u,v),则表示在我们输出的线性序列中节点u必须出现在节点v的前面,这个线性序列就被我们称为满足拓扑排序序列,简称拓扑序列。

需要注意的是,拓扑排序是针对有向无环图的;

拓扑排序的实现也并不复杂:

(1)首先,我们需要选取图中一个入度为0的节点(入度表示某点作为图中边的终点的次数之和)

(2)然后从图中删除以该节点为起点的所有有向边和该节点,此时被删除的有向边的终点的入度减 1;

(3)重复上面的(1)和(2)操作直到图中没有入度为0的节点;

当算法结束后,如果图中仍然还有节点,那么说明该有向图不存在拓扑序列;反之,存在拓扑序列;

下图是拓扑排序的一个简单例子:

由此可以得到拓扑序列{1,2,3,4}

我们还可以看到,在第二步移除节点2和移除节点3的效果是一样的,因此拓扑排序并不是唯一的。

讲完了拓扑排序的原理,下面就是代码实现

代码实现

代码解释已经注释了,应该不难理解:

/************************Graph类************************/
class Graph{
private:
	int nums;                 // 图的顶点个数 
	std::vector<int>* adj;    // 邻接表保存有向边 
	vector<int> indegree;     // 记录每个节点的入度 
public:
	/*
	 * 构造函数
	 * 输入:图的个数  
	 */ 
	Graph(int v){               
		nums = v;
		// 初始化邻接表和每个节点的入度 
		adj = new std::vector<int>[nums];
		for (int i = 0; i < v; ++i)
		{
			indegree.push_back(0);
		}
	}

	/* 析构函数 */
	~Graph(){
		delete []adj;
	}

	/*
	 * 函数说明:添加有向边
	 * 输入:
	 *       from: 起始点 
	 *       to:   终点   
	 */ 
	void addEdge(int from,int to){
		// 往邻接表中添加有向边 
		adj[from].push_back(to);
		// 修改终点的入度 
		indegree[to]++;
	}
	
	/*
	 * 函数说明:判断是否存在拓扑序列 
	 * 输出:布尔值,true表示存在拓扑序列 
	 */ 
	bool topological_sort(){
		// 使用一个队列保存入度为0的节点 
		queue<int> q;
		
		for(int i = 0;i < nums;i++){
			if(indegree[i] == 0){
				q.push(i);
			}
		}
		
		// count记录已经从图中删除的节点个数,当count等于节点个数,说明存在拓扑序列 
		int count = 0,temp;
		
		while(!q.empty()){ // 跳出循环条件:图中已经不存在入度为0的节点 
			temp = q.front();
			q.pop();
			count++;
			
			// 更新入度和保存入度为0的节点的队列 
			for(auto value:adj[temp]){
				indegree[value]--;
				if(indegree[value] == 0){
					q.push(value);
				}
			}
		}
		return (count == nums);
	}
};

/************************Solution类************************/
class Solution {
public:
    bool canFinish(int numCourses, vector<pair<int, int>>& prerequisites) {
    	// 初始化图 
        Graph graph(numCourses);
        for(auto value:prerequisites){
			graph.addEdge(value.first,value.second);
		}
		// 拓扑排序 
		return graph.topological_sort();
    }
};

猜你喜欢

转载自blog.csdn.net/qiuxy23/article/details/82823170