22年44周

目录

1.所有可能的路径

2.课程表

3.课程表Ⅱ

4.打开转盘所

5.判断二分图


这周在刷回溯算法时刷到了有关图相关的东西,但是以前与图相关的遍历或者相关知识都已经忘记的差不多了,索性直接找了几个图相关的算法进行了复习。

图总体上其实比较类似于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
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值