【LeetCode】課程表(圖論判環 拓撲排序/dfs)
課程表( 拓撲排序/dfs 判環)
題目連結:https://leetcode-cn.com/problems/course-schedule/
題目大意:給定一個課程依賴關係圖,比如課程A依賴課程B,課程B依賴課程C,按照上述的依賴關係,能否學習完所有的課程?
先學C,再學B,最後學A即可
方式1:拓撲排序
我們按照圖論的思想,將每個課程看做一個節點,將課程間的這種依賴和被依賴的關係看做節點的出度和入度,即
依賴:出度
被依賴:入度
樣例如下
A依賴B等價於A節點指向B節點,那麼A節點的出度是1,B節點的入度是1
A依賴D等價於A節點指向B節點,那麼A節點現在的出度是2,D節點的入度是1
我們首先找到所有入度為0的節點,即這些節點不被任何節點依賴,我們可以先完成這些課程nums1,這些課程被完成後,依賴這些課程的節點nums2其入度也需要被修改,具體的修改操作為nums2節點的入度都需要減去1,因為nums1節點課程都完成了
然後我們繼續選取入度為0的點,直到沒有入度為0的點
對於這些入度為0的點的儲存我們可以採用佇列,先進先出
當佇列中沒有元素後,即所有入度為0的點都被處理了,如果處理的點總數等於節點總數,那麼證明可以存在一個拓撲順序,學習完所有課程,如果不等於,那麼證明圖中存在環,互相依賴死迴圈了,就不能學習完所有課程
這種方法叫做拓撲排序,出入佇列元素的先後順序就是拓撲排序的順序
時間複雜度:O(n+m)
- n 為課程數,m 為先修課程的要求數。這其實就是對圖進行廣度優先搜尋的時間複雜度。
空間複雜度:O(n+m)
- 雙層map儲存圖需要O(m),佇列儲存需要O(n)
func canFinish(numCourses int, prerequisites [][]int) bool { G:=make(map[int]map[int]int) indegree:=make(map[int]int) var queue []int // 構建圖和入度表 for i:=0;i<len(prerequisites);i++{ v1:=prerequisites[i][0] v2:=prerequisites[i][1] if _,ok:=G[v1];!ok{ G[v1]=make(map[int]int) } G[v1][v2]=1 indegree[v2]++ } // 入度為0的點加入佇列 for i:=0;i<numCourses;i++{ if indegree[i]==0{ queue=append(queue,i) } } c:=0 for len(queue)!=0{ temp:=queue[0] queue=queue[1:] // 符合拓撲排序的節點數量 c++ // temp 和 i 存在邊,則i的入度減1,如果i入度為0,則加入佇列 for i:=0;i<numCourses;i++{ if G[temp][i]==1{ indegree[i]-- if indegree[i]==0{ queue=append(queue,i) } } } } // 符合拓撲排序節點的數量等於節點總數量,證明不存在環,符合要求 if c==numCourses{ return true } return false }
方式2:dfs
拓撲排序的方法是基於入度為0的點考慮的,其實我們也可以基於出度為0的點考慮
出度為0意味著此節點不依賴其他節點,只可能被其他節點依賴
那如何尋找到出度為0的節點呢?
答案就是深度優先搜尋,dfs
有路徑就一直搜尋下去,深度優先
對於每個節點u,我們可以定義0,1,2三種狀態
-
0 未搜尋
- 代表此節點還沒有被搜尋過
-
1 搜素中
- 我們搜尋過這個節點,但還沒有回溯到該節點即該節點還沒有入棧,還有相鄰的節點沒有搜尋完成
-
2 搜素已完成
- 我們搜尋過並且回溯過這個節點,即該節點已經入棧,並且所有該節點的相鄰節點都出現在棧的更底部的位置,滿足拓撲排序的要求。
我們搜素一個節點x時,會搜尋x指向的所有節點
- 如果這些節點沒有被搜尋過,那麼搜尋它
- 如果這些節點處於搜尋中,也就是被其他dfs搜尋過了,那麼證明此圖中存在環!
- 如果此節點處於搜尋已完成,那麼跳過
時間複雜度:O(n+m), n是課程數量,m是依賴數量
空間複雜度:O(n+m) 儲存圖需要O(m),遞迴需要O(n)
var G map[int]map[int]int
var flag [100005]int
var N int
var result bool
func dfs(index int){
// index標記為已搜尋,但是還在搜尋和index有關的其他節點
flag[index]=1
for i:=0;i<N;i++{
if G[index][i]==1{
if flag[i]==0{
// i沒有被搜尋過,搜尋i
dfs(i)
if result==false{
return
}
}else if flag[i]==1{
// i已經被其他dfs標記過了,證明存在環,沒有合法拓撲排序
result=false
return
}else if flag[i]==2 {
continue
// i 此時已經在拓撲排序中,無需做任何操作
}
}
}
// index標記為加入到了拓撲排序中
flag[index]=2
}
func canFinish(numCourses int, prerequisites [][]int) bool {
G = make(map[int]map[int]int)
flag=[100005]int{}
N=numCourses
result=true
// 構建圖
for i:=0;i<len(prerequisites);i++{
v1:=prerequisites[i][0]
v2:=prerequisites[i][1]
if _,ok:=G[v1];!ok{
G[v1]=make(map[int]int)
}
G[v1][v2]=1
}
// 對每一個沒有被搜尋過的節點都進行一次搜尋
for i:=0;i<numCourses;i++{
if flag[i]==0{
dfs(i)
}
}
return result
}