目录
这周在刷回溯算法时刷到了有关图相关的东西,但是以前与图相关的遍历或者相关知识都已经忘记的差不多了,索性直接找了几个图相关的算法进行了复习。
图总体上其实比较类似于N叉树,遍历的算法模型和N叉树的遍历模型比较像。图有两种比较常见的存储格式,分别是邻接表和邻接矩阵。题目中一般比较常见的是邻接表。
图源: 图论基础及遍历算法 :: labuladong的算法小抄
图和N叉树遍历的区别就是,N叉树是不会有环存在的,就是遍历过程中不会进入到死循环状态,但是tu是有可能的,所以需要一个数组单独记录下曾经访问过哪些节点
1.所有可能的路径
链接:力扣
思路:用深度优先遍历算法就能直接解决
var res [][]int
var length int
//用来记录哪些节点曾经被访问过
var visited []int
//用的邻接表来表示的
func allPathsSourceTarget(graph [][]int) [][]int {
res = make([][]int,0)
length = len(graph)
visited = make([]int,length)
visited[0] = 1
Dfs(graph,[]int{0})
return res
}
func Dfs(graph [][]int,path []int) {
if path[len(path)-1] == length-1 {
temp := make([]int,len(path))
copy(temp,path)
res = append(res,temp)
return
}
edge := graph[path[len(path)-1]]
for i:=0;i<len(edge);i++ {
if visited[edge[i]] == 1 {
continue
}
path = append(path,edge[i])
visited[edge[i]] = 1
Dfs(graph,path)
path = path[:len(path)-1]
visited[edge[i]] = 0
}
}
2.课程表
链接:力扣
思路:这道题转换成计算机的语言,就是在问图有没有环,就是可以利用拓扑排序完成,看看能不能进行拓扑排序,就能判断是否存在环。拓扑排序用深度优先和广度优先其实都是可以的。深度优先算法检测是否有环,就是在遍历过程中,判断是否遍历到当前已经包含在当前路径里面的节点。
广度优先遍历就是,每次都是把入度为0 的节点取出来,放入到队列中,最后看下是否把所有节点都遍历过了,如果是就代表没有环,如果不是,就代表其中有环。
深度算法:
//先修课程对 [0, 1] 表示:想要学习课程 0 ,你需要先完成课程 1
//这个应该用1-->0来表示
//也就变相问这道题能不能完成拓扑排序
//先把图给建立出来,再来判断是否存在环,这是个好思路
//这个是深度有限遍历
var visited []int//记录哪些顶点被访问过
//var path []int//这个记录递归栈中的元素
var graph [][]int//有向图的邻接矩阵
var isHuan = false
func canFinish(numCourses int, prerequisites [][]int) bool {
if len(prerequisites) == 0 {
return true
}
visited = make([]int,numCourses)
graph = make([][]int,numCourses)
isHuan = false
for _,v := range prerequisites {
graph[v[1]] = append(graph[v[1]],v[0])
}
for i:=0;i<numCourses;i++ {
if visited[i] == 0 {
Dfs(i)
}
if isHuan {
return false
}
}
return true
}
func Dfs(start int) {
//已经访问过,就不用再访问了
if visited[start] == 2 {
return
}
//碰到恰巧正在访问的,所以就是有环了
if visited[start] == 1 || isHuan{
isHuan = true
return
}
visited[start] = 1
edge := graph[start]
for _,v := range edge {
Dfs(v)
}
visited[start] = 2
}
广度算法:
//先修课程对 [0, 1] 表示:想要学习课程 0 ,你需要先完成课程 1
//这个应该用1-->0来表示
//也就变相问这道题能不能完成拓扑排序
//先把图给建立出来,再来判断是否存在环,这是个好思路
//这个是深度有限遍历
var graph [][]int//有向图的邻接矩阵
var indegree []int//存储着每个节点的入度
var count int
func canFinish(numCourses int, prerequisites [][]int) bool {
if len(prerequisites) == 0 {
return true
}
indegree = make([]int,numCourses)
graph = make([][]int,numCourses)
//存储着遍历了多少的 节点
count = 0
queue := make([]int,0)
for _,v := range prerequisites {
graph[v[1]] = append(graph[v[1]],v[0])
indegree[v[0]]++
}
for k,v := range indegree {
if v == 0 {
queue = append(queue,k)
count++
}
}
Bfs(queue)
//如果count等于总结点数,就说明没有环
return count == numCourses
}
func Bfs(queue []int) {
for len(queue) > 0 {
cur := queue[0]
edge := graph[cur]
for _,v := range edge {
indegree[v]--
if indegree[v] == 0 {
queue = append(queue,v)
count++
}
}
queue = queue[1:]
}
}
3.课程表Ⅱ
链接:力扣
思路:这道题是上一道题的进阶版本,不仅需要判断是否存在环,还需要把拓扑排序给打印出来,也是深度和广度都行。深度的话最后的拓扑排序是需要把遍历的path给逆置一下才是最终的拓扑排序。广度的话直接遍历顺序就是相应的拓扑排序。
深度算法:
//估计得考虑下首先要找一个入度为0的
//先写出来一个不用找入度为0的,试试,看看能不能通过
var graph [][]int
var visited []int
var path []int
var isHuan = false
func findOrder(numCourses int, prerequisites [][]int) []int {
graph = make([][]int,numCourses)
visited = make([]int,numCourses)
path = make([]int,0)
fmt.Println(path)
isHuan = false
for _,v := range prerequisites {
graph[v[1]] = append(graph[v[1]],v[0])
}
for i:=0;i < numCourses;i++ {
if visited[i] == 0 {
Dfs(i)
}
if isHuan {
return []int{}
}
}
i,j := 0,numCourses-1
for i<j {
temp := path[i]
path[i] = path[j]
path[j] = temp
i++
j--
}
return path
}
func Dfs(start int) {
if isHuan || visited[start] == 2{
return
}
if visited[start] == 1 {
isHuan = true
return
}
//表示正在访问
visited[start] = 1
edges := graph[start]
for _,v := range edges {
Dfs(v)
}
visited[start] = 2
path = append(path,start)
}
4.打开转盘所
链接:力扣
思路:这道题如果不是在图算法这里出现,我觉得我可能还真和图联系不到一起来,
他让找最小的扭转次数,一般图的问题中,最短路径问题都是广度优先遍历问题,这里扭动的最小次数其实可以抽象为图的最短路径问题。
//广度遍历就是0000
//0001 0009 0010 0090 .。。 0100 1000
//深度的话是
//0001 0002 0003 0004 0005 0006 0007 0008 0009
//次数
var min int
//死亡数字
var hashDeadends map[string]bool
//曾经遍历过那些
var visited map[string]bool
//向上扭动
func up(cur string,i int) string {
curByte := []byte(cur)
temp := curByte[i] + 1
if temp == 58 {
temp = 48
}
curByte[i] = temp
return string(curByte)
}
//向下扭动
func down(cur string,i int) string {
curByte := []byte(cur)
temp := curByte[i] - 1
if temp == 47 {
temp = 57
}
curByte[i] = temp
return string(curByte)
}
func openLock(deadends []string, target string) int {
if target == "0000" {
return 0
}
hashDeadends = make(map[string]bool)
visited = make(map[string]bool)
for _,v := range deadends {
if v == "0000" {
return -1
}
hashDeadends[v] = true
}
min = 1
queue := make([]string,0)
queue = append(queue,"0000")
cur := queue[0]
curLen := 1
nexLen := 0
for len(queue) > 0 {
cur = queue[0]
if visited[cur] {
curLen--
queue = queue[1:]
if curLen == 0 {
curLen = nexLen
nexLen = 0
min++
}
continue
}
visited[cur] = true
for i:=0;i<4;i++ {
upString := up(cur,i)
downString := down(cur,i)
if target == upString || target == downString {
return min
}
if !hashDeadends[upString] {
nexLen++
queue = append(queue,upString)
}
if !hashDeadends[downString] {
nexLen++
queue = append(queue,downString)
}
}
queue = queue[1:]
curLen--
if curLen == 0 {
curLen = nexLen
nexLen = 0
min++
}
}
return -1
}
5.判断二分图
链接:力扣
思路:二分图就是这个节点的颜色和其所有相邻的节点的颜色都不同,如果能按照这种方式染色,就说明其是二分图。
var visited []int
var color []bool
func isBipartite(graph [][]int) bool {
visited = make([]int,len(graph))
color = make([]bool,len(graph))
for i:=0;i<len(graph);i++ {
if visited[i] == 0 {
visited[i] = 1
color[i] = true
res := Dfs(graph,i)
if !res {
return false
}
}
}
return true
}
func Dfs(graph [][]int,start int) bool {
edges := graph[start]
for _,v := range edges {
if visited[v] == 0 {
//染上不同的颜色
color[v] = !color[start]
visited[v] = 1
res := Dfs(graph,v)
//如果有不满足的节点,立即返回,不需要在进行遍历
if !res {
return false
}
}else if color[v] == color[start] {
return false
}
}
return true
}