(Leetcode) [Graph] Course schedule

Question

๋Œ€ํ•™๊ต ์ˆ˜๊ฐ•์‹ ์ฒญ์„ ์˜ˆ์‹œ๋กœ ๋“  ๋ฌธ์ œ์ด๋‹ค.
int numCourses: number of courses you have to take
vector prerequisites[i] = [a, b]: a์˜ prerequisite๋Š” b์ด๋‹ค. ๊ณ ๋กœ a๋ผ๋Š” ์ˆ˜์—…์„ ๋“ค์œผ๋ ค๋ฉด b๋ฅผ ๋จผ์ € ๋“ค์–ด๋†”์•ผ ํ•œ๋‹ค. Return true if you can finish all courses. Otherwise, return false.

Approach

Consider this problem as a graph.
e.g. if course u is a prerequisite of course v, add a directed edge from node u to node v.

If a cycle is detected in the graph, then it is not possible to finish all courses.


How to detect cycle in a directed graph?

Use DFS (Depth First Search). DFS for a connected graph produces a tree.
If there is a back edge (self-loop or node to one if its ancestors) present in the graph, there is a cycle in a graph.

detect-cycle-in-directed-graph.png


DFS is an algorithm for traversing or searching tree or graph data structures.
Basic idea: start from the root/any arbiytrary node, mark the node and move to the adjacent unmarked node and continue this loop until there is no unmarked adjacent node.

DFS๋Š” ๋ง ๊ทธ๋Œ€๋กœ ๊นŠ์ด ์šฐ์„  ํƒ์ƒ‰์ด๋‹ค.
๊ทธ๋ž˜ํ”„ ์ƒ์— ์กด์žฌํ•˜๋Š” ์ž„์˜์˜ ํ•œ ์ •์ ์œผ๋กœ๋ถ€ํ„ฐ ์—ฐ๊ฒฐ๋˜์–ด ์žˆ๋Š” ํ•œ ์ •์ ์œผ๋กœ๋งŒ ๋‚˜์•„๊ฐ„๋‹ค๋ผ๋Š” ๋ฐฉ๋ฒ•์„ ์šฐ์„ ์œผ๋กœ ์„ ํƒํ•œ๋‹ค. ์—ฐ๊ฒฐํ•  ์ˆ˜ ์žˆ๋Š” ์ •์ ์ด ์žˆ์„ ๋•Œ๊นŒ์ง€ ๊ณ„์† ์—ฐ๊ฒฐํ•˜๋‹ค๊ฐ€๊ฐ€ ๋”์ด์ƒ ์—ฐ๊ฒฐ๋˜์ง€ ์•Š์€ ์ •์ ์ด ์—†์œผ๋ฉด ๋ฐ”๋กœ ๊ทธ ์ „ ๋‹จ๊ณ„์˜ ์ •์ ์œผ๋กœ ๋Œ์•„๊ฐ€์„œ ์—ฐ๊ฒฐํ•  ์ˆ˜ ์žˆ๋Š” ์ •์ ์ด ์žˆ๋Š”์ง€ ์‚ดํŽด๋ณธ๋‹ค.
์ž๋ฃŒ๊ตฌ์กฐ๋Š” stack์„ ์ด์šฉํ•œ๋‹ค.
Time Complexity: O(V+E), where V=number of vertices, E=number of edges.

Algorithm of DFS

Use of recursive function.
Required features: index of the node and a visited array.

  1. Mark the current node as visited.
  2. Traverse all the adjacent and unmarked nodes and call recursive function with the index of the adjacent node.
class Graph {
public:
    map<int, bool> visited;
    map<int, vector<int>> adj;

    void addEdge(int v, int w) {
        adj[v].push_back(w);    // add w to v's list (edge from v to w)
    }

    void DFS(int v) {
        // return false if it meets a node which was visited (cycle is detected)
        // if (visited[v]==true)
        //     return false;

        // mark the current node as visited
        visited[v] = true;
        cout << v << " ";

        // recur for all the vertices adjacent to this vertex
        vector<int>::iterator i;
        for (i=adj[v].begin(); i != adj[v].end(); i++) {
            if (!visited[*i]) {
                DFS(*i);
            }
        }
    }

}

dfs-visualization


Solution

