拓撲排序(附LeetCode題目)


算法期中考到一題關於拓撲序的題目,覺得很值得一寫。

 

1.什么是拓撲序?

對一個有向無環圖進行拓撲排序,假如圖中存在一條從頂點A到頂點B的路徑,則拓撲序中頂點A出現在頂點B的前面。要注意的是,這是對有向無環圖而言的,假如圖是有環的,拓撲序就無從談起了。在這道題目中,已經假定了圖是一個無環圖。因此不需要進行檢查。

 

2.怎么得出拓撲序?

有兩種方法,分別基於BFS和DFS,時間復雜度都是O(|V| + |E|)。以這道題作為例子分別說一下:

(1)BFS

這是我最先想到的方法。我們需要:一個數組,統計每個頂點的入度數;一個隊列用於bfs。首先遍歷一次所有的邊,將每個頂點的入度數都求出來,而入度數為0的頂點說明沒有其他頂點指向它。因此先把入度數為0的頂點放進隊列中。

接着用一個循環每次從隊頭取出front,把它放進返回結果的列表中。然后遍歷一遍所有的邊,假如當前邊對應的起始頂點為front,則將邊對應的終結頂點的入度數減1(我們把front放進結果中,也視作把front從圖中去掉了,那入度數自然要減1了)。如果一個頂點的入度數變為0,說明已經沒有其他頂點指向它了,就可以把它放入隊尾。一直到隊列為空時,說明拓撲序已經得到了。

代碼如下:

class Solution {
public:
    vector<int> topologicalSort(int n, vector<pair<int, int> >& edges) {
        vector<int> res;
        int * in_degree = new int[n];
        queue<int> q;
        for (int i = 0; i < n; i++) {
            in_degree[i] = 0;
        }
        for (int i = 0; i < edges.size(); i++) {
            in_degree[edges[i].second]++;
        }
        for (int i = 0; i < n; i++) {
            if (in_degree[i] == 0) {
                q.push(i);
            }
        }
        while (!q.empty()) {
            int front = q.front();
            q.pop();
            res.push_back(front);
            for (int i = 0; i < edges.size(); i++) {
                if (edges[i].first == front) {
                    in_degree[edges[i].second]--;
                    if (in_degree[edges[i].second] == 0) {
                        q.push(edges[i].second);
                    }
                }
            }
        }
        return res;
    }
}; 

 

 

(2)DFS

據說這是神書《算法導論》中提到的算法:用深度搜索來遍歷整個圖,采用一個數組來保存每個頂點完成的時間,這樣這個數組就存放了按先后順序訪問完成的頂點了。然后我們按照頂點訪問的完成時間從大到小排序,得到的就是一個拓撲序了,具體證明如下(來自其他博客):

 

在這道題中,肯定不用真的開一個數組啊,代碼如下:

class Solution {
public:
    vector<int> topologicalSort(int n, vector<pair<int, int> >& edges) {
        vector<int> res;
        stack<int> s;
        int * isVisited = new int[n];
        for (int i = 0; i < n; i++) {
            isVisited[i] = 0;
        } 
        for (int i = 0; i < n; i++) {
            if (!isVisited[i]) dfs(edges, s, isVisited, i);
        }
        while (!s.empty()) {
            res.push_back(s.top());
            s.pop();
        }
        return res;
    }
    void dfs(vector<pair<int, int> >& edges, stack<int> & s, int * isVisited, int u) {
        isVisited[u] = 1;
        for (int i = 0; i < edges.size(); i++) {
            if (edges[i].first == u && !isVisited[edges[i].second]) {
                dfs(edges, s, isVisited, edges[i].second);
            }
        }
        s.push(u);
    } 
};

這樣就得出結果了。(個人認為還是BFS易理解一點)

 

3.針對這道題......

So sad,以上兩種方法在這道題都TLE了,因為對於每個頂點,我們都需要遍歷一次邊的數組,要想節省時間,我們就要花費空間,使對於每個頂點,我們只需要考慮以它為起始點的邊。

