LintCode 794 Sliding Puzzle II

本文介绍了一种使用广度优先搜索(BFS)解决滑动拼图游戏的算法。通过将游戏状态转化为字符串,利用队列进行层序搜索,有效避免重复状态,实现从初始状态到目标状态的最少移动步数计算。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

思路

【注意bfs中,在将一个元素加入queue中后就要标记为visited】
隐式图bfs,level-order bfs,记录步数。定义每一个节点为每一个状态(9个位置的数字),边定义为两个状态之间通过移动0一次可以得到,则二者之间有一条边。主要过程就是bfs进行层序搜索。
其中一个技巧是将二维数组转化为字符串来保存,这样方便队列操作以及查重等操作。
实现了两个函数:(1)matrixToString:将二维数组转成String;
(2)getNextState:得到下一个状态表示的字符串。在该函数中,涉及到二维数组和一维数组的下标转换问题:
(1)1D=>2D: 一维数组的下标/第一维的大小=二维数组的x;一维数组的下标%第一维的大小=二维数组的y。
(2)2D=>1D: x* 第二维大小+y=一维数组的下标
流程:首先找到‘0’的坐标,然后上下左右4个方向进行探索,用‘0’去交换。每次得到一个状态字符串,加入到结果list中,最后返回一个结果list。

代码

public class Solution {
    /**
     * @param init_state: the initial state of chessboard
     * @param final_state: the final state of chessboard
     * @return: return an integer, denote the number of minimum moving
     */
    public int minMoveStep(int[][] init_state, int[][] final_state) {
        // # write your code here
        String source = matrixToString(init_state);
        String target = matrixToString(final_state);
        
        Queue<String> queue = new LinkedList<>();
        Set<String> visited = new HashSet<>();
        
        queue.offer(source);
        visited.add(source);
        
        int step = 0;
        while(!queue.isEmpty()) {
            int size = queue.size();
            for(int i = 0; i < size; i++) {
                String cur = queue.poll();
                if(cur.equals(target)) return step;
                
                for(String next : getNextState(cur)) {
                    if(!visited.contains(next)) {
                        queue.offer(next);
                        visited.add(next);
                    }
                }
            }
            step++;
        }
        
        return -1;
    }
    
    public String matrixToString(int[][] matrix) {
        StringBuilder sb = new StringBuilder();
        for(int i = 0; i < 3; i++) {
            for(int j = 0; j < 3; j++) {
                sb.append(matrix[i][j]);
            }
        }
        return sb.toString();
    }
    
    public List<String> getNextState(String curState) {
        List<String> res = new ArrayList<>();
        
        int zeroIndex = curState.indexOf('0');
        int zx = zeroIndex / 3;
        int zy = zeroIndex % 3;
        
        int[] dx = {0, 0, 1, -1};
        int[] dy = {1, -1, 0, 0};
        
        for(int i = 0; i < 4; i++) {
            int nx = zx + dx[i];
            int ny = zy + dy[i];
            if(0 <= nx && nx < 3 && 0 <= ny && ny < 3) {
                char[] sc = curState.toCharArray();
                // swap
                sc[zx * 3 + zy] = sc[nx * 3 + ny];
                sc[nx * 3 + ny] = '0';
                
                res.add(new String(sc));
            }
        }
        
        return res;
    }
}

复杂度

时间复杂度O(n),n为节点数,即状态数量,为9!
空间复杂度O(n+m), m为边数,为C(n, 2).
二者都可视为常数级[?]

