【算法】山东大学人工智能限选课实验一(八数码问题)

news2024/12/23 11:19:00

实验一 八数码问题

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/59252.html

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

相关文章

【实验报告NO.000001】MIT 6.858 Computer System Security - Lab 1

0x00. 一切开始之前 MIT 6.858 是面向高年级本科生与研究生开设的一门关于计算机系统安全&#xff08;secure computer security&#xff09;的课程&#xff0c;内容包括威胁模型&#xff08;threat models&#xff09;、危害安全的攻击&#xff08;attacks that compromise s…

客快物流大数据项目(九十):ClickHouse的引擎介绍和深入日志引擎讲解

文章目录 ClickHouse的引擎介绍和深入日志引擎讲解 一、引擎介绍 二、日志引擎

【大数据趋势】12月3日纳指大概率反弹到黄金分割附近,然后下跌,之后进入趋势选择期,恒指会跟随。感觉或许有什么大事情要发生,瞎猜中。

行情核心源头分析: 纳斯达克指数 是否会符合大数据规则&#xff0c;走黄金分割线规则 回顾一下上周大数据预测的趋势&#xff0c;虽有波折但最终趋势预测准确 上周11.20日大数据模拟出一个趋势图&#xff0c;大趋势上需要继续上涨尾期&#xff0c;制造一个背离出现&#xff0c…

ZMQ中请求-应答模式的可靠性设计

一、什么是可靠性&#xff1f; 要给可靠性下定义&#xff0c;我们可以先界定它的相反面——故障。如果我们可以处理某些类型的故障&#xff0c;那么我们的模型对于这些故障就是可靠的。下面我们就来列举分布式ZMQ应用程序中可能发生的问题&#xff0c;从可能性高的故障开始&…

[附源码]计算机毕业设计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…

总结:原生servlet请求转发url与请求重定向url的使用区别

总结&#xff1a;原生servlet请求转发url与请求重定向url的使用区别一演示前提&#xff1a;1.演示案例的项目架构如图&#xff1a;2.设置web应用的映射根目录&#xff1a;/lmf&#xff0c;当然也可以不设置。二什么叫请求转发、请求重定向&#xff1f;1.请求转发解释图2. forwa…

Windows 文件共享功能使用方法, 局域网多台电脑之间传送文件

设想一下&#xff0c;家里或者公司有多台电脑&#xff0c;连接同一个Wifi&#xff0c;也就是处于同一个局域网中。 在不能使用微信、网盘的文件传输功能的情况下&#xff0c;这多台电脑之间&#xff0c;就只能用U盘传送数据吗&#xff1f; 不。Windows系统中已经提供了文件共享…

关于DDR协议一些操作的理解1

整体流程: 一些基本概念: 1.p_bank和l_bank 2.rank和bank 3.DIMM和SIMM 4.DLL概念: DDR控制器架构: 时钟频率对比: (1)

(1-线性回归问题)RBF神经网络

直接看公式&#xff0c;本质上就是非线性变换后的线性变化&#xff08;RBF神经网络的思想是将低维空间非线性不可分问题转换成高维空间线性可分问题&#xff09; Deeplearning Algorithms tutorial 谷歌的人工智能位于全球前列&#xff0c;在图像识别、语音识别、无人驾驶等技…

wy的leetcode刷题记录_Day56

wy的leetcode刷题记录_Day56 声明 本文章的所有题目信息都来源于leetcode 如有侵权请联系我删掉! 时间&#xff1a;2022-11-30 前言 目录wy的leetcode刷题记录_Day56声明前言895. 最大频率栈题目介绍思路代码收获236. 二叉树的最近公共祖先题目介绍思路代码收获895. 最大频率…

React项目中Manifest: Line: 1, column: 1, Syntax error的解决方法

大家好&#xff0c;今天和大家分享一个React项目中的一个小报错的解决方法。 在创建了一个项目后会有几个文件 public ---- 静态资源文件夹 favicon.ico ------ 网站页签图标 index.html -------- 主页面 logo192.png ------- logo图 logo512.png ------- logo图 manifest.js…

如何将C/C++代码转成webassembly

概述 WebAssembly/wasm WebAssembly 或者 wasm 是一个可移植、体积小、加载快并且兼容 Web 的全新格式 官网 &#xff1a; WebAssembly 快速上手&#xff1a; I want to… - WebAssemblyhttps://webassembly.org/getting-started/developers-guide/ 其实官网写的很详细&#xf…

局域网综合设计-----计算机网络

局域网综合设计 信息楼的配置 拓扑图 配置 全部在三层交换机配置 1.创建两个全局地址池vlan 52和valn53 全局地址池vlan52 全局地址池vlan53 2给vlan 52 和53 配置IP 地址 给vlan52配置ip并开启vlan52从全局地址池获取IP 子网 dns 给vlan53配置ip并开启vlan53从全局…

Android入门第37天-在子线程中调用Handler

简介 前一章我们以一个简单的小动画来解释了Handler。 这章我们会介绍在子线程里写Handler。如果是Handler写在了子线程中的话,我们就需要自己创建一个Looper对象了&#xff1a;创建的流程如下: 直接调用Looper.prepare()方法即可为当前线程创建Looper对象,而它的构造器会创…

Java并发编程—线程池

文章目录线程池什么是线程池线程池优点&#xff1a;线程复用技术线程池的实现原理是什么线程池执行任务的流程&#xff1f;线程池如何知道一个线程的任务已经执行完成线程池的核心参数拒绝策略线程池类型&#xff08;常用线程池&#xff09;阻塞队列执行execute()方法和submit(…

[附源码]计算机毕业设计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…

MySQL统计函数count详解

MySQL统计函数count详解1. count()概述2. count(1)和count(*)和count(列名)的区别3. count(*)的实现方式1. count()概述 count() 是一个聚合函数&#xff0c;返回指定匹配条件的行数。开发中常用来统计表中数据&#xff0c;全部数据&#xff0c;不为null数据&#xff0c;或者去…

yocto machine class解析之flashlayout-stm32mp

yocto machine class解析之flashlayout-stm32mp 上一篇文章中我们详细介绍了st-partitions-image class。里面根据配置生成了许多的分区镜像以及分区镜像的一些参数设置。本章节介绍的flashlayout class就会根据上面生成的这些参数来生成特定的.tsv刷机文件供ST的刷机工具使用…

Bootstrap5 容器

我们可以使用以下两个容器类&#xff1a; .container 类用于固定宽度并支持响应式布局的容器。.container-fluid 类用于 100% 宽度&#xff0c;占据全部视口&#xff08;viewport&#xff09;的容器。固定宽度 .container 类用于创建固定宽度的响应式页面。 注意&#xff1a…

[node文件的上传和下载]一.node实现文件上传;二、Express实现文件下载;三、遍历下载文件夹下的文件,拼接成一个下载的url,传递到前端

目录 一.node实现文件上传 1.FormData对象&#xff1a;以对象的方式来表示页面中的表单&#xff0c;又称为表单对象。以key:value的方式来保存数据&#xff0c;XMLHttpRequest对象可以轻松的将表单对象发送到服务器端 &#xff08;1&#xff09;是一个构造函数&#xff1a;ne…