【AI】Python 实现八数码问题

news2024/11/17 10:25:14

实验一 八数码问题

1. 题目介绍

八数码问题描述为:在 3×3 组成的九宫格棋盘上,摆有 8 张牌,每张牌都刻有 1-8 中的某一个数码。棋盘中留有一个空格,允许其周围的某张牌向空格移动,这样通过移动牌就可以不断改变棋盘布局。这种游戏求解的问题是:给定一种初始的棋盘布局或结构(初始状态)和一个目标的布局(称目标状态),问如何移动牌,实现从初始状态到目标状态的转变。

例如如下的棋盘要求将初始状态移动到目标状态:
在这里插入图片描述

传统的解题方法包含深度优先搜索和广度优先搜索。但这会带来一个问题,即搜索是盲目的,没有根据当前棋盘的布局来动态地调整下一步搜索的策略。为此我们定义了启发式搜索算法(A* 算法),它会提取出当前棋盘的一些特征,来最优地选择下一次要搜索的方向。

对于八数码问题,其启发式方法为当前棋盘与目标棋盘的差异度,而差异度又可以通过两种方法来进行计算。

记当前棋盘为 source,目标棋盘为 target。第一种计算方法为:如果 source[i][j] != target[i][j],则差异度 += 1;第二种计算方法为:如果 source[i][j] == target[m][n],则差异度 += (abs(m - i) + abs(n - j))。

例如,对于上面的初始状态和目标状态,使用第一种计算方法,其差异度矩阵为(1 表示该位置两状态矩阵的元素不同,0 表示相同):

在这里插入图片描述

最终可以计算出两个矩阵的差异度为 4。

使用第二种计算方法,其差异度矩阵为(值表示 source[i][j] 的元素移动到目标位置所需的最短步数):

在这里插入图片描述
最终可以计算出两个矩阵的差异度为 5。

不管使用哪种办法,都能得出一个差异度,暂且记为 g。并且,解题的办法要么采用 DFS,要么采用 BFS,两种办法的搜索时间都会随着深度的增加而增加,我们的目标是尽量减少搜索的时间,也就是要想办法减少搜索深度。为了解决这个问题,记当前的搜索深度为 d,那么 d 越小越好。同时,我们又希望 g 越小越好,所以我们整体的目标就可以转化为 d + g 越小越好,这综合了 d 和 g 各自有的优势,是一个良好的 tradeoff。

因此,我们的整体目标也就转化成了:在 DFS 或 BFS 的函数中,对每一个状态都计算 f = d + g,选取 f 最小的那个结点,让它作为下次迭代的首选结点。

2. 代码演示

下面使用三种方式来评估启发式算法的性能,第一种是不使用启发式算法,第二种是使用前文提到的策略 1,第三种是使用前文提到的策略 2。

2.1 不使用启发式算法

在代码中,将变量 use_A_star 设定为 False 即指定不使用启发式算法,运行结果为:

在这里插入图片描述
运行 50 次求得所耗平均时间为:0.022931413650512697 s

2.2 使用启发式策略 1

在代码中,将变量 use_A_star 设定为 True,strategy 设定为 1,即指定使用启发式算法 1,运行结果为:

在这里插入图片描述
运行 50 次求得所耗平均时间为:0.0021903276443481444 s

2.3 使用启发式策略 2

在代码中,将变量 use_A_star 设定为 True,strategy 设定为 2,即指定使用启发式算法 2,运行结果为:

在这里插入图片描述
运行 50 次求得所耗平均时间为:0.002417140007019043 s

3. 结论分析

从三种方法的运行结果可以得出下列结论:使用启发式算法可以大幅度节省程序运行时间(是纯 BFS 的 1/10),启发式算法 1 比启发式算法 2 效率更高,这可能是因为算法 1 在计算矩阵差异度时只需要遍历一遍矩阵,而算法 2 需要遍历两遍矩阵。

4. 源码

import numpy as np
import time


