宽度优先搜索(BFS)

本节重点讲解宽度优先搜索算法。
希望自己的博客能够通俗易懂,与君同学习,共进步。
由于本人实力与精力有限,部分图片可能来源于网络,如有侵权,请联系我删除。

大纲

  • 二叉树上的宽搜
  • 图上的宽搜
    • 拓扑排序
  • 棋盘上的宽搜

前言:我们学习任何一种算法,一定要掌握(背会)三件事:1:算法定义;2:该算法什么时候使用;3:随口举几个常用地方的例子。

什么是BFS

由于本博客篇幅有限,请大家转到此处阅读。

什么时候使用BFS

树的遍历(其实树的层序遍历可以归为图的层序遍历,因为树是图的一种特例,即树属于图)

  • 层序遍历

图的遍历

  • 层级遍历
  • 由点及面
  • 拓扑排序

最短路径

  • 仅限简单图求最短路径
  • 即,图中每条边长度都是1,且没有方向

对以上使用地方进行举例练习

二叉树上的宽度优先遍历
题目:LeetCode 429
思路:这道题就是二叉树的层序遍历,只是比最基础版的层序遍历多了一个要求,就是要分层输出。只要同学们掌握了基础版的二叉树层序遍历再稍加思考就可以写出来了。(好吧!其实最开始我也是看别人的,哪来的稍加思考唉)

// 我们现写一个基础版的层序遍历,然后再修改成按层输出的格式。
//好吧,LeetCcode是多叉树,我们这里就写多叉树了哦。反正都是一样的。
public List<Integer> levelOrder(Node root) {
	// 这里是对特殊情况的处理
	if (root == null) return null;
	// 这里创建要输出的结果list
	List<Integer> list = new ArrayList<Integer>;

	// 下面就是遍历喽,记得层序遍历的时候把每个遍历到的量都放入list即可
	// 准备队列
	Queue<Node> queue = LinkedList<Node>();
	// 放入头节点
	queue.offer(root);
	// 循环体,循环条件是队列不为空
	while (!queue.isEmpty()) {
		// 从队列头部取出一个节点并记录下来
		Node node = queue.poll();
		list.add(node.val);
		// 对于二叉树这里需要把上面取出节点的两个相邻节点放入队列,这里是多叉树,我们就要把许多个相邻的节点放入队列,怎么放?当然是循环啦。
		// 这里采用增强for循环啦哈
		for (Node temp : node.children) {
			if (temp != null) queue.offer(temp);
		}
	}
}

相信大家一定能看懂上面的程序,如果你看不懂,那么我上面提到的博客里面的2和3题你真的会了吗?不会的话要向我不耻下问哦

// 现在我们回到leetCode这道题,对了,上面那个程序是基础,是必须会的,不会你不用看下面的了,不是下面的程序难,是你还没有到达写下面程序的水平
/*我们对上面的程序进一步思考,上面的程序其实就是我们按层遍历到的点依次放入一个最终的结果list中。
可以说是一股脑都放进去了吧,现在我们是要在遍历时把每一行的数据封装成一个list集合,然后放入结果集中。
怎么可以做到这一点呢?我们只需要知道每一层有多少节点即可。我自己想的是用两个变量,没想到下面的程序用一个变量就解决了。所有我就借鉴了人家的程序哈哈哈,来学习一下。大家要搞懂我的程序,然后在LeetCcode上AC了。
大家对于这个程序还是要理解并记住。
其实思路很简单,我们上面那个程序你这样理解就可以了。我们层序遍历的实质其实就是把队列中当前
层的节点从队列中完全吐出来(吐几次,其实就是queue.size()),并在吐出时放入相邻的未遍历的非空节点。一定有同学对这句话理解不清楚。我就举例说明。注:上面的话就发生在循环体中
现在假设队列中只有一个节点(就是头节点,我们程序来到了循环体),那队列中有几个元素?1个,那么
我们接下来需要做什么事?把队列中这一层的元素全部吐出来让我们蹂躏(这一层就是头节点所在层,就一个),然后把这一层所有节点相邻的未访问的非空节点放入队列(假设第二层3个节点,那么这里放入3个元素进入队列)。此时由于是循环,我们就又来到了开头,此时这一层有3个元素。我们就把这一层第三个元素(用都是循环哦)拿来蹂躏,你可以输出,可以放入集合,随你....
*/
//
class Node {
	public int val;
	public List<Node> chileren;

	public Node() {};

	public Node(int val, List<Node> children) {
		this.val = val;
		this.children = children;
	}
}
class Solution {
    public List<List<Integer>> levelOrder(Node root) {
        
        List<List<Integer>> list = new ArrayList<List<Integer>>();
        
        if (root == null) return list;
        
        Queue<Node> queue = new LinkedList<Node>();
        queue.offer(root);
        
        while(!queue.isEmpty()) {
            List<Integer> level = new ArrayList<Integer>();
            int size = queue.size();
            for (int i = 0; i < size; i++) {
                Node node = queue.poll();
                level.add(node.val);
                for (Node temp : node.children) {
                    if (temp != null) queue.offer(temp);
                }
            }
            list.add(level);
        }
        
        return list;
    }
}