真正能AC的代碼:

class Solution {
public:
    vector<int> topologicalSort(int n, vector<pair<int, int> >& edges) {
        vector<int> res;
        vector<vector<int> > newedges(n, vector<int>());
        queue<int> q;
        vector<int> in_degree(n, 0);
        for (int i = 0; i < edges.size(); i++) {
            in_degree[edges[i].second]++;
            newedges[edges[i].first].push_back(edges[i].second);
        }
        for (int i = 0; i < n; i++) {
            if (in_degree[i] == 0) {
                q.push(i);
            }
        }
        while (!q.empty()) {
            int front = q.front();
            q.pop();
            res.push_back(front);
            for (int i = 0; i < newedges[front].size(); i++) {
                in_degree[newedges[front][i]]--;
                if (in_degree[newedges[front][i]] == 0) q.push(newedges[front][i]);
            }
        }
        return res;
    }
};

 

4.拋開這道題目——有環情況的判斷

可以利用上面的dfs方法,比如isVisited這個數組,我們可以多增一種情況,比如0為未訪問,1為已訪問,-1為正在訪問,當dfs搜索時遇到了一條邊終止頂點對應的isVisited元素為-1時,就說明圖中有環了(為-1說明我們是從這個頂點開始dfs的,現在又遇到了這個頂點...)。

另外一種判斷圖是否有環的方法,借助bfs(dfs也可,但既然用了dfs,直接用上面的方法好了),假如“生成拓撲序”后,還有頂點不在這個“拓撲序”里面,則圖就有環了(加雙引號是因為不能真正稱作“拓撲序”啊)。

 

 

LeetCode上有相應的題目,需要判斷有無環(可以試試上面兩種判斷方法):

(1)https://leetcode.com/problems/course-schedule/description/

用了上面的第二種方法:

class Solution {
public:
    bool canFinish(int numCourses, vector<pair<int, int>>& prerequisites) {
        int size = 0;
        vector<int> degree(numCourses, 0);
        queue<int> q;
        for (int i = 0; i < prerequisites.size(); i++) {
            degree[prerequisites[i].second]++;
        }
        for (int i = 0; i < numCourses; i++) {
            if (degree[i] == 0) {
                q.push(i);
            }
        }
        while (!q.empty()) {
            int front = q.front();
            q.pop();
            size++;
            for (int i = 0; i < prerequisites.size(); i++) {
                if (prerequisites[i].first == front) {
                    if (--degree[prerequisites[i].second] == 0) q.push(prerequisites[i].second);
                }
            }
        }
        return size == numCourses;
    }
};

 

(2)https://leetcode.com/problems/course-schedule-ii/description/

用了第一種方法:

class Solution {
public:
    vector<int> findOrder(int numCourses, vector<pair<int, int>>& prerequisites) {
        vector<int> res;
        vector<int> isVisited(numCourses, 0);
        vector<vector<int>> v(numCourses, vector<int>());
        for (int i = 0; i < prerequisites.size(); i++) {
            v[prerequisites[i].second].push_back(prerequisites[i].first);
        }
        stack<int> s;
        bool isCircled = false;
        for (int i = 0; i < numCourses; i++) {
            if (!isVisited[i]) {
                dfs(i, s, v, isVisited, isCircled);
            }
            if (isCircled) {
                break;
            }
        }
        if (isCircled) return vector<int>();
        while (!s.empty()) {
            res.push_back(s.top());
            s.pop();
        }
        return res;
    }
    void dfs(int u, stack<int> &s, vector<vector<int>>& v, vector<int> &isVisited, bool &isCircled) {
        if (isCircled) return;
        isVisited[u] = -1;
        for (int i = 0; i < v[u].size(); i++) {
            if (isVisited[v[u][i]] != 1) {
                if (isVisited[v[u][i]] == 0) {
                    dfs(v[u][i], s, v, isVisited, isCircled);
                }
                else {
                    isCircled = true;
                    return;
                }
            }
        }
        isVisited[u] = 1;
        s.push(u);
    }
};

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM