精华内容
下载资源
问答
  • 规格为A4纸或A3纸折叠 佛山科学技术学院用四号宋体 实 验 报 告用小二号黑体 课程名称 数据结构实验 实验项目 实现DFS和BFS算法 专业班级 姓 名 学 号 指导教师 成 绩 日 期 用小四号宋体 一目的与要求 1通过本实验...
  • 广度优先搜索构建迷宫(BFS算法)动态构建过程的python 源代码,详情请移步本人博客<迷宫与寻路可视化(二)广度优先搜索构建迷宫(BFS算法)>
  • 在我们的最终项目中,有一部分算法的原理与BFS相似,因此我计划使用FPGA加速简单的BFS算法。 如果仍有空间,请使用加速的BFS算法作为模板来对我们项目的相应部分进行修改,以使用FPGA加速项目。 环境 g ++(GCC)...
  • 封装DFS、BFS算法、Prim算法、Kruskal算法、Dijstra算法、Floyd算法 上机作业: 定义采用邻接矩阵存储的图结构
  • BFS 算法框架

    千次阅读 2020-04-18 17:02:18
    文章目录BFS 算法框架一、BFS算法框架二、二叉树的最小高度三、解开密码锁的最少次数四、双向 BFS BFS 的核心思想应该不难理解的,就是把一些问题抽象成图,从一个点开始,向四周开始扩散。一般来说,我们写 BFS ...

    BFS 算法框架


    BFS 的核心思想应该不难理解的,就是 把一些问题抽象成图,从一个点开始,向四周开始扩散。一般来说,我们写 BFS 算法都是用「队列」这种数据结构,每次将一个节点周围的所有节点加入队列。

    BFS 相对 DFS 的最主要的区别是:BFS 找到的路径一定是最短的,但代价就是空间复杂度比 DFS 大很多,至于为什么,我们后面介绍了框架就很容易看出来了。

    一、BFS算法框架

    要说框架的话,我们先举例一下 BFS 出现的常见场景好吧,问题的本质就是让你在一幅「图」中找到从起点start到终点target的最近距离

    // 计算从起点 start 到终点 target 的最近距离
    int BFS(Node start, Node target) 
    {
        Queue<Node> q; // 核心数据结构
        Set<Node> visited; // 避免走回头路
    
        q.offer(start); // 将起点加入队列
        visited.add(start);
        int step = 0; // 记录扩散的步数
    
        while (q not empty) 
        {
            int sz = q.size();
            /* 将当前队列中的所有节点向四周扩散 */
            for (int i = 0; i < sz; i++) 
            {
                Node cur = q.poll();
                /* 划重点:这里判断是否到达终点 */
                if (cur is target)
                    return step;
                /* 将 cur 的相邻节点加入队列 */
                for (Node x : cur.adj())
                    if (x not in visited) 
                    {
                        q.offer(x);
                        visited.add(x);
                    }
            }
            /* 划重点:更新步数在这里 */
            step++;
        }
    }
    

    队列q就不说了,BFS 的核心数据结构cur.adj()泛指cur相邻的节点比如说二维数组中,cur上下左右四面的位置就是相邻节点visited的主要作用是防止走回头路,大部分时候都是必须的,但是像一般的二叉树结构,没有子节点到父节点的指针,不会走回头路就不需要visited

    二、二叉树的最小高度

    给定一个二叉树,找出其最小深度。

    最小深度是从根节点到最近叶子节点的最短路径上的节点数量。

    说明: 叶子节点是指没有子节点的节点。

    示例:

    给定二叉树 [3,9,20,null,null,15,7],
    
        3
       / \
      9  20
        /  \
       15   7
    返回它的最小深度  2.
    

    怎么套到 BFS 的框架里呢?首先明确一下起点start和终点target是什么,怎么判断到达了终点?

    显然 起点start就是root根节点终点target就是最靠近根节点的那个「叶子节点」

    叶子节点就是两个子节点都是null的节点:

    if (cur.left == null && cur.right == null) 
        // 到达叶子节点
    

    那么,按照我们上述的框架稍加改造来写解法即可:

    /**
     * Definition for a binary tree node.
     * struct TreeNode {
     *     int val;
     *     TreeNode *left;
     *     TreeNode *right;
     *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
     * };
     */
    class Solution {
    public:
        int minDepth(TreeNode* root) 
    {
        if (root == nullptr) 
        	return 0;
        queue<TreeNode*> q ;
        q.push(root);
        // root 本身就是一层,depth 初始化为 1
        int depth = 1;
    
        while (!q.empty()) 
        {
            int sz = q.size();
            /* 将当前队列中的所有节点向四周扩散 */
            for (int i = 0; i < sz; i++) 
            {
                TreeNode* cur = q.front();
                q.pop();
                /* 判断是否到达终点 */
                if (cur->left == nullptr && cur->right == nullptr) 
                    return depth;
                /* 将 cur 的相邻节点加入队列 */
                if (cur->left != nullptr)
                    q.push(cur->left);
                if (cur->right != nullptr) 
                    q.push(cur->right);
            }
            /* 这里增加步数 */
            depth++;
        }
        return depth;
    }
    
    };
    

    1、为什么 BFS 可以找到最短距离,DFS 不行吗?

    首先,你看 BFS 的逻辑,depth每增加一次,队列中的所有节点都向前迈一步,这保证了第一次到达终点的时候,走的步数是最少的。

    DFS 不能找最短路径吗?其实也是可以的,但是时间复杂度相对高很多。

    你想啊,DFS 实际上是靠递归的堆栈记录走过的路径,你要找到最短路径,肯定需要把二叉树中所有树杈都探索完才能对比出最短的路径有多长对不对

    而 BFS 借助队列做到一次一步「齐头并进」,是可以在不遍历完整棵树的条件下找到最短距离的。

    形象点说,DFS 是线,遍历所有可能的情况从而得处结论,BFS 是面,在遍历的过程中就可以直接得出结论;DFS 是单打独斗,BFS 是集体行动。

    2、既然 BFS 那么好,为啥 DFS 还要存在?

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

    还是拿刚才我们处理二叉树问题的例子,假设给你的这个二叉树是满二叉树,节点总数为N,对于 DFS 算法来说,空间复杂度无非就是递归堆栈,最坏情况下顶多就是树的高度,也就是O(logN)

    但是你想想 BFS 算法,队列中每次都会储存着二叉树一层的节点,这样的话最坏情况下空间复杂度应该是树的最底层节点的数量,也就是N/2,也就是O(N)。

    由此观之,BFS 还是有代价的,一般来说在找最短路径的时候使用 BFS,其他时候还是 DFS 使用得多一些(主要是递归代码好写)。

    三、解开密码锁的最少次数

    你有一个带有四个圆形拨轮的转盘锁。每个拨轮都有10个数字: ‘0’, ‘1’, ‘2’, ‘3’, ‘4’, ‘5’, ‘6’, ‘7’, ‘8’, ‘9’ 。每个拨轮可以自由旋转:例如把 ‘9’ 变为 ‘0’,‘0’ 变为 ‘9’ 。每次旋转都只能旋转一个拨轮的一位数字。

    锁的初始数字为 ‘0000’ ,一个代表四个拨轮的数字的字符串。

    列表 deadends 包含了一组死亡数字,一旦拨轮的数字和列表里的任何一个元素相同,这个锁将会被永久锁定,无法再被旋转。

    字符串 target 代表可以解锁的数字,你需要给出最小的旋转次数,如果无论如何不能解锁,返回 -1。

    示例 1:

    输入:deadends = ["0201","0101","0102","1212","2002"], target = "0202"
    输出:6
    解释:
    可能的移动序列为 "0000" -> "1000" -> "1100" -> "1200" -> "1201" -> "1202" -> "0202"。
    注意 "0000" -> "0001" -> "0002" -> "0102" -> "0202" 这样的序列是不能解锁的,
    因为当拨动到 "0102" 时这个锁就会被锁定。
    

    示例 2:

    输入: deadends = ["8888"], target = "0009"
    输出:1
    解释:
    把最后一位反向旋转一次即可 "0000" -> "0009"

    示例 3:

    输入: deadends = ["8887","8889","8878","8898","8788","8988","7888","9888"],
    						 target = "8888"
    输出:-1
    解释:
    无法旋转到目标数字且不被锁定。
    

    示例 4:

    输入: deadends = ["0000"], target = "8888"
    输出:-1
     
    

    提示:

    1.死亡列表 deadends 的长度范围为 [1, 500]。
    2.目标数字 target 不会在 deadends 之中。
    3.每个deadends 和 target 中的字符串的数字会在 10,000 个可能的情况 ‘0000’ 到 ‘9999’ 中产生。

    第一步,

    我们不管所有的限制条件,不管deadends和target的限制,就思考一个问题:如果让你设计一个算法,求所有可能的密码组合,你怎么做?

    穷举呗,再简单一点,如果你只转一下锁,有几种可能?总共有 4 个位置,每个位置可以向上转,也可以向下转,也就是有 8 种可能对吧。

    比如说从"0000"开始,转一次,可以穷举出"1000", “9000”, “0100”, “0900”…共 8 种密码。然后,再以这 8 种密码作为基础,对每个密码再转一下,穷举出所有可能…

    仔细想想,这就可以抽象成一幅图,每个节点有 8 个相邻的节点,又让你求最短距离,这不就是典型的 BFS 嘛,框架就可以派上用场了,先写出一个「简陋」的 BFS 框架代码再说别的:

    // 将 s[j] 向上拨动一次
    String plusOne(String s, int j) 
    {
        char[] ch = s.toCharArray();
        if (ch[j] == '9')
            ch[j] = '0';
        else
            ch[j] += 1;
        return new String(ch);
    }
    // 将 s[i] 向下拨动一次
    String minusOne(String s, int j) 
    {
        char[] ch = s.toCharArray();
        if (ch[j] == '0')
            ch[j] = '9';
        else
            ch[j] -= 1;
        return new String(ch);
    }
    
    // BFS 框架,打印出所有可能的密码
    void BFS(String target) 
    {
        Queue<String> q = new LinkedList<>();
        q.offer("0000");
    
        while (!q.isEmpty()) 
        {
            int sz = q.size();
            /* 将当前队列中的所有节点向周围扩散 */
            for (int i = 0; i < sz; i++) 
            {
                String cur = q.poll();
                /* 判断是否到达终点 */
                System.out.println(cur);
    
                /* 将一个节点的相邻节点加入队列 */
                for (int j = 0; j < 4; j++) 
                {
                    String up = plusOne(cur, j);
                    String down = minusOne(cur, j);
                    q.offer(up);
                    q.offer(down);
                }
            }
            /* 在这里增加步数 */
        }
        return;
    }
    

    这段 BFS 代码已经能够穷举所有可能的密码组合了,但是显然不能完成题目,有如下问题需要解决

    1、会走回头路。比如说我们从"0000"拨到"1000",但是等从队列拿出"1000"时,还会拨出一个"0000",这样的话会产生死循环

    2、没有终止条件,按照题目要求,我们找到target就应该结束并返回拨动的次数。

    3、没有对deadends的处理,按道理这些「死亡密码」是不能出现的,也就是说你遇到这些密码的时候需要跳过。

    完整代码

    class Solution {
    public:
        // 将 s[j] 向上拨动一次
    string plusOne(string ch, int j) 
    {
        
        if (ch[j] == '9')
            ch[j] = '0';
        else
            ch[j] += 1;
        return ch;
    }
    // 将 s[i] 向下拨动一次
    string minusOne(string ch, int j) 
    {
     
        if (ch[j] == '0')
            ch[j] = '9';
        else
            ch[j] -= 1;
        return ch;
    }
    
        int openLock(vector<string>& deadends, string target) 
    	{
        	// 记录需要跳过的死亡密码
        	set<string> deads;
        	for (string s : deadends) 
        		deads.insert(s);
        	
        	// 记录已经穷举过的密码,防止走回头路
        	set<string> visited;
        
        	queue<string> q;
        	// 从起点开始启动广度优先搜索
        	int step = 0;
        	q.push("0000");
        	visited.insert("0000");
    
       		while (!q.empty()) 
        	{
            	int sz = q.size();
            	/* 将当前队列中的所有节点向周围扩散 */
            	for (int i = 0; i < sz; i++) 
            	{
                	string cur = q.front();
    				q.pop();
    				
                	/* 判断是否到达终点 */
                	if (deads.count(cur))
                    	continue;
                	if (cur == target)
                    	return step;
    
                	/* 将一个节点的未遍历相邻节点加入队列 */
                	for (int j = 0; j < 4; j++) 
                	{
                    	string up = plusOne(cur, j);
                    	if (!visited.count(up)) 
                    	{
                        	q.push(up);
                        	visited.insert(up);
                    	}
                    	string down = minusOne(cur, j);
                    	if (!visited.count(down)) 
                    	{
                        	q.push(down);
                        	visited.insert(down);
                    	}
                	}
            	}
            	/* 在这里增加步数 */
            	step++;
        	}
        	// 如果穷举完都没找到目标密码,那就是找不到了
        	return -1;
    	}
    };
    

    至此,我们就解决这道题目了。有一个比较小的优化:可以不需要dead这个哈希集合,可以直接将这些元素初始化到visited集合中,效果是一样的

    四、双向 BFS

    你以为到这里 BFS 算法就结束了?恰恰相反。BFS 算法还有一种稍微高级一点的优化思路:双向 BFS,可以进一步提高算法的效率。

    这里就提一下区别:传统的 BFS 框架就是从起点开始向四周扩散,遇到终点时停止;而双向 BFS 则是从起点和终点同时开始扩散,当两边有交集的时候停止

    为什么这样能够能够提升效率呢?其实分析算法复杂度的话,它俩的最坏复杂度都是O(N),但是实际上双向 BFS 确实会快一些,我给你画两张图看一眼就明白了:
    在这里插入图片描述
    在这里插入图片描述

    图示中的树形结构,如果终点在最底部,按照传统 BFS 算法的策略,会把整棵树的节点都搜索一遍,最后找到target;而双向 BFS 其实只遍历了半棵树就出现了交集,也就是找到了最短距离。从这个例子可以直观地感受到,双向 BFS 是要比传统 BFS 高效的。

    不过,双向 BFS 也有局限,因为你必须知道终点在哪里。比如我们刚才讨论的二叉树最小高度的问题,你一开始根本就不知道终点在哪里,也就无法使用双向 BFS;但是第二个密码锁的问题,是可以使用双向 BFS 算法来提高效率的,代码稍加修改即可:

    int openLock(String[] deadends, String target) 
    {
        Set<String> deads = new HashSet<>();
        for (String s : deadends) 
        	deads.add(s);
        	
        // 用集合不用队列,可以快速判断元素是否存在
        Set<String> q1 = new HashSet<>();
        Set<String> q2 = new HashSet<>();
        Set<String> visited = new HashSet<>();
    
        int step = 0;
        q1.add("0000");
        q2.add(target);
    
        while (!q1.isEmpty() && !q2.isEmpty()) 
        {
            // 哈希集合在遍历的过程中不能修改,用 temp 存储扩散结果
            Set<String> temp = new HashSet<>();
    
            /* 将 q1 中的所有节点向周围扩散 */
            for (String cur : q1) 
            {
                /* 判断是否到达终点 */
                if (deads.contains(cur))
                    continue;
                
                if (q2.contains(cur))
                    return step;
                
                visited.add(cur);
    
                /* 将一个节点的未遍历相邻节点加入集合 */
                for (int j = 0; j < 4; j++) 
                {
                    String up = plusOne(cur, j);
                    if (!visited.contains(up))
                        temp.add(up);
                    String down = minusOne(cur, j);
                    if (!visited.contains(down))
                        temp.add(down);
                }
            }
            /* 在这里增加步数 */
            step++;
            // temp 相当于 q1
            // 这里交换 q1 q2,下一轮 while 就是扩散 q2
            q1 = q2;
            q2 = temp;
        }
        return -1;
    }
    

    双向 BFS 还是遵循 BFS 算法框架的,只是不再使用队列,而是使用 HashSet 方便快速判断两个集合是否有交集。

    另外的一个技巧点就是 while 循环的最后交换q1和q2的内容,所以只要默认扩散q1就相当于轮流扩散q1和q2。

    其实双向 BFS 还有一个优化,就是在 while 循环开始时做一个判断:

    // ...
    while (!q1.isEmpty() && !q2.isEmpty()) {
        if (q1.size() > q2.size()) {
            // 交换 q1 和 q2
            temp = q1;
            q1 = q2;
            q2 = temp;
        }
        // ...
    

    为什么这是一个优化呢?

    因为按照 BFS 的逻辑,队列(集合)中的元素越多,扩散之后新的队列(集合)中的元素就越多;在双向 BFS 算法中,如果我们每次都选择一个较小的集合进行扩散,那么占用的空间增长速度就会慢一些,效率就会高一些

    不过话说回来,无论传统 BFS 还是双向 BFS,无论做不做优化,空间复杂度都是一样的,只能说双向 BFS 是一种 trick 吧

    展开全文
  • BFS算法(广度优先搜索)java

    千次阅读 2020-12-15 21:35:56
    BFS:宽度优先搜索算法(又称广度优先搜索)是最简便的图的搜索算法之一,属于一种盲目搜寻法,目的是系统地展开并检查图中的所有节点,以找寻结果。它并不考虑结果的可能位置,彻底地搜索整张图,直到找到结果为止...

    BFS介绍

    BFS:宽度优先搜索算法(又称广度优先搜索)是最简便的图的搜索算法之一,属于一种盲目搜寻法,目的是系统地展开并检查图中的所有节点,以找寻结果。它并不考虑结果的可能位置,彻底地搜索整张图,直到找到结果为止。
    BFS 的核心思想就是把一些问题抽象成图,从一个点开始,向四周开始扩散。一般来说,我们写 BFS 算法都是用「队列」这种数据结构,每次将一个节点周围的所有节点加入队列。
    BFS 出现的常见场景,问题的本质就是让你在一幅「图」中找到从起点start到终点target的最近距离。
    嘛意思呢,假设你进入一个迷宫,里面有各个分叉口,你刚开始从入口进入,当走到分叉口的时候,在求生技能的加持下,你学会了分身术。每当你走到分叉口,你都分身成好几个你分别进入还没走过的通道。各自探寻下一个分叉口。只要其中一个找到了出口,求生技能就会发挥作用将你的所有元神召集在出口处合并,然后成功走出迷宫。走出迷宫的路径(步数)就是找到出口的这个分身所走的路径,也就是最短路径。呃。。。晕了,好吧,慢慢来,先看看一技能(框架)

    // 计算从起点start到终点target的最近距离
        int BFS(Node start, Node target) {
            Queue<Node> q;      // 核心数据结构
            Set<Node> visited;  // 避免走回头路
    
            q.offer(start);     // 将起点加入队列
            visited.add(start);
            int step = 0;       // 记录扩散的步数
    
            while (q not empty) {
                int sz = q.size(); // 挨个取出同一层深度(迷宫分叉口)的所有节点
                // 将当前队列中的所有节点向四周扩散(分身)
                for (int i = 0; i < sz; i++) {
                    Node cur = q.poll(); // poll()从头部删除元素,若头部无元素,返回null
    
                    // !!注意,这里判断是否到达终点
                    if (cur is target)
                        return step;
                    // 将cur的相邻节点加入队列
                    for (Node x : cur.adj()) { // cur.adj()泛指cur相邻的节点,比如在二维数组中,cur上下左右的位置都是相邻节点
                        if (x not in visited) {
                            // 加入队列,以便后续寻找该节点的相邻节点(目标节点有可能就在该节点的相邻节点中哦,不要让他跑掉)
                            q.offer(x);
                            // 做标记,该节点已经遍历过了,防止在后面遍历他的子节点时,子节点把他作为临近节点再次加入队列,那样会进入死循环
                            visited.add(x);
                        }
                    }
                }
                // 划重点,更新步数在这里,即while循环中的最后一步,这样就能保证while再一次执行时q中剩余的是同一层节点
                step++;
            }
        }
    

    就这?就这?对,就这。。emmm~ 牛刀有了,来只鸡 ~

    /*
     * leetcode 第111题:二叉树的最小深度
     * 给定一个二叉树,找出其最小深度
     * 最小深度是从根节点到最近叶子节点的最短路径上的节点数量,叶子节点是没有子节点的节点
     * 示例:给定二叉树[3, 9, 20, null, null, 15, 7]
     *     3
     *    / \
     *   9  20
     *     / \
     *    15  7
     * 返回他的最小深度 2
     * 提示:树中节点数的范围在 [0, 105] 内;-1000 <= Node.val <= 1000
     * */
    
    
    // 首先明确起点start和终点target:起点就是root根节点,终点就是最靠近根节点的那个叶子节点,叶子节点就是两个子节点都是null的节点:
    // if(cur.left == null && cur.right == null)
    // 到达叶子节点
        int minDepth(TreeNode root) {
            if (root == null)
                return 0;
            Queue<TreeNode> q = new LinkedList<>();
            q.offer(root);
            int depth = 1;
            while (!q.isEmpty()) {
                int sz = q.size();
                for (int i = 0; i < sz; i++) {  // size为每一层的节点总数
                    TreeNode cur = q.poll();
                    if (cur.left == null && cur.right == null) {
                        return depth;
                    }
                    // 不需要for了,因为就俩分叉(二叉树)
                    // 当左右子树不等于null时,加入队列,该题不需要标记,因为子节点没有到父节点的指针
                    if (cur.left != null) {
                        q.offer(cur.left);
                    }
                    if (cur.right != null) {
                        q.offer(cur.right);
                    }
                }
                depth++;
            }
            return depth;
        }
    

    BFS的逻辑,depth每增加一次,队列中的所有节点都向前迈一步,这保证了第一次到达终点时,走的步数是最少的即借助队列一次一步齐头并进。

    再试试?
    leetcode:752:打开转盘锁

    你有一个带有四个圆形拨轮的转盘锁。每个拨轮都有10个数字: '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' 。
    每个拨轮可以自由旋转:例如把 '9' 变为 '0','0' 变为 '9' 。每次旋转都只能旋转一个拨轮的一位数字。锁的初始数字为 '0000' ,
    一个代表四个拨轮的数字的字符串。
    列表 deadends 包含了一组死亡数字,一旦拨轮的数字和列表里的任何一个元素相同,这个锁将会被永久锁定,无法再被旋转。
    字符串 target 代表可以解锁的数字,你需要给出最小的旋转次数,如果无论如何不能解锁,返回 -1。
    

    示例 1:

    输入:deadends = ["0201","0101","0102","1212","2002"], target = "0202"
    输出:6
    解释:
      可能的移动序列为 "0000" -> "1000" -> "1100" -> "1200" -> "1201" ->"1202" -> "0202"。 
      注意 "0000" -> "0001" -> "0002" -> "0102" -> "0202" 这样的序列是不能解锁的,因为当拨动到 "0102" 时这个锁就会被锁定。
    

    示例 2:

    输入: deadends = ["8888"], target = "0009"
    输出:1
    解释:
    把最后一位反向旋转一次即可 "0000" -> "0009"。
    

    示例 3:

    输入: deadends = ["8887","8889","8878","8898","8788","8988","7888","9888"], target = "8888"
    输出:-1
    解释:
    无法旋转到目标数字且不被锁定。
    

    先不管所有的限制条件,不管deadends和target的限制,就思考一个问题:如果让你设计一个算法,求所有可能的密码组合,你怎么做?

    穷举呗,再简单一点,如果你只转一下锁,有几种可能?总共有 4 个位置,每个位置可以向上转,也可以向下转,也就是有 8 种可能对吧。

    比如说从"0000"开始,转一次,可以穷举出"1000", “9000”, “0100”, “0900”…共 8 种密码。然后,再以这 8 种密码作为基础,对每个密码再转一下,穷举出所有可能…

    仔细想想,这就可以抽象成一幅图,每个节点有 8 个相邻的节点,又让你求最短距离,这不就是典型的 BFS 嘛,框架就可以派上用场了,先写出一个「简陋」的 BFS 框架代码再说别的:

    // 将s[j]向上拨动一次
    String plusOne(String s, int j) {
        char[] ch =  s.toCharArray();
        if (ch[j] == '9')
            ch[j] = '0';
        else
            ch[j] ++;
        return new String(ch);
    }
    // 将s[j]向下拨动一次
    String minusOne(String s, int j) {
        char[] ch = s.toCharArray();
        if (ch[j] == '0')
            ch[j] = '9';
        else
            ch[j] --;
        return new String(ch);
    }
    // BFS框架,打印出所有可能的密码
    void BFS(String target) {
        Queue<String> q = new LinkedList<>();
        q.offer("0000");
        while (!q.isEmpty()){
            int sz = q.size();
            for (int i = 0; i < sz; i++) {
                String cur = q.poll();
                // 判断是否到达终点
                System.out.println(cur);
    
                // 将一个节点的相邻节点加入队列
                for (int j = 0; j < 4; j++) {
                    String up = plusOne(cur, j);
                    String down = minusOne(cur, j);
                    q.offer(up);
                    q.offer(down);
                }
            }
            //在这里增加步数
        }
    }
    
    /**
     *存在的问题:
     *  1、会走回头路。比如说我们从"0000"拨到"1000",但是等从队列拿出"1000"时,还会拨出一个"0000",这样的话会产生死循环。
     *
     * 2、没有终止条件,按照题目要求,我们找到target就应该结束并返回拨动的次数。
     *
     * 3、没有对deadends的处理,按道理这些「死亡密码」是不能出现的,也就是说你遇到这些密码的时候需要跳过。
     */
    
    //完善:将限制条件都考虑在内
    int openLock(String[] deadends, String target) {
        // 记录需要跳过的死亡密码
        Set<String> deads = new HashSet<>();
        for (String s : deadends) deads.add(s);
        // 记录已经穷举过的密码,防止出现死循环
        Set<String> visited = new HashSet<>();
        Queue<String> q = new LinkedList<>();
        // 从起点开始广度优先搜索
        int step = 0;
        q.offer("0000");
        visited.add("0000");
        while (!q.isEmpty()) {
            int sz = q.size();
            // 将当前队列中的所有节点向四周扩散
            for (int i = 0; i < sz; i++) {
                String cur = q.poll();
                // 判断是否到终点
                if (deads.contains(cur)) continue;
                if (cur.equals(target))
                    return step;
                // 将节点的未遍历相邻节点加入队列
                for (int j = 0; j < 4; j++) {
                    String up = plusOne(cur, j);
                    // 此处为什么判断是否在visited中(已访问)?
                    /**
                     * 对于一个节点来说,他的下一步是以他本身为基准的八种情况,因为是BFS;对于某一层已经遍历过的节点,
                     * 如果在下一层中还遍历到,则不再考虑.
                     * */
    
                    if (visited.contains(up)) {
                        q.offer(up);
                        visited.add(up);
                    }
                    String down = minusOne(cur, j);
                    if (visited.contains(down)) {
                        q.offer(down);
                        visited.add(down);
                    }
                }
            }
            // 在这里增加步数
            step++;
        }
        // 穷举完仍为找到目标密码,返回 -1
        return -1;
    }
    

    注:本文根据《labuladong的算法小抄》整理记录,以便后续复习。
    leetcode 第111题 二叉树的最小深度 :https://leetcode-cn.com/problems/minimum-depth-of-binary-tree/

    leetcode 第752题 打开转盘锁:https://leetcode-cn.com/problems/open-the-lock/

    展开全文
  • BFS算法模板

    2020-08-26 22:12:30
    BFS算法 BFS算法框架 BFS算法是利用队列实现的一种搜索算法。逐层向下遍历,从一个点像四周扩散(将可选节点存放于队列中,删除已被使用的节点),使用队列完成操作,通常用于最短路径的寻找。 单向队列 void BFS...

    BFS算法

    BFS算法框架

    BFS算法是利用队列实现的一种搜索算法。逐层向下遍历,从一个点像四周扩散(将可选节点存放于队列中,删除已被使用的节点),使用队列完成操作,通常用于最短路径的寻找。

    单向队列

    void BFS(Node root,int target)
    {
        // 1、两个队列,分别用于存储起始节点和已访问过的节点
        queue<Node> q; 						
        unordered_set<Node> visited; 		
        // 2、将最初的起始节点放入队列,状态更新    
        q.push(root); 						
        visted(root); 						
        int step; // 记录步数
        
        // 3、遍历队列中的所有可能作为起始的节点
        while (!q.empty()) { 				
            int sz = q.size();
    	    // 4、当前队列中的所有节点为同一扩散层级
            for (int i = 0; i < sz; ++i) { 	
                // 5、每次读取队列顶端节点,进行相应的判断,并从队列中弹出
                Node cur = q.top(); 
                q.pop();
                // 6、判断当前该节点是否为最终目标,满足条件return
                if (cur == target) {
                    return step;
                }
                
                // 7、当前节点满足条件,将其子节点放入队列中存储,在下一次循环(扩散)时进行读取
                for (Node &e : Graph[root]) {
                    // 8、边界判断,其子节点是否越界、已被访问以及其他限制条件
                    if(visited.find(e) != visited.end()) {
                        q.push(e);
                        visited.insert(e);
                    }
                } 
            }
            step++;
        }    
    }
    

    上述为单向BFS算法模板,可以提炼为7个步骤:
    1、定义两个队列q和visited,分别用来存储可作为起始的节点和已被访问过的节点
    2、将最开始的起始节点放入队列q,并在visited中更新其状态
    3、遍历能作为起始节点的队列q(while循环队列)
    4、对当前同一层节点进行遍历(对应for循环)
    5、每次读取队列顶端节点,进行相应的判断,并从队列中弹出
    6、判断当前节点是否为最终目标,满足条件return
    7、当前节点满足条件,但不是最终目标。将其子节点放入队列q,在下一次循环(扩散)时进行读取
    8、在将子节点放入队列是需要判断子节点是否合法,如果合法放入队列,在visited中进行状态更新

    双向队列

    为提高BFS效率,可使用双向BFS算法,从起点和终点同时扩展,当二者存在交际时就停止。

    int openLock(String[] deadends, String target) {
       Set<String> deads = new HashSet<>();
       for (String s : deadends) deads.add(s);
       // 用集合不用队列,可以快速判断元素是否存在
       Set<String> q1 = new HashSet<>();
       Set<String> q2 = new HashSet<>();
       Set<String> visited = new HashSet<>();
    
       int step = 0;
       q1.add("0000");
       q2.add(target);
    
       while (!q1.isEmpty() && !q2.isEmpty()) {
           // 哈希集合在遍历的过程中不能修改,用 temp 存储扩散结果
           Set<String> temp = new HashSet<>();
    
           /* 将 q1 中的所有节点向周围扩散 */
           for (String cur : q1) {
               /* 判断是否到达终点 */
               if (deads.contains(cur))
                   continue;
               if (q2.contains(cur))
                   return step;
               visited.add(cur);
    
               /* 将一个节点的未遍历相邻节点加入集合 */
               for (int j = 0; j < 4; j++) {
                   String up = plusOne(cur, j);
                   if (!visited.contains(up))
                       temp.add(up);
                   String down = minusOne(cur, j);
                   if (!visited.contains(down))
                       temp.add(down);
              }
          }
           /* 在这里增加步数 */
           step++;
           // temp 相当于 q1
           // 这里交换 q1 q2,下一轮 while 就是扩散 q2
           q1 = q2;
           q2 = temp;
      }
       return -1;
    }
    

    习题

    leetcode 127 单词接龙

    在这里插入图片描述
    代码:

    // 单向bfs
    class Solution {
    public:
        int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
            // 1、两个队列,分别用于存储当前的起始节点和已访问的节点
            queue<string> q;
            unordered_set<string> visited;
            
            // 2、将最初的起始节点存入队列,状态更新
            q.push(beginWord);
            visited.insert(beginWord);
            int step = 1; // 记录步数 
            // 3、遍历当前所有可能作为起始的节点
            while (!q.empty()) {
                int size = q.size();
                // 4、当前队列中的所有节点为同一层次
                for (int i = 0; i < size; ++i) {
                    // 5、获取队首节点,并从队列中删除
                    string cur = q.front();
                    q.pop();
                    // 6、判断当前节点是否合乎条件
                    // 此处是将单词的每一位进行转换,判断是否存在于字典中
                    for (int j = 0; j < cur.size(); ++j) {
                        string temp = cur;
                        for (char ch = 'a'; ch <= 'z'; ++ch) {
                        	temp[j] = ch;
                            // 判断转换后的单词是否在字典中
                            if (!wordList.count(temp) || visited.count(temp)) continue; // 当前单词不存在于字典中或者已经被访问,为无效单词
                            // 单词有效,判断是否为目的单词
                            if (temp == endWord) {
                                step++;
                                return step;
                            }
                            //7、 单词有效,非目的单词,放入队列,状态更新,在下一次循环(扩散)时读取
                            q.push(temp);
                            visited.insert(temp);
                    	}
                    }
                }
                step++;
            }
            return 0;
        }
    };
    

    leetcode 139单词拆分

    在这里插入图片描述
    代码:

    方法1:DFS(84ms,击败5%)
    class Solution {
    public:
        bool flag = false;
        int vis[100000];
        unordered_set<string> visited;
        bool wordBreak(string s, vector<string>& wordDict) {
            unordered_set<string> st(wordDict.begin(),wordDict.end());
            for(int i  =0;i <= s.size();i++)
                vis[i] = -1;
            int start = 0;
            helper(s,start,st);
    
            return flag;
        }
        void helper(string s,int start,unordered_set<string> st)
        {
            if (start == s.size()) {
                flag = true;
                return  ;
            }
            if (vis[start] == 0)  return;
            int len = s.length();
            for (int i = start; i < len; ++i) {
                string cur = s.substr(start,i - start + 1);
                if (st.count(cur) != 0) {
                    vis[start] = 1;
                    helper(s,i + 1,st);
                }
            }
            vis[start] = 0;
        }
    };
    方法2:BFS(8ms,击败73%)
    class Solution {
    public:
        bool wordBreak(string s, vector<string>& wordDict) {
            queue<int> q;
            unordered_set<int> visited;
    
            q.push(0);
            visited.insert(0);
    
            while (!q.empty()) {
                int sz = q.size();
                for (int i = 0; i < sz; ++i) {
                    int cur = q.front();
                    q.pop();
                    for (auto word : wordDict) { // 遍历字典,从当前位置cur截取遍历单词的长度,截取出的单词是否相同
                        int len = word.length();
                        if (cur + len > s.length()) // 当前位置截取的单词,超出了原始单词的长度 
                            continue;
                        if (word == s.substr(cur)) // 最后一个位置截取的单词存在于字典中,直接return(s.substr(cur)是截取s在cur以后的字符串)
                            return true;
                        if (visited.count(cur + len))
                            continue;
                        if (s.substr(cur,len) == word) {
                            q.push(cur + len);
                            visited.insert(cur + len);
                        }
                    }
    
                }
            }
            return false;
        }
    };
    方法3:动态规划
    。。。。
    。。。。
    

    练习:

    leetcode:130、317、529、 1263、1197、815、934

    展开全文
  • BFS算法和DFS算法(含图解:简单易懂)

    万次阅读 多人点赞 2020-08-30 13:54:17
    图解BFS算法和DFS算法BFS算法算法思路实现过程Python代码实现DFS算法算法思路实现过程Python代码实现 BFS算法 BFS类似于树的层次遍历过程,从根节点开始,沿着树的宽度遍历树的节点。如果所有节点均被访问,则算法...

    BFS算法

    BFS类似于树的层次遍历过程,从根节点开始,沿着树的宽度遍历树的节点。如果所有节点均被访问,则算法中止。
    舍去空间换时间。

    算法思路

    队列(先进先出)

    1、创建一个空队列queue(用来存放节点)和一个空列表visit(用来存放已访问的节点)

    2、依次将起始点及邻接点加入queue和visit中

    3、poo出队列中最先进入的节点,从图中获取该节点的邻接点

    4、如果邻接点不在visit中,则将该邻接点加入queue和visit中

    5、输出pop出的节点

    6、重复3、4、5,直至队列为空

    实现过程

    如图:从A开始
    在这里插入图片描述

    1、A进入队列
    在这里插入图片描述
    2、A出队列时,A的邻接结点B、C、D进入队列
    在这里插入图片描述

    3、B出队列时,B的邻接结点A、E、F中未进过队列的E、F进入队列
    在这里插入图片描述

    4、C出队列时,C的邻接结点A、D、F、G、中未进过队列的G进入队列
    在这里插入图片描述

    5、D出队列时,D的邻接结点A、C、G已经全部进入过队列
    在这里插入图片描述

    6、E出队列,邻接结点均已进入过队列
    在这里插入图片描述

    7、F出队列,邻接结点均已进入过队列

    在这里插入图片描述

    8、G出队列,邻接结点均已进入过队列

    在这里插入图片描述

    结果 : A B C D E F G

    Python代码实现

    用字典结构表示

    
    graph = {
        'A' : ['B','C','D'],
        'B' : ['A','E','F'],
        'C' : ['A','D','F','G'],
        'D' : ['A','C','G'],
        'E' : ['B'],
        'F' : ['B','C'],
        'G' : ['C','D']
    }
    
    

    BFS

    
    def BFS(start,graph):
        queue =[]
        visit = []
        queue.append(start)
        visit.append(start)
        while queue:
            node = queue.pop(0)
            nodes = graph[node]
            for i in nodes:
                if i not in visit:
                    queue.append(i)
                    visit.append(i)
            print(node,end='\t')
            
    

    运行结果:
    在这里插入图片描述

    DFS算法

    DFS沿着树的深度遍历树的节点,
    选一条路一直走到底,回溯,遍历所有的子节点,进而达到全局搜索的目的。

    算法思路

    栈(先进后出)
    和BFS相似,只是稍微做了一丝改变

    1、创建一个空栈stack(用来存放节点)和一个空列表visit(用来存放已访问的节点)

    2、依次将起始点及邻接点加入stack和visit中

    3、poo出栈中最后进入的节点,从图中获取该节点的邻接点

    4、如果邻接点不在visit中,则将该邻接点加入stack和visit中

    5、输出pop出的节点

    6、重复3、4、5,直至栈为空

    实现过程

    如图:从A开始
    在这里插入图片描述
    1、A进入堆栈
    在这里插入图片描述

    2、A出堆栈时,A的邻接结点B、C、D进入堆栈
    在这里插入图片描述

    3、D出堆栈时,D的邻接结点A、C、G中未进过堆栈的G进入堆栈

    在这里插入图片描述

    4、G出堆栈时,G的邻接结点C、D已经全部进入过堆栈
    在这里插入图片描述

    5、C出堆栈时,C的邻接结点A、D、F、G中未进过堆栈的F进入堆栈
    在这里插入图片描述

    6、F出堆栈,邻接结点均已进入过堆栈
    在这里插入图片描述

    7、B出堆栈,邻接结点E进入堆栈
    在这里插入图片描述
    8、E出堆栈
    在这里插入图片描述
    结果 : A D G C F B E

    Python代码实现

    
    graph = {
        'A' : ['B','C','D'],
        'B' : ['A','E','F'],
        'C' : ['A','D','F','G'],
        'D' : ['A','C','G'],
        'E' : ['B'],
        'F' : ['B','C'],
        'G' : ['C','D']
    }
    def DFS(start,graph):
        stack = []
        visit = []
        stack.append(start)
        visit.append(start)
        while stack:
            node = stack.pop()
            nodes = graph[node]
            for i in nodes:
                if i not in visit:
                    stack.append(i)
                    visit.append(i)
            print(node,end='\t')
    
    DFS('A',graph)
    
    

    运行结果:
    在这里插入图片描述

    展开全文
  • 我需要将其转换为BFS算法,以找到两个顶点之间的最短路径。好吧…我该怎么做?BFS算法与上面的算法有点相似吗?还是我需要从头开始编写它? l-邻接表dfst-在最后保留生成树的数组x-起始顶点y-帮助程序变量
  • DFS(深度优先遍历)与BFS(广度优先遍历)算法是基于树和图结构进行遍历的两种算法。 一般来说DFS在前中后遍历中运用比较明显,DFS的运用基本是要利用...所以为了更深入的理解DFS和BFS算法的精髓,这里针对这些数据
  • C语言BFS算法的实现(附完整源码)

    千次阅读 2021-03-29 09:13:06
    C语言BFS算法的实现C语言BFS算法的实现完整源码(定义,实现,main函数测试) C语言BFS算法的实现完整源码(定义,实现,main函数测试) #include <iostream> #include <vector> #include <queue> ...
  • BFS算法框架详解

    2021-03-22 19:38:45
    BFS算法框架详解 DFS就是回溯算法,BFS的核心思想就是把一些问题抽象成图,从一个点开始,向四周开始扩散。 BFS相对DFS最主要的区别是:BFS找到的路径一定是最短的,但代价就是空间复杂度比DFS大很多。 本文主要讲解...
  • 图的BFS算法

    2020-06-27 15:38:52
    BFS:Breadth-First-Search 介绍 BFS:广度优先搜索 类似于树的按层次遍历过程 思想 访问起始点v 之后,依次访问v的各个未曾访问过的邻接点 再分别从这些邻接点出发,访问他们的邻接点;并使“先被访问的顶点的...
  • 图文详解 DFS 算法BFS 算法

    千次阅读 2020-04-19 12:15:00
    点击关注上方“五分钟学算法”,设为“置顶或星标”,第一时间送达干货。转自码海前言 深度优先遍历(Depth First Search, 简称 DFS) 与广度优先遍历(Breath F...
  • Javascript实现BFS算法

    千次阅读 2019-10-02 22:19:44
    在实现BFS算法之前,先建立邻接矩阵和邻接表: 邻接矩阵 每个节点都和一个整数相关联,该整数将作为数组的索引。用一个二维数组来表示顶点之间的连接。比如上面图片中的图的邻接矩阵表示: 由于该图不是强连通...
  • 定义采用邻接矩阵存储的图结构封装DFS、BFS算法
  • //要打印的方向 void BFS() { queue<point>q; q.push({0,0,""}); vis[0][0]=1; while(!q.empty())//队列不为空的情况下 { point now=q.front();//第一个元素出队 q.pop();//删除第一个元素释放空间 if(now.x==m-1&&...
  • 广度优先法(BFS)算法C/C++代码,要说明和图解!谢谢!#include #define MAX 10 int front=-1,rear=-1; struct node { int value; struct. return queue[front]; } void bfs(int current) //广度优先 { link tempnode...
  • 图的BFS算法和DFS算法

    2020-09-05 15:47:08
    BFS(breadth first search)广度优先搜索,和二叉树广度优先搜索算法一样,一层一层的搜索,只不过得先找个起始点,如下图所示,比如起始点设为A,那就是A,B,C,D,E,F DFS,也是先找个起始点,然后一条路走到底,走到...
  • DFS和BFS算法

    千次阅读 2018-01-05 01:10:19
    DFS和BFS异同比较 本质区别 BFS 的重点在于队列,而 DFS 的重点在于递归。这是它们的本质区别。...BFS算法 是一种利用队列(用队列来保存未访问的结点,先进先出)实现的搜索算法。简单来说...
  • BFS算法介绍

    2021-06-26 12:21:07
    这篇博文我介绍宽度优先搜素(Breadth First Search, 简称BFS算法。首先我们先回到三个问题,为什么要提出这种算法(Why)、BFS是什么(What)、BFS怎么做(How)。 为什么要提出BFS BFS由Moore提出,是为了解决从...
  • 数据结构笔记——最短路径BFS算法

    千次阅读 2020-06-16 19:03:35
    其中BFS算法(无权图)、Dijkstra算法(带权图,无权图) 各顶点间的最短路径——Floyd算法(带权图、无权图) 二、BFS求无权图的单源最短路径 注:无权图可以视为一种特殊的带权图,只是每条边的权值都为1 ...
  • BFS算法详解

    千次阅读 2017-04-06 11:24:11
    一、概述 作为搜索算法的一种,DFS对于寻找一个解的NP(包括NPC)问题作用很大。但是,搜索算法毕竟是时间复杂度是O(n!)的阶乘级算法,它的效率非常低,在数据规模变大时,这种算法就显得力不从心了。当节点v的所有...
  • BFS算法求解单源最短路径问题: 1、 最短路径的定义
  • Bfs算法

    2019-03-28 14:26:21
    Bfs算法 https://www.2cto.com/kf/201605/509249.html
  • 算法之BFS算法框架

    2021-01-03 04:33:52
    BFS算法框架 前言 BFS(广度优先级搜索)和DFS(深度优先级搜索)是比较常用的算法,其中DFS算是一种回溯算法,在二叉树中就相当于前序遍历算法。这里先分析BFS算法,为什么呢?因为BFS比较简单!废话少说,直接上...
  • 数据结构基础27:DFS和BFS算法总结

    千次阅读 2019-02-16 05:06:04
    前言:图的遍历算法DFS和BFS是许多图算法的基础,所以有必要单独拎出来总结一下。DFS和BFS主要是运用于对于图和树的搜索,很多问题模型都是可以建模变成一个图或者树的,所以差不多不少问题都会涉及到这两个。比如求...
  • 算法进阶之BFS 算法

    2020-06-05 15:39:05
    BFS 算法 BFS 的核心思想应该不难理解的,就是把一些问题抽象成图,从一个点开始,向四周开始扩散。一般来说,我们写 BFS 算法都是用「队列」这种数据结构,每次将一个节点周围的所有节点加入队列。 BFS 相对 DFS 的...
  • BFS算法解析

    千次阅读 2017-12-21 18:49:18
    深度优先搜索得到的路径不仅取决于图的...解决这个问题的经典方法叫做广度优先搜索(BFS)。 要找到从s到v的最短路径,从s开始,在所有由一条边就可以到达的顶点中寻找v,如果找不到我们就继续在与s距离两条边的所有顶
  • BFS算法(详细C)

    万次阅读 2017-02-24 09:43:35
    void bfs(Gragh g,int x,Queue Q) { int i,temp; visited[x]=1; Insertqueue(Q,x); while(Q->front!=Q->rear) { temp=Deletequeue(Q); printf("正在遍历%d个顶点\n",temp); for(i=0;i<g->Nv;i++) { ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 68,768
精华内容 27,507
关键字:

bfs算法

友情链接: 完成结果.zip