bfs-最小步数问题

最小步长模型

  • 特征:

    主要是解决权值为1且状态为字符串类型的最短路问题,实质上是有向图的最短路问题,可以简化为bfs求最短路问题。

  • 代表题目:

    • acwing 845 八数码问题:

      八数码题中由于每次交换的状态是由x进行上下左右移动,采用直接对x的位置进行四个方向枚举,每次求出来x的位置,需要将原字符串的位置转换为 3 * 3 矩阵中的坐标,在四个方向上进行交换x的坐标,每次记录当前状态到起点状态的步数 + 1即可,细节见代码,本题最重要的是学会字符串与二维矩阵之间的坐标转换。

      java 复制代码
      import java.util.*;
      
      public class Main {
          // 定义四个移动方向:上、下、左、右
          static int[][] dirs = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
      
          // 获取字符串中 'x' 的索引
          public static int get(String t) {
              for (int i = 0; i < t.length(); i++) {
                  if (t.charAt(i) == 'x') {
                      return i;
                  }
              }
              return -1; // 理论上不会到达
          }
      
          // 交换字符串 t 中索引 k 和 j 的字符,返回新字符串
          public static String swap(String t, int k, int j) {
              char[] arr = t.toCharArray();
              char temp = arr[k];
              arr[k] = arr[j];
              arr[j] = temp;
              return new String(arr);
          }
      
          // BFS 求解从 start 到 end 的最小移动次数
          public static int bfs(String start, String end) {
              // 距离映射:记录每个状态的步数
              Map<String, Integer> dist = new HashMap<>();
              // 队列:存储待探索的状态
              Queue<String> q = new LinkedList<>();
              q.offer(start);
              dist.put(start, 0);
      
              while (!q.isEmpty()) {
                  String t = q.poll();
                  // 如果当前状态是目标状态,返回步数
                  if (t.equals(end)) {
                      return dist.get(t);
                  }
      
                  // 找到 'x' 的索引
                  int k = get(t);
                  // 计算 'x' 在 3x3 网格中的坐标
                  int x = k / 3, y = k % 3;
      
                  // 尝试四个方向移动
                  for (int[] dir : dirs) {
                      int nx = x + dir[0];
                      int ny = y + dir[1];
                      // 检查新坐标是否越界
                      if (nx < 0 || nx >= 3 || ny < 0 || ny >= 3) {
                          continue;
                      }
      
                      // 计算新位置的索引
                      int newIdx = nx * 3 + ny;
                      // 生成新状态
                      String next = swap(t, k, newIdx);
                      // 如果新状态未访问过,加入队列
                      if (!dist.containsKey(next)) {
                          dist.put(next, dist.get(t) + 1);
                          q.offer(next);
                      }
                  }
              }
              // 无法到达目标状态
              return -1;
          }
      
          public static void main(String[] args) {
              Scanner sc = new Scanner(System.in);
              String start = "";
              for(int i = 0; i < 9; i++) 
                  start += sc.next();
              
      
              String end = "12345678x";
              int res = bfs(start, end);
              System.out.println(res);
          }
      }

  • 题目2:acwing 1107 魔版 温馨提示代码又臭又长 hhh

思路很简单,初始状态和结束状态都已知,要求解最小步数和字典序最小的操作序列,也就是在上个题目的基础上,在bfs的时候要记录当前层的状态是由上一层的哪个状态,通过哪种方式进行转换得到的,所以需要记录路径,根据分析可知,需要用一个哈希表存储,key为当前状态,value需要涵盖上一个状态(String)和操作(char) ,所以这里的map类型为<String, PII>,具体见代码。记录完毕这个路径以后,要获得正序的操作数,需要遍历存储的pre,然后再倒序输出即可,思路不难,代码很长。。

java 复制代码
import java.util.*;

public class Main {
    static Map<String, Integer> dist = new HashMap<>(); // 记录当前状态距离初始状态的步数
    static Map<String,PII> pre = new HashMap<>(); // 存储当前状态由那个状态的哪个操作进行转移过来
    static char[][] g = new char[2][4];
    
    // 将字符串转换为二维矩阵存储
    public static void set(String t) {
        for(int i = 0; i < 4; i++) g[0][i] = t.charAt(i);
        for(int i = 0; i < 4; i++) g[1][i] = t.charAt(7 - i);
    }
    
    // 将二维矩阵转换为字符串
    public static String get() {
        String res = "";
        for(int i = 0; i < 4; i++) res += g[0][i];
        for(int i = 0; i < 4; i++) res += g[1][3 - i];
        
        return res;
    }
    
    // 操作A 交换上下两行
    public static String moveA(String t) {
        set(t);
        
        char[] temp = new char[4];
        for(int i = 0; i < 4; i++) {
            temp[i] = g[0][i];
            g[0][i] = g[1][i];
            g[1][i] = temp[i];
        }
        return get();
    }
    
    // 操作B 将最右边的一列插入到最左边;
    
    public static String moveB(String t) {
        set(t);
        char a = g[0][3];
        char b = g[1][3];
        
        for(int i = 3; i >= 1; i--) {
            g[0][i] = g[0][i - 1];
            g[1][i] = g[1][i - 1];
        }
        g[0][0] = a;
        g[1][0] = b;
        
        return get();
    }
    
