leetcode(207) Course Schedule即拓扑排序讲解

来源:互联网 发布:短信监控软件 编辑:程序博客网 时间:2024/06/05 23:07

关于拓扑排序的知识点可以参看http://www.cnblogs.com/newpanderking/archive/2012/10/18/2729552.html及http://blog.csdn.net/midgard/article/details/4101025两篇写的很好,这里做一个粗略的讲解

拓扑排序(topological-sort)是指由某个集合上的一个偏序得到该集合上的一个全序的操作。拓扑排序常用来确定一个依赖关系集中,事物发生的顺序。拓扑排序是对有向无环图的顶点的一种排序,它使得如果存在一条从顶点A到顶点B的路径,那么在排序中B出现在A的后面

拓扑排序两个条件:

  1. 有向无环,图中不能有环路,有环路则不能进行拓扑排序。有向无环图(DAG)才有拓扑排序,非DAG图没有拓扑排序一说!!!
  2. 如果存在一条从顶点A到顶点B的路径,那么在排序中B出现在A的后面。
下面是算法流程图,本博客中我检测代码(C/C++和java)都用的是(a)这个模型

拓扑流程图

图1.拓扑流程图

    (1) 在(a)图中v0和v1的入度都为0,不妨选择v0并输出之,接着删去顶点v0及出边<0,2>,得到的结果如(b)图所示。

    (2) 在(b)图中只有一个入度为0的顶点v1,输出v1,接着删去v1和它的三条出边<1,2>,<1,3>和<1,4>,得到的结果如(c)图所示。

    (3) 在(c)图中v2和v4的入度都为0,不妨选择v2并输出之,接着删去v2及两条出边<2,3>和<2,5>,得到的结果如(d)图所示。

    (4) 在(d)图上依次输出顶点v3,v4和v5,并在每个顶点输出后删除该顶点及出边,操作都很简单,不再赘述。

拓扑链表

图2.初始状态链表

更详细的流程可见http://www.cnblogs.com/newpanderking/archive/2012/10/18/2729552.html。作者写的真是不错。

