第一章--核心套路篇 之 BFS算法套路框架

BFS算法套路框架

BFS(Breath First Search)和DFS(Depth First Search) 是两种十分常用的算法,其中DFS算法可以认为是回溯算法

BFS算法和核心就是将问题抽象成图,从一点开始进行扩散,一般来说写BFS的时候均使用队列,每次将一个结点周围的所有结点加入到队列中

BFS相对于DFS的主要区别是:BFS找到的路径一定是最短的,但代价是空间复杂度比DFS高

算法框架

func BFS(start Node, target Node) {
  // 假设Queue是已经实现好的队列
  var q Queue 
  
  // 保存已经访问过的结点,避免重复访问
	visited := make(map[Node]bool)

	// 将开始结点加入到队列中
	q.Push(start)
	visited[start] = true

	var step = 0

	for !q.Empty() {
		s := q.Size()
		// 将队列中的所有结点取出来
		for i := 0; i < s; i++ {
      // 删除并取出队列首元素
			n := q.Poll()
			// 如果是目标结点
			if n == target {
				return step
			}

			// 否则如果其相邻结点没有被访问过就加入到队列中
			for _, i := range n.Adjacency {
				if !visited[i] {
					q.Push(i)
					visited[i] = true
				}
			}
    }
    // 又访问了一层
		step++
	}
}

队列是BFS中的核心数据结构,n.Adjacency泛指与结点相邻的其他结点,visited作用是为了防止走回头路,大部分的时候是需要的,但是对于类似一般的二叉树结构,没有子节点到父结点的指针,不会走回头路就不需要使用visited进行标记

二叉树的最小高度

输入一个二叉树,计算它的最小高度,也就是根节点到叶子结点的短距离

那么这道题应该如何套用BFS呢?该题中的开始结点为start,终点就是最靠近根节点的叶子结点,也就是左子树为nil,右子树也为nil

func getMinHeight(root *Node)int{
  if root == nil{
    return 0
  }
  // 假设声明一个队列
  var q Queue
  // 将根节点加入到队列中
	q.Push(root)
	depth := 1
	for !q.Empty(){
		size := q.Size()
		for i:=0 ; i<size ; i++{
	      	n := q.Poll()
	      	// 结束条件
			if n.Left == nil && n.Right== nil{
				return depth
	      	}
	      	// 左节点
			if n.Left != nil{
				q.Push(n.Left)
	      	}
	      	// 右结点
			if n.Right != nil{
				q.Push(n.Right)
			}
    }
    // 增加一层深度
		depth ++
	}

	return depth
}

当然其实求解二叉树的最大高度也可以使用BFS进行求解,我们只要将队列中的数据全部遍历完,这样我们肯定就遍历了最后一层结点,题目可见 LeetCode

下面是代码:

func maxDepth(root *TreeNode) int{
    if root == nil{
        return 0
    }
    q := make([]*TreeNode, 1)
    q[0] = root
    count := 0
    for len(q)>0{
        size := len(q)
        for i:=0;i<size;i++{
            n := q[0]
            q = q[1:]
            if n.Left != nil{
                q = append(q, n.Left)
            }

            if n.Right != nil{
                q = append(q, n.Right)
            }
        }
        count ++
    }
    return  count
}

在探讨复杂问题之前,先回答下面的两个问题:

1. 为什么BFS可以找到最短距离,但是DFS不行?

在BFS中,只有当每一个结点都向下移动一步的时候,才会使得depth加一,所以可以保证一旦找到一个终点,那么所需要的步数是最小的,BFS最坏的时间复杂度为 O ( N ) O(N) O(N)

DFS同样可以求解这类问题,但是需要遍历所有的路径才能找到最短的路径,虽然说DFS在Big O衡量标准下最坏的时间复杂度都是 O ( N ) O(N) O(N),但是BFS更加高效,我们可以认为,在求解的过程中,BFS走的是线,而DFS走的是点。

2. 既然BFS这么好,那么为啥要使用DFS?

BFS可以找到最短距离,但是空间复杂度相对而言比较高,而DFS的空间复杂度较低

一般而言,在求解最短路径的时候使用BFS,其他时候更多的使用BFS

解开密码锁的最小次数

LeetCode 752题

我们可以每一个密码看出一个结点,每一个密码进行变化之后可以衍生出其他的不同密码,一个结点可以连接到其他另外8个顶点,整个求解过程就是一个图的遍历问题,我们要想获取到最小的次数,那么可以使用BFS来进行求解。

在这个题目中,开始结点就是0000,而终点就是所给的target,但是这个不想二叉树,有可能会走回头路,所以需要使用visited进行

代码如下

func openLock(deadends []string, target string) int {
	q := make([]string, 1)

	visited := make(map[string]bool)
	count := 0
	q[0] = "0000"

	for len(q) > 0 {
		s := len(q)
		for i := 0; i < s; i++ {
			cur := q[0]
			// q = append([]string{}, q[1:]...)
			q = q[1:]
			// 如果是死锁
			if isDead(deadends, cur) {
				continue
			}

			if cur == target {
				return count
			}

			for i := 0; i < 4; i++ {
				u := up([]byte(cur), i)
				if !visited[u] {
					q = append(q, u)
					visited[u] = true
				}

				d := down([]byte(cur), i)
				if !visited[d] {
					q = append(q, d)
					visited[d] = true
				}
			}
		}

		count++
	}

	return -1
}

func isDead(deadends []string, lock string) bool {
	for _, deadend := range deadends {
		if deadend == lock {
			return true
		}
	}
	return false
}

// 向上拨动一格
func up(s []byte, i int) string {
	if s[i] == '9' {
		s[i] = '0'
	} else {
		s[i] += 1
	}
	return string(s)
}

// 向下拨动一格
func down(s []byte, i int) string {
	if s[i] == '0' {
		s[i] = '9'
	} else {
		s[i] -= 1
	}
	return string(s)
}

这道题还可以使用双向BFS进行求解,具体见: BFS框架,这里不做讲解

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值