对于这道题我们有几个点需要回顾记忆

1.宽搜是使用__作为主要的数据结构?使用栈可以吗?
回答:队列,栈可以,但是很麻烦。比如可以使用两个栈实现一个队列,但是何必呢?
2.我们平时按层遍历我们输出时不需要分层,那么当需要分层时和不需要分层时区别在哪里?
回答:其实需要分层就是不需要分层的特例,在处理时只需要多一个循环即可(这个循环的目的时处理当前层的所有结点,循环次数时queue.size,在这个循环中,我们会把该层的所有点都遍历到,并把这一层所有相接的点放入队列中以备后续遍历)
3.程序中我们确定当前层用的是size=queue.size(),那么如果我们直接for (int i = 0; i < queue.size(); i++)会怎么样?
回答:会乱套,好吧,其实大家只要理解了这个程序的实质,就知道会怎样了?我就不说了,其实主要是写不出。

学习了这道题你必须练习以下题目,强调,我这里写代码目的是大家参考,同学们写的时千万不要抄。不会我们可以交流。

LeetCode 102

class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> result = new ArrayList<List<Integer>>();
        
        if (root == null) return result;
        
        Queue<TreeNode> queue = new LinkedList<TreeNode>();
        queue.offer(root);
        
        while (!queue.isEmpty()) {
            List<Integer> level = new ArrayList<Integer>();
            int size = queue.size();
            for (int i = 0; i < size; i++) {
                TreeNode node = queue.poll();
                level.add(node.val);
                if (node.left != null) queue.offer(node.left);
                if (node.right != null) queue.offer(node.right);
            }
            result.add(level);
        }
        
        return result;
    }
}

LeetCode 107

//这道题其实就只改了一句代码,就是将每行遍历得到的list放入最终结果list中的头部,sh上一题是放尾部。
class Solution {
    public List<List<Integer>> levelOrderBottom(TreeNode root) {
        List<List<Integer>> result = new ArrayList<List<Integer>>();
        
        if (root == null) return result;
        
        Queue<TreeNode> queue = new LinkedList<TreeNode>();
        queue.offer(root);
        
        while (!queue.isEmpty()) {
            int size = queue.size();
            List<Integer> level = new ArrayList<Integer>();
            for (int i = 0; i < size; i++) {
                TreeNode node = queue.poll();
                level.add(node.val);
                if (node.left != null) queue.offer(node.left);
                if (node.right != null) queue.offer(node.right);
            }
            result.add(0, level);
        }
        return result;
    }
}

LeetCode 1161

class Solution {
    public int maxLevelSum(TreeNode root) {
        if (root == null) return 0;
        
        Queue<TreeNode> queue = new LinkedList<TreeNode>();
        queue.offer(root);
        int maxSum = Integer.MIN_VALUE;
        int resLevel = 1;
        int curLevel = 0;
        while (!queue.isEmpty()) {
            curLevel++;
            int size = queue.size();
            int levelSum = 0;
            for (int i = 0; i < size; i++) {
                TreeNode node = queue.poll();
                levelSum += node.val;
                if (node.left != null) queue.offer(node.left);
                if (node.right != null) queue.offer(node.right);
            }
            if (maxSum < levelSum) {
                maxSum = levelSum;
                resLevel = curLevel;
            }
        }
        return resLevel;
    }
}

LeetCode 637

class Solution {
    public List<Double> averageOfLevels(TreeNode root) {
        List<Double> res= new ArrayList<Double>();
        
        if (root == null) return res;
        
        Queue<TreeNode> queue = new LinkedList<TreeNode>();
        queue.offer(root);
        
        while (!queue.isEmpty()) {
            int size = queue.size();
            Double levelSum = 0.0;
            for (int i = 0; i < size; i++) {
                TreeNode node = queue.poll();
                levelSum += node.val;
                
                if (node.left != null) queue.offer(node.left);
                if (node.right != null) queue.offer(node.right);
            }
            res.add(levelSum / size);
        }
        
        return res;
    }
}

LeetCode 662
虽然这道题在LeetCode标记为中等,但要比上面几个都难一点,我的代码没有进行注释,大家可以去LeetCode看一下官方答案,和我的差不多,不过也没有注释,如果大家有不会的地方,我们可以交流。

