datawhale12月学习——算法的应用:Puzzle(八数码/华容道)

前情回顾

  1. Percolation

1 题目

1.1 问题概述

详见AlgorithmRunnig - 八数码

目标:使用A*算法 or bfs、dfs算法,解决8数码问题。
八数码:是指在3x3的矩阵中,其中有8个格子放置成1-8,剩下一个格子是空格。你能够移动和空格相邻的格子到空格,直到这个矩阵满足每一行依次从左到右读取是有序,例如得到最后得到1-8有序,最后一个格子是空格。下图是一个例子:
在这里插入图片描述

使用广度优先搜索(BFS)和A*算法实现求解。

1.2 实现要求

  1. 实现一个Board类,存储每一个棋盘状态
    • 存储棋盘状态
    • 计算是否为最终解
    • 查找邻近状态
    • 计算与目标解间的距离(海明距离、曼哈顿距离)
    • 计算代价函数(启发式得分)
    • 输出唯一标识
  2. 实现一个Solver类,用于求解
    • 求解
    • 回溯并输出解

2 程序实现

2.1 Board类

class Board(object):
    """
    data: NxN的二维数组,list or narray; 例如: np.array([[0,1,3],[4,2,5],[7,8,6]])
    """
    def __init__(self, data, use_func = "hamming"):
        if type(data) == list:
            data = np.array(data)
        self.board_data = data
        self.use_func = use_func
        self.prev = None
        self.size()

    # board size n
    def size(self):
        self.n = self.board_data.shape[0]
        return self.n
    
    # 从val值,得到其在八数码中的正确的问题。
    def get_xy_from_value(self, val):
        if val == 0:
            return self.n-1,self.n-1
        idx = (val) // self.n - 1
        idy = (val) % self.n
        return idx, idy

    # 当前状态到目标状态的海明距离
    def hamming(self):
        dist = 0
        for i in range(self.n):
            for j in range(self.n):
                if i==j==self.n-1:
                    break
                if (i)*self.n+j+1 == self.board_data[i][j]:
                    pass
                else:
                    dist += 1
        return dist

    # 当前状态到目标状态的曼哈顿距离
    def manhattan(self):
        dist = 0
        for i in range(self.n):
            for j in range(self.n):
                if self.board_data[i][j] == 0:
                    continue
                idx,idy = self.get_xy_from_value(self.board_data[i][j])
                dist += abs(idx-i) + abs(idy-j)
        return dist
    
    # 启发式的得分 source 
    def get_score(self):
        if self.use_func == 'hamming':
            return self.height + self.hamming()
        return self.height + self.manhattan()

    # 是否是目标状态
    def is_target(self):
        if self.use_func == 'hamming':
            return self.hamming() == 0
        return self.manhattan() == 0
    
    # 当前状态的所有邻居节点,return list<Board>
    def neighbors(self):
        all_neighbors = []
        [zeroi,zeroj] = np.argwhere(self.board_data == 0)[0]
        if zeroi < self.n - 1:
            newdata = self.board_data.copy()
            newdata[zeroi][zeroj], newdata[zeroi+1][zeroj] = newdata[zeroi+1][zeroj], newdata[zeroi][zeroj]
            new = Board(newdata)
            new.prev = self
            all_neighbors.append(new)

        if zeroj < self.n - 1:
            newdata = self.board_data.copy()
            newdata[zeroi][zeroj], newdata[zeroi][zeroj+1] = newdata[zeroi][zeroj+1], newdata[zeroi][zeroj]
            new = Board(newdata)
            new.prev = self
            all_neighbors.append(new)

        if zeroi > 0:
            newdata = self.board_data.copy()
            newdata[zeroi][zeroj], newdata[zeroi-1][zeroj] = newdata[zeroi-1][zeroj], newdata[zeroi][zeroj]
            new = Board(newdata)
            new.prev = self
            all_neighbors.append(new)

        if zeroj > 0:
            newdata = self.board_data.copy()
            newdata[zeroi][zeroj], newdata[zeroi][zeroj-1] = newdata[zeroi][zeroj-1], newdata[zeroi][zeroj]
            new = Board(newdata)
            new.prev = self
            all_neighbors.append(new)

        self.neighbor = all_neighbors
        return all_neighbors

    # 当前状态是否是最终状态 ?这个用来干啥的
    def is_solvable(self):
        return self.is_target()
    
    # 棋牌状态的string表示,例如return "1 2 3 4 5 6 7 8 0"
    def to_string(self):
        data = self.board_data.flatten()
        data = [str(each) for each in data]
        return "_".join(data)
    
    # 使用数值表示当前状态的唯一性,用于迭代过程中的判断
    def hash_code(self):
        hash_value = 0
        for i in range(self.n):
            for j in range(self.n):
                hash_value = hash_value * 10 + self.board_data[i][j]
        return hash_value