class Node(object):
    def __init__(self, source, target, strategy, cost=0, depth=0):
        self.directions = ['left', 'up', 'right', 'down']
        self.source = source
        self.target = target
        self.cost = cost
        self.depth = depth
        self.strategy = strategy
        

    # 打印原始矩阵
    def print_source(self):
        for i in range(3):
            [print(self.source[i][j], end=' ') for j in range(3)]
            print()
        print()

    # 计算不在位的棋子个数
    def num_misposition(self):
        # 比较source和target,不同的地方标记为1,相同的地方标记为0
        flag = np.where(self.source == self.target, 0, 1)
        # 返回source与target之间不相同的数的个数
        return np.sum(np.reshape(flag, (flag.size,)))

    # 计算耗散值
    def get_cost(self):
        if self.strategy == 1:
            return self.depth + self.num_misposition()
        elif self.strategy == 2:
            flag = np.where(self.source == self.target, 0, 1)
            sum_cost = 0
            for i in range(3):
                for j in range(3):
                    if flag[i][j]:
                        for m in range(3):
                            for n in range(3):
                                if self.target[m][n] == self.source[i][j]:
                                    dif_row, dif_col = abs(m - i), abs(n - j)
                                    sum_cost += (dif_row + dif_col)
            return sum_cost

    # 将棋子0分别往四个方向移动
    def move(self):
        # 记录棋子0所在的行号和列号
        row, col = np.where(self.source == 0)
        row, col = row[0], col[0]
        moved_nodes = []
        for direction in self.directions:
            if direction == 'left' and col > 0:
                source_copy = self.source.copy()
                source_copy[row, col], source_copy[row, col - 1] = source_copy[row, col - 1], source_copy[row, col]
                moved_nodes.append(
                    Node(source_copy, target=self.target, cost=self.get_cost(), depth=self.depth + 1, strategy=self.strategy))
            elif direction == 'up' and row > 0:
                source_copy = self.source.copy()
                source_copy[row, col], source_copy[row - 1, col] = source_copy[row - 1, col], source_copy[row, col]
                moved_nodes.append(
                    Node(source_copy, target=self.target, cost=self.get_cost(), depth=self.depth + 1, strategy=self.strategy))
            elif direction == 'right' and col < len(self.source) - 1:
                source_copy = self.source.copy()
                source_copy[row, col], source_copy[row, col + 1] = source_copy[row, col + 1], source_copy[row, col]
                moved_nodes.append(
                    Node(source_copy, target=self.target, cost=self.get_cost(), depth=self.depth + 1, strategy=self.strategy))
            elif direction == 'down' and row < len(self.source) - 1:
                source_copy = self.source.copy()
                source_copy[row, col], source_copy[row + 1, col] = source_copy[row + 1, col], source_copy[row, col]
                moved_nodes.append(
                    Node(source_copy, target=self.target, cost=self.get_cost(), depth=self.depth + 1, strategy=self.strategy))
        return moved_nodes


class EightPuzzle(object):
    def __init__(self, init_node, use_A_star, strategy):
        self.use_A_star = use_A_star
        self.queue = []
        self.closed_nodes = []
        self.count = 0
        self.init_node = init_node
        self.time_start = 0
        self.time_end = 0
        self.strategy = strategy
        

    # 判断传入的结点是否在self.closed_nodes中
    def is_in_closed_nodes(self, node):
        for closed_node in self.closed_nodes:
            # 比较closed_node和node,不同的地方标记为1,相同的地方标记为0
            flag = np.where(closed_node.source == node.source, 0, 1)
            if np.sum(np.reshape(flag, (flag.size,))) == 0:
                return True
        return False
    
    # 获取最小耗散值的那个结点
    def get_min_cost_index(self):
        min_cost = self.queue[0].cost
        index = 0
        for i in range(len(self.queue)):
            if self.queue[i].cost < min_cost:
                index = i
                min_cost = self.queue[i].cost
        return index

    # bfs求解问题
    def bfs(self):
        self.time_start = time.time()
        self.queue.append(self.init_node)
        min_cost = self.init_node.cost
        while self.queue:
            if self.use_A_star:
                current_node_index = self.get_min_cost_index()
                current_node = self.queue.pop(current_node_index)
            else:
                current_node = self.queue.pop(0)
            self.closed_nodes.append(current_node)
            # 不在位棋子个数为0,到达终点
            if current_node.num_misposition() == 0:
                self.time_end = time.time()
                return True, self.time_end - self.time_start, current_node
            moved_nodes = current_node.move()
            for next_node in moved_nodes:
                if self.is_in_closed_nodes(next_node):
                    continue
                self.queue.append(next_node)
            self.count += 1
        self.time_end = time.time()
        return False, self.time_end - self.time_start, None


