Azide

文章 分类
11 2

站点介绍

摸鱼!都可以摸

使用A*算法解决八数码问题[Python]

Azote 2022-12-05 840 0条评论 文章 八数码问题A*AstarPython

首页 / 正文

参考文章:八数码问题,A*算法,启发函数
Python >= 3.10

八数码问题

八数码问题是在3×3的九宫格棋盘上,摆有8个刻有1~8数码的将牌。棋盘中有一个空格,允许紧邻空格的某一将牌可以移到空格中,这样通过平移将牌可以将某一将牌布局变换为另一布局。针对给定的一种初始布局或结构(目标状态),问如何移动将牌,实现从初始状态到目标状态的转变。

A*算法

A*算法是一种启发式图搜索算法,其特点在于对估价函数的定义上。对于一般的启发式图搜索,总是选择估价函数f值最小的节点作为扩展节点。因此,f是根据需要找到一条最小代价路径的观点来估算节点的,所以,可考虑每个节点n的估价函数值为两个分量:从起始节点到节点n的实际代价g(n)以及从节点n到达目标节点的估价代价h(n),且$h(n) \leq h^*(n)$,$h^*(n)$为n节点到目的节点的最佳路径的代价。

代码

所使用的启发函数

def hn(self, a_chess_board:ChessBoard) -> int:
        self.f = self.h = 0
        # h(n)为不在目标状态的数码移到目的位置所需移动的曼哈顿距离的总和
        # D = |X1 - X2| + |Y1 - Y2|
        # X = i // 3
        # Y = i mod 3
        for i in range(0, 9):
            diff_chess_pos = abs(a_chess_board.chess_map.index(i) - self.chess_map.index(i))
#list.index(i):找到数字i在list中的索引,即在当前棋盘中的位置
            self.h += diff_chess_pos // 3 + diff_chess_pos % 3

        self.f = self.g + self.h
        return self.f

所需库

from __future__ import annotations 
# 为的是在类中使用自己的类型注解,在Python3.11中可以直接使用Self
# https://docs.python.org/3/whatsnew/3.11.html#whatsnew311-pep673
from queue import PriorityQueue
from copy import deepcopy
from typing import Literal

定义一个类来记录当前状态(棋盘)

#给该类型定义一个别名方便理解
ChessMap = list[int]

class ChessBoard:

    def __init__(self, chess_map:list[int] = []):
        self.f:int = 0
        self.h:int = 0
        self.g:int = 0

        self.chess_map:ChessMap = chess_map
        self.solution_path:list[ChessMap] = list()

    def __gt__(self, another:ChessBoard):
        # 作为PriorityQueue的排序依据
        return self.f > another.f

    def __eq__(self, another:ChessBoard):
        # 比较chessmap是否相同
        return self.chess_map == another.chess_map

    def __str__(self):
        return "".join(str(i) for i in self.chess_map)

    def get_space_index(self) -> int:
        return self.chess_map.index(0)

    def index2xy(self, index:int) -> tuple[int,int]:
        return (index // 3 ,index % 3)

    def hn(self, a_chess_board:ChessBoard) -> int:
        self.f = self.h = 0
        # h(n)为不在目标状态的数码移到目的位置所需移动的曼哈顿距离的总和
        # D = |X1 - X2| + |Y1 - Y2|
        # X = i // 3
        # Y = i mod 3
        for i in range(0, 9):
            diff_chess_pos = abs(a_chess_board.chess_map.index(i) - self.chess_map.index(i))
#list.index(i):找到数字i在list中的索引,即在当前棋盘中的位置
            self.h += diff_chess_pos // 3 + diff_chess_pos % 3

        self.f = self.g + self.h
        return self.f

定义一个可以操作棋盘的类(代理人)

class Oper:

    moves:dict[str, tuple] = {
        "up": (-1, 0),
        "down": (1, 0),
        "left": (0, -1),
        "right": (0, 1)
    }

    def __init__(self, source:ChessBoard, target:ChessBoard):
        self.source:ChessBoard = source
        self.target:ChessBoard = target
        self.close_list:list[ChessBoard] = list()
        self.open_list:PriorityQueue[ChessBoard] = PriorityQueue()
        self.search_chess_board:ChessBoard = ChessBoard()

    def possible_move(self, chess_board:ChessBoard) ->list[ChessBoard | Literal["overflow"]]:
        space_index = chess_board.get_space_index()
        space_x, space_y = chess_board.index2xy(space_index)
        possible_chess_map:list[ChessBoard | Literal["overflow"]] = []
        for direct in ["up", "down", "left", "right"]:
            new_x = space_x + self.moves[direct][0]
            new_y = space_y + self.moves[direct][1]
            if 0 <= new_x <= 2 and 0 <= new_y <= 2:
                new_chess_board = ChessBoard()
                new_chess_board.chess_map = deepcopy(chess_board.chess_map)
                new_chess_board.solution_path = deepcopy(chess_board.solution_path)
                new_chess_board.chess_map[space_index] = new_chess_board.chess_map[new_x * 3 + new_y]
                new_chess_board.chess_map[new_x * 3 + new_y] = 0
                new_chess_board.g = chess_board.g + 1
                new_chess_board.hn(self.target)
                new_chess_board.solution_path.append(new_chess_board.chess_map)
                possible_chess_map.append(new_chess_board)
            else:
                possible_chess_map.append("overflow")

        return possible_chess_map

    def calculate(self):
        self.source.hn(self.target)
        self.close_list.append(self.source)
        self.open_list.put(self.source)

        while not self.open_list.empty():
            chess_board = self.open_list.get()
            #print(chess_board, " size:%d"%self.open_list.qsize())
            if chess_board.h == 0:
                self.search_chess_board = chess_board
                break
            possible_chess_board_list = self.possible_move(chess_board)
            for pcb in possible_chess_board_list:
                if not isinstance(pcb, ChessBoard):
                    continue

                if pcb not in self.close_list:
                    self.close_list.append(pcb)
                    self.open_list.put(pcb)

        return self.search_chess_board

让代理人解决八数码问题

def print_chess_board(map:ChessMap):
    for i in range(1, 10):
        if map[i-1] == 0:
            print(' ', end=' ')
        else:
            print(map[i-1], end=' ')

        if i % 3 == 0:
            print("")

if __name__ == "__main__":
    source = list(map(int, input("按从左到右从上到下的顺序\n输入初始状态的八数码行:\n")))
    target = list(map(int, input("输入目标状态的八数码行:\n")))

    source_chess_board = ChessBoard(source)
    target_chess_board = ChessBoard(target)

    oper = Oper(source_chess_board, target_chess_board)

    result = oper.calculate()

    step_index = 1
    print("----------------")
    print("起手式:")
    print_chess_board(source_chess_board.chess_map)
    for step in result.solution_path:
        print("step #%d:"%step_index)
        print_chess_board(step)
        print("----------------")
        step_index += 1
-- 到底了 --

日历

2025年04月

  12345
6789101112
13141516171819
20212223242526
27282930   

友情链接

文章目录