拓扑算法C/C++代码(来自博客http://blog.csdn.net/midgard/article/details/4101025):

//http://blog.csdn.net/midgard/article/details/4101025#include <iostream>#include <queue>using namespace std;#define MAX_VERTEX_NUM    20struct adjVertexNode {    int adjVertexPosition;    adjVertexNode* next; };struct VertexNode{    char data[2];    adjVertexNode* list;    int indegree;}; struct Graph{    VertexNode vertexNode[MAX_VERTEX_NUM];    int vertexNum;    int edgeNum;};void CreateGraph (Graph& g){     int i, j, edgeStart, edgeEnd;     adjVertexNode* adjNode;     cout << "Please input vertex and edge num (vnum enum):" <<endl;     cin >> g.vertexNum >> g.edgeNum;     cout << "Please input vertex information (v1)/n note: every vertex info end with Enter" <<endl;     for (i=0;i<g.vertexNum;i++)      {         cin >> g.vertexNode[i].data; // vertex data info.         g.vertexNode[i].list = NULL;          g.vertexNode[i].indegree = 0;     }     cout << "input edge information(start end):" <<endl;     for (j=0; j<g.edgeNum; j++)      {          cin >>edgeStart >>edgeEnd;          adjNode = new adjVertexNode;          adjNode->adjVertexPosition = edgeEnd;         adjNode->next=g.vertexNode[edgeStart].list;          g.vertexNode[edgeStart].list=adjNode;          //每增加一条边,则边的End顶点的入度加1         g.vertexNode[edgeEnd].indegree++;      }}void PrintAdjList(const Graph& g){    cout << "The adjacent list for graph is:" << endl;     for (int i=0; i < g.vertexNum; i++)    {        cout<< g.vertexNode[i].data << "->";        adjVertexNode* head = g.vertexNode[i].list;        if (head == NULL)            cout << "NULL";        while (head != NULL)        {            cout << head->adjVertexPosition <<" ";            head = head->next;        }        cout << endl;    }}//寻找0入度的点,否则取栈头VertexNode& FindZeroIndegree(Graph& g){    for (int i=0; i<g.vertexNum; i++)    {        if (g.vertexNode[i].indegree==0)            return g.vertexNode[i];    }    return g.vertexNode[0];}void TopSort(Graph& g){    cout << "The topsort is:" <<endl;    for (int i=0; i<g.vertexNum; i++)    {        VertexNode& v = FindZeroIndegree(g);        if (v.indegree!=NULL)            cout << "The graph has cycle, can not do topsort"<<endl;        // print graph as topsort.        cout<< v.data << " ";        // for each vertex w adjacent to v, --indegree        adjVertexNode* padjv = v.list;        while (padjv!=NULL)        {//!!这个算法这里破坏了原图中的入度信息。最后入度均为1            g.vertexNode[padjv->adjVertexPosition].indegree--;            padjv = padjv->next;        }        //避免入度信息均为零FindZeroIndegree找到删除的顶点,将删除的顶点入度置为-1        v.indegree=-1;    }    cout << endl;}void TopSort2(Graph& g){    queue<VertexNode> q;    for (int i=0; i<g.vertexNum; i++)    {        if (g.vertexNode[i].indegree == 0)            q.push(g.vertexNode[i]);    }    int count = 0;    cout << "The topsort is:" <<endl;    while (!q.empty())    {        VertexNode v = q.front();        q.pop();        cout<< v.data << " ";         count++;        adjVertexNode* padjv = v.list;        while (padjv!=NULL)        {//!!这个算法这里破坏了原图中的入度信息。最后入度均为1            if (--(g.vertexNode[padjv->adjVertexPosition].indegree)==0)                q.push(g.vertexNode[padjv->adjVertexPosition]);            padjv = padjv->next;        }    }    if (count != g.vertexNum)        cout << "The graph has cycle, can not do topsort"<<endl;}void DeleteGraph(Graph &g){    for (int i=0; i<g.vertexNum; i++)    {        adjVertexNode* tmp=NULL;        while(g.vertexNode[i].list!=NULL)        {            tmp = g.vertexNode[i].list;            g.vertexNode[i].list = g.vertexNode[i].list->next;            delete tmp;            tmp = NULL;        }    }}int main(int argc, const char** argv){    Graph g;    CreateGraph(g);    PrintAdjList(g);    TopSort(g);    DeleteGraph(g);    return 0;}
从上面的代码能发现FindZeroIndegree的时间复杂度为O(|V|),TopSort的时间复杂度为O(|V|2)。原因在于,每次删除顶点,只有邻接点需要调整入度,但FindZeroIndegree却是遍历了所有顶点,甚至已经删除的顶点。更为合理的方法是将每次遍历得出的入度为0的顶点放入一个队列即TopSort2。(原作者著)

leetcode题解:
https://leetcode.com/problems/course-schedule/
实际上就是拓扑排序的运用
java代码(包括测试主函数):
package leetcode;import java.util.LinkedList;import java.util.Queue;public class CourseSchedule {/** * 是否可以进行拓扑排序 *  * @param numCourses *            元素个数 * @param prerequisites *            关系数组 prerequistes是个二维数组,比如一个元素是{0,1}代表0--->1的路径,即0到1的有向图路径 * @return */public static boolean canFinish(int numCourses, int[][] prerequisites) {int[] map = new int[numCourses]; // 代表元素的入度数组for (int i = 0; i < prerequisites.length; i++) {map[prerequisites[i][1]]++; // 后面元素入度+1}Queue<Integer> que = new LinkedList<Integer>();for (int i = 0; i < map.length; i++) {if (map[i] == 0)que.add(i); // 入度为0的元素加入到队列中}int count = que.size();while (!que.isEmpty()) {int k = que.remove(); // 队列中取出第一个元素并移除for (int i = 0; i < prerequisites.length; i++) { // 遍历此节点是否有指向的节点if (k == prerequisites[i][0]) {int l = prerequisites[i][1];map[l]--; // 指向的节点度数-1if (map[l] == 0) { // 如果度数为0,则加入队列中,计数+1que.add(l);++count;}}}}return count == numCourses; // 如果最后count的数字等于元素个数,说明全部遍历完成没有闭环,返回true,否则返回false}public static void main(String[] args) {// TODO Auto-generated method stubint countNum = 6;int[][] prerequisites = { { 0, 2 }, { 1, 2 }, { 1, 3 }, { 1, 4 }, { 2, 3 }, { 2, 5 }, { 3, 5 }, { 4, 5 } };System.out.println(canFinish(countNum, prerequisites));}}
此外,也可以运用bfs遍历,具体可见博客http://blog.csdn.net/dm_vincent/article/details/7714519


0 0
原创粉丝点击