def main():
    source = np.array([[2, 8, 3], [1, 6, 4], [7, 0, 5]])
    target = np.array([[1, 2, 3], [8, 0, 4], [7, 6, 5]])
    print('The source matrix is:\n', source)
    print('The target matrix is:\n', target)
    use_A_star = True
    strategy = 2
    init_node = Node(source, target, strategy, cost=0)

    solution = EightPuzzle(init_node, use_A_star, strategy)
    has_solved, time_used, result_node = solution.bfs()

    if has_solved:
        print('\nThis problem has been solved, using', time_used, 's.')
        print('The result matrix is:')
        result_node.print_source()
        
    return time_used


if __name__ == '__main__':
    main()

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/60867.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

PDF或PPT中的某个图或表无损、高清的插入word里的方法,再转成pdf后放大6400%倍仍是高清图片...

本人使用Microsoft Office LTSC 专业增强版 2021版本的&#xff0c;其他版本不确定可不可以 可通过office tool plus下载安装相应版本的office&#xff0c;通过安装与激活安装与激活 0. 参考方法网址&#xff1a; PDF 转成 SVG 格式的方法(无损保留笔记痕迹) 1. pdf可能很多页&…

Dubbo SPI扩展机制源码详解(基于2.7.10)

Dubbo SPI 一. 概述 本文主要分享 Dubbo 的拓展机制 SPI。 想要理解 Dubbo &#xff0c;理解 Dubbo SPI 是非常必须的。在 Dubbo 中&#xff0c;提供了大量的拓展点&#xff0c;基于 Dubbo SPI 机制加载 Dubbo SPI官方文档&#xff1a;Dubbo SPI 概述 | Apache Dubbo 本文基…

Spring-IOC控制反转

Spring 1.简介 1.1简介 1.常见的框架&#xff1a; 2.常见的依赖&#xff1a; <dependencies><!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc --><dependency><groupId>org.springframework</groupId><artif…

SpringBoot基础之声明式事务和切面事务和编程式事务

文章目录前言一、事务特性开启事务二、事务的隔离级别三、事务的传播行为四、 Springboot事务1.Springboot声明式事务优点&#xff1a;缺点&#xff1a;实现方式&#xff1a;Transactional的参数声明式事务的约定流程&#xff1a;2. Springboot编程式事务SpringBoo切面编程式事…

[附源码]Python计算机毕业设计SSM教学团队管理系统(程序+LW)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

微服务框架 SpringCloud微服务架构 11 自定义镜像 11.2 Dockerfile

微服务框架 【SpringCloudRabbitMQDockerRedis搜索分布式&#xff0c;系统详解springcloud微服务技术栈课程|黑马程序员Java微服务】 SpringCloud微服务架构 文章目录微服务框架SpringCloud微服务架构11 自定义镜像11.2 Dockerfile11.2.1 什么是Dcokerfile11.2.2 直接开干11.…

【机器学习】支持向量机【上】硬间隔

有任何的书写错误、排版错误、概念错误等&#xff0c;希望大家包含指正。 在阅读本篇之前建议先学习&#xff1a; 【机器学习】拉格朗日对偶性 【机器学习】核函数 由于字数限制&#xff0c;分成两篇博客。 【机器学习】支持向量机【上】硬间隔 【机器学习】支持向量机【下】…

绿色荧光染料FITC-PEG-FA,Folic acid-PEG-Fluorescein,荧光素-聚乙二醇-叶酸

​ 1、名称 英文&#xff1a;FITC-PEG-FA&#xff0c;Folic acid-PEG-Fluorescein 中文&#xff1a;荧光素-聚乙二醇-叶酸 2、CAS编号&#xff1a;N/A 3、所属分类&#xff1a; Fluorescent PEG Folic acid&#xff08;FA&#xff09; PEG 4、分子量&#xff1a;可定制&a…

[附源码]计算机毕业设计网上书城网站Springboot程序

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

Java实现猜数游戏

