一、有向无环图

一个无环有向图做有向无环图(Directed Acyclic Graph)。简称DAG 图

图论中,如果一个有向图无法从某个顶点出发经过若干条边回到该点,则这个图是一个有向无环图(DAG图)。

因为有向图中一个点经过两种路线到达另一个点未必形成环,因此有向无环图未必能转化成树,但任何有向树均为有向无环图。

使用有向无环图解题时,要先判断是否是有向无环题。如果任务x必须在任务y之前完成:x→y,而y→z。也就是说一般在涉及优先级限制的问题时,使用有向无环图的方法。

注意与并查集进行区分

关于并查集模板:https://blog.csdn.net/qq_41687938/article/details/112850265

二、有向无环图求解过程

做有向无环图题的时候,一共就三步:

1.根据题目意思及输入,理清两节点间的指向关系,构建由前指向后的有向无环图

也就是构建一个map,key为当前节点,val数组为当前节点指向的那些节点。

2.根据有向无环图,统计每个节点出现的次数,因为有的节点已经出现过,但还是可能由其他指向路径的节点再指回来,在输出的时候需要遍历其最后出现的地方,所以要记一下它出现的次数。

其中,头节点的次数记为-1,并将头节点保存起来,方便接下来的遍历。

3.因为有向无环图的输出一般都有要求按大小关系输出(本文按升序输出!),也就是构建一个优先队列来完成节点输出。每遍历一个节点就将其所指向的节点压入队列中,实现了某节点的下一层与当前节点层的其他节点的比较。并将遍历到的节点输出。直到队列中所有节点输出。

2.1 具体看题:

一个完整的软件项目往往会包含很多由代码和文档组成的源文件。编译器在编译整个项目的时候,可能需要按照依赖关系来依次编译每个源文件。比如,A.cpp 依赖 B.cpp,那么在编译的时候,编译器需要先编译 B.cpp,才能再编译 A.cpp。 假设现有 0,1,2,3 四个文件,0号文件依赖1号文件,1号文件依赖2号文件,3号文件依赖1号文件,则源文件的编译顺序为 2,1,0,3 或 2,1,3,0。现给出文件依赖关系,如 1,2,-1,1,表示0号文件依赖1号文件,1号文件依赖2号文件,2号文件没有依赖,3号文件依赖1号文件。请补充完整程序,返回正确的编译顺序。注意如有同时可以编译多个文件的情况,按数字升序返回一种情况即可,比如前述案例输出为:2,1,0,3

输入例子1:

"1,2,-1,1"

输出例子1:

"2,1,0,3"

三、C++代码模板: 

class Solution {
public:
    string compileSeq(string input) {
        //首先完成有向无环图的构建
        //统计图中各节点的个数 并标记头节点为-1
        //使用优先队列 按照先小后大的顺序遍历输出节点值
        
        int len = input.size();
        
        /*********构建有向无环图(指向关系)*********/
        map<int, vector<int>> mp;//first为先 second为后, 也就是second依赖于first
        string tmp;
        int idx = 0;
        for(auto& s:input){
            if(s != ',')
                tmp += s;
            else{
                mp[stoi(tmp)].push_back(idx++);
                string().swap(tmp);//清空string
            }
        }
        if(!tmp.empty())
            mp[stoi(tmp)].push_back(idx++);
        
        /**********统计各节点个数 并保存头节点***********/
        vector<int> indexcount(len, 0);//统计各节点个数
        priority_queue<int, vector<int>, greater<>> pq;//保存节点
        for(auto& m:mp){
            if(m.first == -1){
                for(auto& a:m.second){
                    pq.push(a);
                    indexcount[a] = -1;
                }
            }else{
                for(auto& a:m.second)
                    ++indexcount[a];
            }
        }
        
        /************根据指向关系遍历图 并按照优先队列输出结果********/
        vector<int> ans;
        while(!pq.empty()){
            int node = pq.top();
            pq.pop();//输出了就需要从优先队列中弹出
            ans.push_back(node);
            for(auto& m:mp[node]){
                if(--indexcount[m] == 0)//如果该节点是最后一次在图中出现 则放入队列中
                    pq.push(m);
            }
        }
        
        /***********输出结果************/
        string res;
        for(auto& i:ans){
            res += to_string(i);
            res.push_back(',');
        }
        if(!res.empty())
            res.pop_back();
        
        return res;
    }
};