### A* 算法在滑动拼图问题中的应用 A* 算法是一种高效的路径寻找算法,它通过结合实际代价 \( g(n) \) 和启发式估计 \( h(n) \),能够快速找到最优解。对于滑动拼图问题(Sliding Puzzle),可以将其视为一种状态空间搜索问题,其中每个拼图布局是一个状态。 #### 1. 定义状态表示 在滑动拼图中,每种可能的棋盘配置都可以看作一个节点。通常可以用二维数组来存储当前的状态。例如,在一个 3×3 的拼图中: 初始状态: ```plaintext 1 2 3 4 0 5 7 8 6 ``` 目标状态: ```plaintext 1 2 3 4 5 6 7 8 0 ``` 这里,“0” 表示空白格子的位置。 #### 2. 启发函数的选择 为了使 A* 更加高效,需要设计合适的启发函数 \( f(n) = g(n) + h(n) \)[^2]。以下是两种常见的启发式方法用于滑动拼图问题: - **曼哈顿距离 (Manhattan Distance)**:计算每个方块到其目标位置之间的水平和垂直移动总步数之和。 - 对于某个方块位于坐标 `(i, j)` 而它的目标位置是 `(x, y)`,则该方块的曼哈顿距离为 `|i-x| + |j-y|`。 - **错位数量 (Misplaced Tiles)**:统计不在正确位置上的方块数目。 - 如果某一块不处于最终的目标位置,则计数值增加 1。 这两种方式各有优劣;一般情况下,曼哈顿距离会提供更好的性能因为它是更精确的距离度量[^3]。 #### 3. 实现伪代码 下面给出基于 Python 的简单实现框架: ```python from heapq import heappush, heappop def a_star(start_state, goal_state): open_list = [] # 使用优先队列维护待探索集合 closed_set = set() # 已经访问过的结点 start_node = Node(state=start_state, parent=None, action=None) heuristic_cost = calculate_heuristic(start_state, goal_state) heappush(open_list, (heuristic_cost, start_node)) while open_list: _, current_node = heappop(open_list) if tuple(map(tuple, current_node.state)) in closed_set: continue if is_goal(current_node.state, goal_state): # 判断是否达到目标态 return reconstruct_path(current_node), len(closed_set)+len(open_list) closed_set.add(tuple(map(tuple, current_node.state))) neighbors = get_neighbors(current_node.state) for neighbor in neighbors: movement_cost_to_neighbor = current_node.g_cost + 1 # 假设每次移动成本固定为1 new_g_cost = movement_cost_to_neighbor estimated_remaining_distance = calculate_heuristic(neighbor, goal_state) total_estimated_cost = new_g_cost + estimated_remaining_distance successor_node = Node( state=neighbor, parent=current_node, action="move", # 可替换具体动作描述 g_cost=new_g_cost ) heappush(open_list, (total_estimated_cost, successor_node)) return None class Node(): def __init__(self, state, parent, action, g_cost=0): self.state = state # 当前状态 self.parent = parent # 上一状态指针 self.action = action # 执行的动作 self.g_cost = g_cost # 移动到这里所花费的实际费用 def calculate_heuristic(state, goal_state): """ 计算曼哈顿距离 """ distance_sum = 0 size = len(goal_state) for i in range(size): for j in range(size): value = state[i][j] if value != 0: # 不考虑空格'0' target_position = divmod(value-1, size) distance_sum += abs(i-target_position[0]) + abs(j-target_position[1]) return distance_sum def is_goal(state, goal_state): return all([state[row][col]==goal_state[row][col] for row in range(len(state)) for col in range(len(state))]) def get_neighbors(state): """ 获取合法邻居状态列表 """ zero_row, zero_col = next((r,c) for r,row in enumerate(state) for c,val in enumerate(row) if val==0) directions = [(0,-1),(0,+1),(-1,0),(+1,0)] # 四个方向尝试交换零所在位置与其相邻单元 result_states = [] for dr, dc in directions: nr, nc = zero_row + dr, zero_col + dc if 0<=nr<len(state) and 0<=nc<len(state[0]): swapped_copy = [list(subrow)[:] for subrow in state] swapped_copy[nr][nc],swapped_copy[zero_row][zero_col]=swapped_copy[zero_row][zero_col],swapped_copy[nr][nc] result_states.append(swapped_copy) return result_states def reconstruct_path(node): actions_sequence = [] while node.parent is not None: actions_sequence.insert(0,node.action) node=node.parent return actions_sequence ``` 上述程序定义了一个基本版本的 A* 搜索过程并应用于解决滑动谜题问题上。注意这只是一个简化版模型,实际开发过程中还需要处理更多边界条件以及优化内存管理等问题。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值