BFS、DFS與選課問題

1選課問題

  Leetcode上有這樣一道題:有代號0,1,2……n-1的n門課程。其中選擇某些課程需要另一些課程作爲前提條件。用一組pair來表示這些條件:[1,0],[1,2],表示如果要選修課程1,必須先選修課程0和課程2。問是否可能把這n門課程都選修一遍。

  這個問題看起來相當複雜。難點在於,一門課程可能需要多門課程作爲前提條件,這樣就很難找到一條可以不重複地遍歷所有課程的方法。

  比較笨的方法就是先找出那些不需要前提條件的課程,然後由他們出發將那些只需要他們作爲前提條件的課程修完。再循環往復。一直到某一次遍歷之後,並沒有修到新的課程。最後再檢查是否還有課程沒能修夠。這樣的話,最壞情況,我們是時間複雜度最壞能達到O(N^3)。使用了unordered_multimap作爲輔助工具,代碼被Accept了。但是運行時間是最好的coder的10倍。於是,細細研究他人的算法。

2 BFS

  其實。這道題難在用一維數據結構來表示這些前提條件,很容易增大時間複雜度。於是,考慮用二維數據結構來表示。

  圖!而題中給的條件明顯是一對多的。那麼,適合用鄰接鏈表法表示。採用數據結構vector<unordered_set<int>> map來表示。vector的每個元素表示由某門課程作爲前提條件的一組課程。每門課程需要的前提條件可以用一組向量表示vector<int> de。我們用入度稱呼這個值,也就是說在圖中,進入該節點有多少條路徑。那麼對於那些不需要前提條件就完成的課程,其入度爲0。

  BFS法,廣度優先。我們先找到一門入度爲0的課,然後由它作爲前提條件的課程的入度都可以減1,因爲這門課程學到了,就等於不需要這個前提條件了。而以它爲前提的課程就是該節點的鄰接節點。用這種方法,我們每次學到一門課程,N次就可以學完所有的課程,如果某一次沒有學到新的課程,那麼最終肯定是無法學完的。

  上代碼:    

class Solution {
public:
    bool canFinish(int n, vector<pair<int, int>>& pre) {
        vector<unordered_set<int> > gra(n);
        vector<int> de(n,0);
        //make graph
        for(auto a:pre)
        {
            gra[a.second].insert(a.first);
        }
        //calculate the indegree
        for(int i=0;i<n;++i)
        {
            for(auto a:gra[i])
                de[a]++;
        }
        
        for(int i=0;i<n;++i)
        {
            int j=0;
            for(;j<n;++j)
            {
                if(de[j]==0)    break;
            }
            if(j==n)    return 0;   //this time could not learn a new lesson and we won't make it
            de[j]=-1;   //in case next time we will learn this lesson again
            for(auto a:gra[j])
                de[a]--;    //any lesson take this lesson as it's prerequisites, it's indegree decrease 1
        }
        return 1;
    }
};

3 DFS

  那麼DFS(深度優先搜索)是怎麼樣的思路呢?其實,如果這個圖中存在一個環的話,那麼肯定無法完成任務。那麼什麼情況下會完不成任務呢?也就是說1->2,2->3,3->1,也就是說形成了一個環。所以我們把原問題轉化爲檢測圖中是否有環。檢測環的方法?從某個節點出發,如果又回到該節點,那麼此圖有環!

  我們依次從任意節點出發,遍歷該節點可以到達的所有節點,我們用一個向量vector<bool> onePath來記錄從該節點出發經過的節點。爲了防止重複,我們用變量vector<bool> path記錄所有遍歷過的節點。

  上代碼:

class Solution {
public:
    bool canFinish(int n, vector<pair<int, int>>& pre) {
        vector<unordered_set<int> > gra(n);
        vector<bool> path(n,0),onePath(n,0);
        //make graph
        for(auto a:pre)
        {
            gra[a.second].insert(a.first);
        }
        
        for(int i=0;i<n;++i)
        {
            if(path[i]) continue;
            if(dfs_circle(gra,i,path,onePath))
                return 0;
        }
        return 1;
    }
    bool dfs_circle(vector<unordered_set<int> >& gra,int node,vector<bool>& path,vector<bool>& onePath)
    {
        if(onePath[node]==1)    return true;
        path[node]=onePath[node]=1;
        for(auto a:gra[node])
            if(dfs_circle(gra,a,path,onePath))
                return 1;
        onePath[node]=0;
        return 0;
    }
};

  這種轉化問題的解法真得很巧妙!太贊!

  另外,對於很多條件有交叉的題目,就考慮用圖論方法解決,尤其是DFS和BFS算法。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章