1 问题 编写一个Java程序&#xff0c;实现以下功能&#xff1a; 2 方法 首先导入java.util包下的Random&#xff0c;让程序随便分配给用户一个数。 再导入java.util包下的Scanner类&#xff0c;构建Scanner对象&#xff0c;以便输入。 利用Random().nextInt()生成一个随机的i…

统计学中关于自由度的详细解释以及求平均值为什么消耗自由度1

首先我们要了解自由度是什么 自由度在本科教学中一般都不会过度涉及&#xff0c;因为它给老师和学生都带来了很大的困扰&#xff0c;它本身也有一些历史遗留问题&#xff0c;有很多人给出了很多不同的出发点和解释&#xff0c;比如1946年“自由度就是二次型的秩”&#xff0c;…

Android入门第42天-Android中的Service(IntentService)

开篇 在前一篇中我们讲了bindService的使用。并且我们留下了一个念想&#xff0c;即在bindService取值时故意阻塞30秒&#xff0c;引起了一次ANR并引出了今天的章节-IntentService。 IntentService的生命周期中有一个非常好的方法-onHandleIntent方法&#xff0c;它是一个abs…

创建你的第⼀个XXL-Job分布式调度任务

文章目录一、程序讲解1. 注解介绍2. 编写代码二、执⾏器管理2.1. Appname2.2. 名称2.3. 注册⽅式2.4. 机器地址2.5. 效果图三、任务管理一、程序讲解 1. 注解介绍 在 Spring Bean 实例中&#xff0c;开发 Job ⽅法⽅式格式要求为 public ReturnT<String> execute(Stri…

ARM 重定位引入和链接脚本

一、一个事实&#xff1a;大部分指令是位置有关编码 位置无关编码(PIC&#xff0c;position independent code)&#xff1a;汇编源文件被编码成二进制可执行程序时&#xff0c;编码方式与位置&#xff08;内存地址&#xff09;无关。 位置有关编码&#xff1a;汇编源码编码成…

分布式智能家居项目雏形

需求分析 客户端进入室内后(局域网)能够主动发现服务(如: 环境服务&#xff0c;灯光服务) 各种服务可以有不同的载体(如&#xff1a;由不同设备提供) 各种服务之间&#xff0c;服务与客户端之间完全无耦合(服务可自由增减) 客户端可以是移动APP&#xff0c;也可以是桌面软件…

【计算机毕业设计】基于JSP的房产中介系统的设计与实现

分类号&#xff1a;TP315 U D C&#xff1a;D10621-408-(2007) 6032 -0 密 级&#xff1a;公 开 编 号&#xff1a;2003211030 学位论文 基于JSP的房产中介系统的设计与实现 摘要 计算机与通信技术为基础的信息系统正处于蓬勃发展的时期。随着科学技术的不断提高&#xff…

2022SDNU-ACM结训赛题解

首先感谢一下各位出题人的精心准备、验题人的辛勤付出、以及选手的积极参加 题解 Problem A 柳予欣的归来【数学】 出题人&#xff1a; bhq 没想到一血是被打完山大的牛客比赛后来结训赛玩的wyx拿走的&#xff01; 题目描述&#xff1a; 计算(∑0<d<pd−1)m(\sum_{0…

Procreate iPad绘画教程

Procreate iPad绘画教程 通过动手绘画课程了解您需要了解的有关 Procreate 的所有信息。现在为 Procreate 5 更新 课程英文名&#xff1a;Drawing and Painting on the iPad with Procreate 此视频教程共11.0小时&#xff0c;中英双语字幕&#xff0c;画质清晰无水印&#x…

Qt第二十八章:异步

所有的数据处理都应该通过异步进行 定义信号# 自定义信号源 class QCustomSignal(QObject):signal_update_table Signal(list)signal_get_token Signal(str)signal_send_mq Signal() 处理函数 Slot()def handle_search(self):def thread_function():user_info get_use…

uImage的制作工具mkimage详解(源码编译、使用方法、添加的头解析、uImage的制作)

1、mkimage工具的源码 (1)mkimage是uboot下面的一个工具&#xff0c;用来将zImage制作成uImage&#xff0c;制作内核启动镜像(给zImage镜像添加64字节的头信息成uImage)&#xff1b; (2)mkimage的源码在"uboot/tool"目录下&#xff0c;在编译uboot时默认会编译出mkim…