class Solution {
    public int widthOfBinaryTree(TreeNode root) {
        int res = 0;
        if (root == null) return res;
        
        Queue<AnnotatedNode> queue = new LinkedList<AnnotatedNode>();
        queue.offer(new AnnotatedNode(root, 0));
        
        res = 1;
        
        while (!queue.isEmpty()) {
            int size = queue.size();
            boolean flag = true;
            int levelFirstPos = -1;
            for (int i = 0; i < size; i++) {
                AnnotatedNode AnNode = queue.poll();
                if (AnNode.node != null && flag) {
                    levelFirstPos = AnNode.pos;
                    flag = false;
                }
                res = AnNode.pos - levelFirstPos + 1 > res && levelFirstPos != -1 && AnNode.node != null ? AnNode.pos - levelFirstPos + 1 :res;
                if (AnNode.node != null) {
                    queue.offer(new AnnotatedNode(AnNode.node.left, AnNode.pos * 2));
                    queue.offer(new AnnotatedNode(AnNode.node.right, AnNode.pos * 2 + 1));
                }
            }
        }
        
        return res;
    }
}
class AnnotatedNode {
    TreeNode node;
    int pos;
    AnnotatedNode(TreeNode node, int pos) {
        this.node = node;
        this.pos = pos;
    }
}

LeetCode 103

// 这道题其实就是当每一行放入数据时,奇数行从左到右放:即levelList.add(node.val),偶数号从右往左放:即levelList.add(0,node.val)。
class Solution {
    public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
        List<List<Integer>> res = new ArrayList<List<Integer>>();
        if (root == null) return res;
        
        Queue<TreeNode> queue = new LinkedList<TreeNode>();
        queue.offer(root);
        int flag = 1;
        
        while (!queue.isEmpty()) {
            int size = queue.size();
            List<Integer> levelList = new ArrayList<Integer>();
            for (int i = 0; i < size; i++) {
                TreeNode node = queue.poll();
                if (flag == 1) {
                    levelList.add(node.val);
                }else {
                    levelList.add(0, node.val);
                }
                if (node.left != null) queue.offer(node.left);
                if (node.right != null) queue.offer(node.right);
            }
            flag *= -1;
            
            res.add(levelList);
        }
        
        return res;
    }
}

二叉树的序列化
我想关于关于序列化这个词语我已经不需要作任何解释啦!什么,你不知道?那就百度下,概念而已,尔等莫觉得难。当然我这里还是稍微提一下哦!下面的话如果你看不懂,那就大概读下就可以了,没有任何影响,会做后面的题就可以了。

序列化其实就是将内存中结构化的数据编程字符串的过程。序列化(object to string)与反序列化(string to object)是成对出现的。
我们什么时候需要序列化呢?

  1. 将内存中的数据持久化存储时:内存中重要的数据不能只是呆在内存里,这样断电就没有了,所以需要用一种方式写入硬盘,在需要的时候,再从硬盘中读出来在内存中重新创建。
  2. 网络传输时:机器与机器之间交换数据的时候,不可能互相读取对方的内存。只能将数据变成字符流数据(字符串)后通过网络传输过去。接收的一方再将字符串解析后到内存中。

常用的一些序列化手段

  • XML
  • Json
  • Thrift(made by Facebook)
  • ProtoBuf(made by Google)

一些序列化的例子:

  • 比如一个数组,里面都是整数,我们可以简单的序列化为**[1,2,3]**
  • 一个整数链表,我们可以序列化为1->2->3
  • 一个哈希表(HashMap),我们可以可以序列化为**{“key”:“value”}**

序列化算法设计时需要考虑的因素:

  • 压缩率:对于网络传输和磁盘存储而言,当然希望更节省存储空间。如Thrift,ProtoBuf都是为了更快的传输数据和节省存储空间而设计的。
  • 可读性:我们希望把一组数据序列化后能够直接直接看懂原始数据是什么。

我们这里重点说明二叉树的序列化,概念很简单哦,只需花费你1分钟百度下就知道了。
二叉树序列化与反序列化的方式很多,我们一般是用什么方式序列化,就用什么方式反序列化。现在我们就用层序遍历的方式解决二叉树序列化与反序列化的问题。
题目:LeetCode 449
思路:这题是常见面试题,这里用层序遍历。思路就是上面层序遍历的模板。

// 不好意思,这道题我不会,以后再补
// 此题非常重要,务必要会,其实它也弥补了模板代码中对于节点为null1的处理的不足。

学习了这道题你必须练习以下题目,强调,我这里写代码目的是大家参考,同学们写的时千万不要抄。不会我们可以交流。
LeetCode 297、LeetCode 428

图上的宽度优先遍历——层级遍历
图上的宽度优先遍历与图上的宽度优先遍历有什么区别?????
回答:大师回答是没有区别,有人说树是图的一种,但我感觉任何一张图我们却可以把它看成树来处理,不信你提着图上的任一个节点试试,不就成了树了吗?但是图中时存在环的,存在环就意味着,同一个节点可能重复进入队列,如果这种情况不处理的话,就有可能会永远循环下去。

图上的宽度优先遍历——由点及面
LeetCode 261

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值