2.2 BFS和A*

BFS的实现,使用递归形式实现搜索,生成字符串存储查找集,并逐层传递(这里其实可以不用存储字典,会更节省内存)。当找到目标状态时,搜索结束并返回目标board。
由于目标board存储了上一步骤,因此可以回溯。

def BFS(queue, status, i = 0):
    newqueue = []
    for each in queue:
        sta = each.to_string()
        if sta in status.keys():
            continue
        #标记状态
        status[sta] = each
        each.height = i
        if each.is_target():
            return each,i
        neighbors = each.neighbors()
        newqueue.extend(neighbors)
    i += 1
    end,endi = BFS(newqueue, status, i)
    return end,endi

A*只需要在此基础上,首先进行启发式函数的计算,并实现排序即可。

def Astar(queue, status, i = 0):
    score = dict()
    for each in queue:
        each.height = i
        if each.is_target():
            return each
        score[each] = each.get_score()
        
    score = dict(sorted(score.items(), key=lambda d:d[1]))
    newqueue = []
    minscore = [each for each in score.values()]
    for prior in score.keys():
        if score[prior] > minscore[0]:
            break
        sta = prior.to_string()
        if sta in status.keys():
            continue
        status[sta] = prior
        neighbors = prior.neighbors()
        newqueue.extend(neighbors)
    
    i += 1
    end = Astar(newqueue, status, i)
    return end

2.3 Solver

class Solver(object):
    """
    board:初始化棋盘状态。
    use_algo: 解决八数码问题的算法,bfs和astart,当前你也可以实现dfs
    
    """
    def __init__(self, board, use_algo="astar"):
        self.board = board
        self.ans_board = None
        self.use_algo = use_algo

    # 解决八数码问题,你必须实验这个函数,用来解决八数码问题
    def solver(self):
        if self.use_algo == "astar":
            ans_board = Astar([self.board], dict(), i = 0)
        else:
            ans_board,endi = BFS([self.board], dict(), i = 0)
        self.ans_board = ans_board
        return ans_board

    # 返回最短的路径
    def moves(self):
        return self.ans_board.height

    # 返回最优结果的路径,你必须返回这个结果
    def solution(self):
        ans_list = []
        last = self.ans_board
        ans_list.append(last.board_data)
        for i in range(self.ans_board.height):
            last = last.prev
            ans_list.append(last.board_data)
        ans_list.reverse()
        return ans_list

3 测试实现

3.1 本地测试

input[4x4]: 
    [[ 1  6  2  4]
     [ 5  0  3  8]
     [ 9 10  7 11]
     [13 14 15 12]]

测试结果
在这里插入图片描述

input[3x3]: 
    [[0 1 3]
     [4 2 5]
     [7 8 6]]

测试结果
在这里插入图片描述

可视化代码如下

num = len(solution)
fig,ax = plt.subplots(num//3+1,3,figsize=(6*3, 6*(num//3+1)),dpi = 100)
for i in range(num):
    each = solution[i]
    sns.heatmap(each, cmap=sns.diverging_palette(20, 220, n=200), linewidths = 3,annot=True,cbar = False,annot_kws={"fontsize":20},ax = ax[i//3][i % 3])
    ax[i//3][i % 3].set_title(str(i))
for i in range(ax.size):    
    ax[i//3][i % 3].axis("off")

3.2 线上测试

线上测试地址

需要solver.solver()的返回值为列表,否则会报len的错误。

三阶数独
在这里插入图片描述
四阶数独
在这里插入图片描述
五阶数独
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

SheltonXiao

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值