Leetcode --- 課程表問題(拓?fù)渑判颍?/h2>

寫在前:拓?fù)渑判?strong>本質(zhì)是BFS和貪心算法,是這兩種算法在有向圖應(yīng)用的專有名詞,即拓?fù)渑判蜥槍?duì)有向圖問題。參考這里。

  • 拓?fù)渑判驅(qū)嶋H上應(yīng)用的是貪心算法。貪心算法簡(jiǎn)而言之:每一步最優(yōu),全局就最優(yōu)。

  • 具體到拓?fù)渑判?,每一次都從圖中刪除沒有前驅(qū)的頂點(diǎn),這里并不需要真正的做刪除操作,我們可以設(shè)置一個(gè)入度數(shù)組,每一輪都輸出入度為 0 的結(jié)點(diǎn),并移除它、修改它指向的結(jié)點(diǎn)的入度(?1即可),依次得到的結(jié)點(diǎn)序列就是拓?fù)渑判虻慕Y(jié)點(diǎn)序列。如果圖中還有結(jié)點(diǎn)沒有被移除,則說明“不能完成所有課程的學(xué)習(xí)”。

  • 拓?fù)渑判虮WC了每個(gè)活動(dòng)(在這題中是“課程”)的所有前驅(qū)活動(dòng)都排在該活動(dòng)的前面,并且可以完成所有活動(dòng)。拓?fù)渑判虻慕Y(jié)果不唯一。拓?fù)渑判蜻€可以用于檢測(cè)一個(gè)有向圖是否有環(huán)。相關(guān)的概念還有 AOV 網(wǎng),這里就不展開了。

ps:入度:指向當(dāng)前節(jié)點(diǎn)的節(jié)點(diǎn)個(gè)數(shù);后繼節(jié)點(diǎn):當(dāng)前節(jié)點(diǎn)指向的節(jié)點(diǎn)

拓?fù)渑判蜃⒁恻c(diǎn):

  • 拓?fù)渑判虻?strong>結(jié)果不唯一
  • 可以檢測(cè)有向圖是否有環(huán)(對(duì)于無向圖是否有環(huán)使用并查集這種數(shù)據(jù)結(jié)構(gòu))
  • 貪心點(diǎn):讓入度為1的點(diǎn)入隊(duì)
  • 刪除節(jié)點(diǎn)的操作,通過入度數(shù)組體現(xiàn)!

算法的基本流程

  • 在開始排序前,掃描對(duì)應(yīng)的存儲(chǔ)空間(使用鄰接表),將入度為 0 的結(jié)點(diǎn)放入隊(duì)列。
  • 只要隊(duì)列非空,就從隊(duì)首取出入度為 0 的結(jié)點(diǎn),將這個(gè)結(jié)點(diǎn)輸出到結(jié)果集中,并且將這個(gè)結(jié)點(diǎn)的所有鄰接結(jié)點(diǎn)(它指向的結(jié)點(diǎn))的入度減 1,在減 1 以后,如果這個(gè)被減 1的結(jié)點(diǎn)的入度為 0 ,就繼續(xù)入隊(duì)。
  • 當(dāng)隊(duì)列為空的時(shí)候,檢查結(jié)果集中的頂點(diǎn)個(gè)數(shù)是否和課程數(shù)相等即可。

代碼具體實(shí)現(xiàn)的時(shí)候,除了保存入度為 0 的隊(duì)列,我們還需要兩個(gè)輔助的數(shù)據(jù)結(jié)構(gòu)(本質(zhì)都是數(shù)組結(jié)構(gòu))

  • 鄰接表:通過結(jié)點(diǎn)的索引,我們能夠得到這個(gè)結(jié)點(diǎn)的后繼結(jié)點(diǎn),注:為了避免重復(fù)加入,鄰接表元素為hashset類型
  • 入度數(shù)組:通過結(jié)點(diǎn)的索引,我們能夠得到指向這個(gè)結(jié)點(diǎn)的結(jié)點(diǎn)個(gè)數(shù)。

這個(gè)兩個(gè)數(shù)據(jù)結(jié)構(gòu)在遍歷題目給出的鄰邊以后就可以很方便地得到。

應(yīng)用場(chǎng)景:任務(wù)調(diào)度計(jì)劃、課程安排

1.課程表(207 - 中)

題目描述:你這個(gè)學(xué)期必須選修 numCourses 門課程,記為 0 到 numCourses - 1 。

在選修某些課程之前需要一些先修課程。 先修課程按數(shù)組 prerequisites 給出,其中 prerequisites[i] = [ai, bi] ,表示如果要學(xué)習(xí)課程 ai 則 必須 先學(xué)習(xí)課程 bi 。例如,先修課程對(duì) [0, 1] 表示:想要學(xué)習(xí)課程 0 ,你需要先完成課程 1 。

