You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用A*算法求解N数码(八数码)问题的代码实现故障排查

嘿,我之前折腾N数码的A*实现时踩过超多坑,太懂你这种卡壳的感觉了!结合我的经验,给你梳理下核心要点和常见问题的解决办法吧~

A* 解N数码问题的核心逻辑

首先得把A*的几个核心组件捋清楚,这是避免出错的基础:

  • 状态表示:用元组存棋盘状态(比列表好,因为不可变,能直接当字典的键)
  • 启发函数:最靠谱的是曼哈顿距离——每个非0数字到它目标位置的曼哈顿距离之和,这是可采纳的启发函数,能保证找到最优解
  • 代价函数:f(n) = g(n) + h(n),其中g(n)是从初始状态到当前状态的步数,h(n)就是启发函数值
  • 优先队列:用最小堆来存储待探索的状态,每次取出f(n)最小的状态优先处理
  • 已访问集合:记录已经处理过的状态,还要存到达该状态的最小g值,避免重复走冤枉路
高频坑点排查与修复

大概率你的问题出在这几个地方,逐个检查:

  • 启发函数计算错误
    很多人会搞反目标位置的计算!比如3×3谜题里,数字1的目标位置是(0,0),所以对于数字num(非0),目标行应该是(num-1) // N,目标列是(num-1) % N。要是写成num // N,那数字1的目标位置就错成(0,1)了,直接导致启发函数失效。
  • 相邻状态生成出错
    找到空白块的位置后,要检查上下左右四个方向是否在棋盘范围内(比如x不能小于0,也不能大于N-1)。生成新状态时,要交换空白块和相邻方块的位置,别搞反顺序。另外,题目要求记录的是被移动的方块的坐标,不是空白块的坐标!比如空白块在(2,2),往上移的话,移动的是(1,2)的方块,所以步骤要存[1,2]。
  • 优先队列元素结构不对
    堆里的元素必须包含(f值, g值, 当前状态, 已走路径),不然没法回溯步骤,也没法判断是否找到更优路径。只存f值和状态的话,到最后根本拿不到移动步骤。
  • 已访问集合处理不当
    不要只存状态,要存到达该状态的最小g值。如果遇到同一个状态,但当前的g值比已记录的小,说明找到了更优路径,得重新把这个状态加入堆里(旧的高g值状态后续会被自动忽略)。
  • 目标状态定义不一致
    确认你的目标状态是[1,2,...,N²-1, 0]还是[0,1,2,...,N²-1]?如果是后者,启发函数里的目标位置计算要调整,比如数字num的目标行是num // N,目标列是num % N。
关键代码片段参考

给你贴几个核心部分的代码,对照着检查你的实现:

曼哈顿距离计算

def manhattan_distance(state, N):
    distance = 0
    for idx, num in enumerate(state):
        if num == 0:
            continue
        # 计算目标位置(假设目标状态是1,2,...N²-1,0)
        target_x = (num - 1) // N
        target_y = (num - 1) % N
        # 计算当前位置
        current_x = idx // N
        current_y = idx % N
        distance += abs(current_x - target_x) + abs(current_y - target_y)
    return distance

生成相邻状态与移动步骤

def get_neighbors(state, N):
    neighbors = []
    # 找到空白块的索引
    blank_idx = state.index(0)
    blank_x, blank_y = blank_idx // N, blank_idx % N
    # 四个移动方向:上、下、左、右
    directions = [(-1, 0), (1, 0), (0, -1), (0, 1)]
    for dx, dy in directions:
        new_x, new_y = blank_x + dx, blank_y + dy
        # 检查是否在棋盘范围内
        if 0 <= new_x < N and 0 <= new_y < N:
            neighbor_idx = new_x * N + new_y
            # 生成新状态
            new_state = list(state)
            new_state[blank_idx], new_state[neighbor_idx] = new_state[neighbor_idx], new_state[blank_idx]
            # 记录移动的方块坐标(就是新的x,y,因为这个方块移到空白位置)
            neighbors.append((tuple(new_state), [new_x, new_y]))
    return neighbors

A*主循环

import heapq

def solve_n_puzzle(initial_state, N):
    # 定义目标状态
    target_state = tuple([i+1 for i in range(N*N - 1)] + [0])
    # 初始状态已经是解,直接返回空列表
    if tuple(initial_state) == target_state:
        return []
    
    # 初始化堆:(f值, g值, 当前状态, 移动路径)
    initial_g = 0
    initial_h = manhattan_distance(initial_state, N)
    heap = [(initial_g + initial_h, initial_g, tuple(initial_state), [])]
    heapq.heapify(heap)
    
    # 已访问字典:键是状态,值是到达该状态的最小g值
    visited = {}
    
    while heap:
        f, g, current_state, path = heapq.heappop(heap)
        
        # 如果当前状态已经有更优路径,跳过
        if current_state in visited and visited[current_state] < g:
            continue
        visited[current_state] = g
        
        # 遍历所有相邻状态
        for neighbor_state, move in get_neighbors(current_state, N):
            if neighbor_state == target_state:
                # 找到解,返回路径+当前移动
                return path + [move]
            new_g = g + 1
            new_h = manhattan_distance(neighbor_state, N)
            new_f = new_g + new_h
            
            # 如果邻居状态未访问,或者有更优路径,加入堆
            if neighbor_state not in visited or new_g < visited.get(neighbor_state, float('inf')):
                heapq.heappush(heap, (new_f, new_g, neighbor_state, path + [move]))
    
    # 题目说测试用例可解,所以这里理论上不会执行到
    return None

按照上面的要点排查调整,应该能解决你的A*实现问题啦!

内容的提问来源于stack exchange,提问作者Anonymous

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.26 09:42:16