๊ทธ๋ž˜ํ”„์—์„œ cycle์ด ๋ฐœ๊ฒฌ๋˜๋ฉด false, ์•„๋‹ˆ๋ฉด true.

  1. Create a unordered map with key: course, value: list of adjacent nodes.
  2. Use vector visited to record all the visited nodes.
  3. ์ค‘๊ฐ„์— false๊ฐ€ ํ•˜๋‚˜๋ผ๋„ ๋‚˜์˜ค๋ฉด dfs ํ•จ์ˆ˜ ์•ˆ์— ์žˆ๋Š” for loop์— ์ธํ•ด์„œ ์—ฐ์†์œผ๋กœ false๊ฐ€ ๋‚˜์˜ค๊ธฐ ๋•Œ๋ฌธ์— returns false.
bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
    unordered_map<int, vector<int>> adjacencyList(numCourses);
    vector<int> visited(numCourses, 0);
    // vector<int> visited;

    // make list of prerequisites of all courses and add to a map.
    for (int i=0; i<prerequisites.size(); i++) {
        adjacencyList[prerequisites[i][0]].push_back(prerequisites[i][1]);
    }

    // since there may be more than one graph, loop is required.
    for (int i=0; i<numCourses; i++) {
        if (!dfs(i, adjacencyList, visited))
            return false;
    }

    return true;
}

bool dfs(int course, unordered_map<int, vector<int>>& adjacencyList, vector<int> visited) {
    // return false if the node is already visited
    if (visited[course]==1)
        return false;
    // if (find(visited.begin(), visited.end(), course) != visited.end())
    //     return false;
    if (adjacencyList[course].size()==0)
        return true;

    visited[course]=1;     // mark node as visiting
    // visited.push_back(course);
    for (int i=0; i<adjacencyList[course].size(); i++) {
        if (!dfs(adjacencyList[course][i], adjacencyList, visited))
            return false;
    }
    // visited.pop_back();
    adjacencyList[course] = {};
    return true;
}

course-schedule-drawing.jpg


Runtime

course-schedule-runtime.png

์œ ํŠœ๋ธŒ์— neetcode๋ผ๋Š” ์ฑ„๋„์— ์˜ฌ๋ผ์˜จ ํ’€์ด๋ฅผ ๋ดค์„๋•Œ visited๋ผ๋Š” list์— ๋ฐฉ๋ฌธํ•˜๊ณ  ์žˆ๋Š” ๋…ธ๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ  ๋„ฃ๊ณ  ํ•˜๊ธธ๋ž˜ vector push_back๊ณผ pop_back์„ ์ด์šฉํ•ด์„œ ์ถ”๊ฐ€ํ•˜๊ณ  ๋„ฃ๋Š” ๋ฐฉ์‹ ํ•˜๋‚˜์™€, ๊ทธ๋ƒฅ 0์œผ๋กœ ์ดˆ๊ธฐํ™”๋œ numCourses ์‚ฌ์ด์ฆˆ์˜ vector๋ฅผ ์ด์šฉํ•ด์„œ ๋ฐฉ๋ฌธํ•˜๊ณ  ์žˆ๋Š” ๋…ธ๋“œ๋ฅผ 1๋กœ ๋ฐ”๊ฟ”์ฃผ๋Š” ๋ฐฉ์‹ ํ•˜๋‚˜๋ฅผ ํ…Œ์ŠคํŠธ ํ•ด๋ณด์•˜๋‹ค.
๊ทธ ๊ฒฐ๊ณผ ๋„ฃ๊ณ  ๋นผ๋Š” ๋ฐฉ์‹์ด runtime์ด ์กฐ๊ธˆ ๋” ๊ธฐ๋‚˜ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ํ™•์—ฐํžˆ ๋œ ์žก์•„๋จน๋Š”๋‹ค.


์ฝ”๋ฉ˜ํŠธ

์‹œ๊ฐ„์„ ๋„ˆ๋ฌด ์˜ค๋ž˜ ์žก์•„๋จน์—ˆ๋‹ค. ์‚ฝ์งˆ์„ ๋„ˆ๋ฌด ๋งŽ์ด ํ–ˆ๋‹ค. ํ™•์‹คํžˆ ์ฒ˜์Œ ๋ฐฐ์šฐ๋Š” DFS๋ผ๋Š” ๊ฐœ๋…์ด๋ผ ๊ทธ๋ ‡๊ธฐ๋„ ํ•˜๊ณ , ์ง‘์ค‘๋ ฅ๋„ ํํŠธ๋Ÿฌ์กŒ๋‹ค. ์–ผ๋ฅธ ๋‹ค์Œ ๋ฌธ์ œ๋กœ ๋„˜์–ด๊ฐ€์•ผ๊ฒ ๋‹ค.


References

https://www.geeksforgeeks.org/find-whether-it-is-possible-to-finish-all-tasks-or-not-from-given-dependencies/
https://www.youtube.com/watch?v=EgI5nU9etnU

Categories:

Updated:

Leave a comment