請(qǐng)你判斷是否可能完成所有課程的學(xué)習(xí)?如果可以,返回 true ;否則,返回 false 。

示例:

輸入:numCourses = 2, prerequisites = [[1,0]]
輸出:true
解釋:總共有 2 門課程。學(xué)習(xí)課程 1 之前,你需要完成課程 0 。這是可能的。

輸入:numCourses = 2, prerequisites = [[1,0],[0,1]]
輸出:false
解釋:總共有 2 門課程。學(xué)習(xí)課程 1 之前,你需要先完成課程 0 ;并且學(xué)習(xí)課程 0 之前,你還應(yīng)先完成課程 1 。這是不可能的

代碼實(shí)現(xiàn)

public boolean canFinish(int numCourses, int[][] prerequisites) {
    if (numCourses <= 0) return false;
    // 不需要先修課程
    if (prerequisites.length == 0) return true;

    int[] inDegree = new int[numCourses];
    HashSet<Integer>[] adj = new HashSet[numCourses];
    for (int i = 0; i < numCourses; i++) {
        adj[i] = new HashSet<>();
    }
    // 遍歷鄰邊,得到入度數(shù)組和鄰接表
    for (int[] p : prerequisites) {
        inDegree[p[0]]++;
        adj[p[1]].add(p[0]);
    }

    Queue<Integer> queue = new LinkedList<>();

    // 加入入度為0的節(jié)點(diǎn)
    for (int i = 0; i < numCourses; i++) {
        if (inDegree[i] == 0) {
            queue.add(i);
        }
    }

    // 記錄已經(jīng)出隊(duì)的課程數(shù)量(選修的課程數(shù))
    int cnt = 0;
    while (!queue.isEmpty()) {
        Integer top = queue.poll();
        cnt++;
        // 遍歷當(dāng)前節(jié)點(diǎn)的所有后繼節(jié)點(diǎn)
        for (int successor : adj[top]) {
            // ps:通過重置后繼幾點(diǎn)的入度數(shù)組,“刪除”top節(jié)點(diǎn)
            inDegree[successor]--;
            if (inDegree[successor] == 0) {
                queue.add(successor);
            }
        }
    }
    return cnt == numCourses;
}

2.課程表II(210 - 中)

題目描述:現(xiàn)在你總共有 n 門課需要選,記為 0 到 n-1。在選修某些課程之前需要一些先修課程。 例如,想要學(xué)習(xí)課程 0 ,你需要先完成課程 1 ,我們用一個(gè)匹配來表示他們: [0,1]

給定課程總量以及它們的先決條件,返回你為了學(xué)完所有課程所安排的學(xué)習(xí)順序。

可能會(huì)有多個(gè)正確的順序,你只要返回一種就可以了。如果不可能完成所有課程,返回一個(gè)空數(shù)組。

示例:

輸入:numCourses = 2, prerequisites = [[1,0]]
輸出:true
解釋:總共有 2 門課程。學(xué)習(xí)課程 1 之前,你需要完成課程 0 。這是可能的。

輸入:numCourses = 2, prerequisites = [[1,0],[0,1]]
輸出:false
解釋:總共有 2 門課程。學(xué)習(xí)課程 1 之前,你需要先完成課程 0 ;并且學(xué)習(xí)課程 0 之前,你還應(yīng)先完成課程 1 。這是不可能的

代碼實(shí)現(xiàn)

public int[] findOrder(int numCourses, int[][] prerequisites) {
    if (numCourses <= 0) return new int[0];
    int[] inDegree = new int[numCourses];
    HashSet<Integer>[] adj = new HashSet[numCourses];

    for (int i = 0; i < numCourses; i++) {
        adj[i] = new HashSet<>();
    }

    for (int[] p : prerequisites) {
        inDegree[p[0]]++;
        adj[p[1]].add(p[0]);
    }

    int cnt = 0;
    int[] ans = new int[numCourses];
    Queue<Integer> queue = new LinkedList<>();

    // 入度為0的節(jié)點(diǎn)入隊(duì)列
    for (int i = 0; i < numCourses; i++) {
        if (inDegree[i] == 0) {
            queue.add(i);
        }
    }

    // 當(dāng)前課程修完,并加入結(jié)果集
    while (!queue.isEmpty()) {
        Integer top = queue.poll();
        ans[cnt] = top;
        cnt++;

        for (int successor : adj[top]) {
            inDegree[successor]--;
            if (inDegree[successor] == 0) {
                queue.add(successor);
            }
        }
    } 
    if (cnt == numCourses) return ans;
    return new int[0]; 
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容