    // 操作C 魔板中央对的4个数作顺时针旋转。
    
    public static String moveC(String t) {
        set(t);
        char a = g[0][1];
        char b = g[0][2];
        char c = g[1][1];
        char d = g[1][2];
        
        g[0][1] = c;
        g[0][2] = a;
        g[1][1] = d;
        g[1][2] = b;
       
       return get();
    }
    
    public static int bfs(String start, String end) {
        if(start.equals(end)) return 0;
        
        Queue<String> q = new LinkedList<>();
        
        q.offer(start);
        dist.put(start, 0);
        
        //  bfs遍历框架
        while(!q.isEmpty()){
            String t = q.poll();
            
            // 三种操作
            String[] m = new String[3];
            m[0] = moveA(t);
            m[1] = moveB(t);
            m[2] = moveC(t);
            
            
            for(int i = 0; i < 3; i++) {
                if(dist.containsKey(m[i])) continue; // 当前的状态被遍历过了 
                dist.put(m[i], dist.get(t) + 1);
                
                // 存储下一个状态由哪个状态+操作进行转移
                if(pre.containsKey(m[i])) continue;
                pre.put(m[i], new PII((char) ('A' + i), t));
                
                
                q.offer(m[i]);
                
                if(m[i].equals(end)) return dist.get(end);
            }
        }
        return -1;
    }
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        String start = "12345678";
        String end = "";
        for(int i = 0; i < 8; i++) {
            end += sc.nextInt();
        }
        int step = bfs(start, end);
        System.out.println(step);
        
        // 遍历统计途中的最优解方案
        String res = "";
        String ans = end;
        
        while(!ans.equals(start)) {
            res += pre.get(ans).opera;
            ans = pre.get(ans).str;
        }
        
        for(int i = res.length() - 1; i >= 0; i--) System.out.print(res.charAt(i));
        
        
    }
}

class PII {
    char opera; //  操作 
    String str; // 状态
    public PII(char opera, String str) {
        this.opera = opera;
        this.str = str;
    }
}

  • 题目3: leetcode 752 打开转盘锁

本题的思路跟上面两题类似,不同点在于多了一个限制条件: deadends,也就是说在bfs的时候如果新得到的状态存在与deadends,则不会继续更新,也就是不会将当前的状态与初始状态连接;此外,每次操作相当于是对四位字符中的一位进行+1/-1操作,因为转盘是圆的,这里需要 + 10 % 10 的操作。

java 复制代码
class Solution {
    static Set<String> lock = new HashSet<>();
    public static int bfs(String start, String end) {
        Queue<String> q = new LinkedList<>();
        Map<String, Integer> dist = new HashMap<>();
        q.offer(start);
        dist.put(start, 0);

        while(!q.isEmpty()) {
            String t = q.poll();
            if(t.equals(end)) return dist.get(t);
            StringBuilder sb = new StringBuilder();
            for(int i = 0; i < 4; i++) {
               String s1 = t.substring(0, i);
               String s2 = t.substring(i + 1, 4);
                for(int j = -1; j <= 1; j += 2) {
                   int c = (t.charAt(i) - '0' + j + 10) % 10 ;
                  

                   String newStr = s1 + c + s2;

                   if(lock.contains(newStr)) continue;
                   if(dist.containsKey(newStr)) continue;

                   dist.put(newStr, dist.get(t) + 1);
                   q.offer(newStr);

                }
            }

            
        }
        return -1;

    }
    public int openLock(String[] deadends, String target) {

        lock.clear();
        String start = "0000";
        // 特判起点等于终点
        if(start.equals(target)) return 0;

        // 特判起点为不可走
        for(int i = 0; i < deadends.length; i++) {
            String t = deadends[i];
            lock.add(t);
        }
        if(lock.contains(start)) return -1;

        int ans = bfs(start, target);

        return ans;
    }
}
相关推荐
一只鱼^_3 分钟前
牛客练习赛138(首篇万字题解???)
数据结构·c++·算法·贪心算法·动态规划·广度优先·图搜索算法
一只码代码的章鱼11 分钟前
Spring的 @Validate注解详细分析
前端·spring boot·算法
lyw20561913 分钟前
RabbitMQ,Kafka八股(自用笔记)
java
邹诗钰-电子信息工程14 分钟前
嵌入式自学第二十一天(5.14)
java·开发语言·算法
有梦想的攻城狮23 分钟前
spring中的@MapperScan注解详解
java·后端·spring·mapperscan
寒小松29 分钟前
Problem E: List练习
java·数据结构·list
zimoyin32 分钟前
Kotlin 协程实战:实现异步值加载委托,对值进行异步懒初始化
java·前端·kotlin
↣life♚1 小时前
从SAM看交互式分割与可提示分割的区别与联系:Interactive Segmentation & Promptable Segmentation
人工智能·深度学习·算法·sam·分割·交互式分割
zqh176736464691 小时前
2025年阿里云ACP人工智能高级工程师认证模拟试题(附答案解析)
人工智能·算法·阿里云·人工智能工程师·阿里云acp·阿里云认证·acp人工智能
柚个朵朵1 小时前
Spring的Validation,这是一套基于注解的权限校验框架
java·后端·spring