Python实现A*算法解决N数码问题

  • A*算法的描述
  • A*算法的步骤
  • 问题描述
  • 代码以及测试结果
    • 算法优势
    • 算法存在一些不足

A*算法的描述

A*算法是BFS的一个变种,它把原来的BFS算法的无启发式的搜索改成了启发式的搜索,可以有效的减低节点的搜索个数。启发式的搜索公式:
f(n)=g(n)+h(n)f(n)=g(n)+h(n)f(n)=g(n)+h(n)
其中g(n)g(n)g(n)是从根点到当前节点的距离,h(n)h(n)h(n)是当前节点到目标节点的估计距离。

假设当前节点到目标节点的真是距离是h∗(n)h^*(n)h∗(n),那么只有当h(n)≤h∗(n)h(n)\le h^*(n)h(n)≤h∗(n)时才是A算法,否则是A算法。只有A算法才能保证有最优解!

A*算法的步骤

A算法和BFS十分类似,两者的主要区别在于BFS的候选队列是盲目的,而A算法也使用了类似于BFS的候选队列,但是在选择的时候,是先选择出候选队列中代价最小的优先搜索,这个候选队列一般使用堆来表示。

问题描述

数码问题如图所示,以3数码为例:
216408753⇒123456780\begin{array}{ccc} 2 & 1 & 6\\ 4 & 0 & 8\\ 7 & 5 & 3 \end{array} \Rightarrow \begin{array}{ccc} 1 & 2 & 3\\ 4 & 5 & 6\\ 7 & 8 & 0 \end{array}247​105​683​⇒147​258​360​
上述是起始状态到目标状态的图解。我们要求给定任意的N数码,要在规定的时间内求解或者判定超时。

这里的启发函数使用曼哈顿距离,两点的曼哈顿距离是对应横纵坐标差的绝对值之和,具体可以百度。。在这里我们计算每个点的曼哈顿,并把所有点曼哈顿距离累加起来最为启发值。

代码以及测试结果

需要把数据存放在和代码同一级目录下的infile.txt文件中,文件格式如下:

N=3
1 6 3 4 5 2 8 7 0

上述说明这是个3数码问题,初始状态是:
163452870\begin{array}{ccc} 1& 6 & 3\\ 4 & 5 & 2\\ 8 & 7 & 0 \end{array} 148​657​320​
同样的,对于4数码问题,应该是下面所示:

N=4
5 1 2 4 9 6 3 8 13 15 10 11 14 0 7 12

每个文件可以有很多行数据。

下面是代码:

import heapq
import copy
import re
import datetimeBLOCK = []  # 给定状态
GOAL = []  # 目标状态# 4个方向
direction = [[0, 1], [0, -1], [1, 0], [-1, 0]]# OPEN表
OPEN = []# 节点的总数
SUM_NODE_NUM = 0# 状态节点
class State(object):def __init__(self, gn=0, hn=0, state=None, hash_value=None, par=None):'''初始化:param gn: gn是初始化到现在的距离:param hn: 启发距离:param state: 节点存储的状态:param hash_value: 哈希值,用于判重:param par: 父节点指针'''self.gn = gnself.hn = hnself.fn = self.gn + self.hnself.child = []  # 孩子节点self.par = par  # 父节点self.state = state  # 局面状态self.hash_value = hash_value  # 哈希值def __lt__(self, other):  # 用于堆的比较,返回距离最小的return self.fn < other.fndef __eq__(self, other):  # 相等的判断return self.hash_value == other.hash_valuedef __ne__(self, other):  # 不等的判断return not self.__eq__(other)def manhattan_dis(cur_node, end_node):'''计算曼哈顿距离:param cur_state: 当前状态:return: 到目的状态的曼哈顿距离'''cur_state = cur_node.stateend_state = end_node.statedist = 0N = len(cur_state)for i in range(N):for j in range(N):if cur_state[i][j] == end_state[i][j]:continuenum = cur_state[i][j]if num == 0:x = N - 1y = N - 1else:x = num / N  # 理论横坐标y = num - N * x - 1  # 理论的纵坐标dist += (abs(x - i) + abs(y - j))return distdef test_fn(cur_node, end_node):return 0def generate_child(cur_node, end_node, hash_set, open_table, dis_fn):'''生成子节点函数:param cur_node:  当前节点:param end_node:  最终状态节点:param hash_set:  哈希表,用于判重:param open_table: OPEN表:param dis_fn: 距离函数:return: None'''if cur_node == end_node:heapq.heappush(open_table, end_node)returnnum = len(cur_node.state)for i in range(0, num):for j in range(0, num):if cur_node.state[i][j] != 0:continuefor d in direction:  # 四个偏移方向x = i + d[0]y = j + d[1]if x < 0 or x >= num or y < 0 or y >= num:  # 越界了continue# 记录扩展节点的个数global SUM_NODE_NUMSUM_NODE_NUM += 1state = copy.deepcopy(cur_node.state)  # 复制父节点的状态state[i][j], state[x][y] = state[x][y], state[i][j]  # 交换位置h = hash(str(state))  # 哈希时要先转换成字符串if h in hash_set:  # 重复了continuehash_set.add(h)  # 加入哈希表gn = cur_node.gn + 1  # 已经走的距离函数hn = dis_fn(cur_node, end_node)  # 启发的距离函数node = State(gn, hn, state, h, cur_node)  # 新建节点cur_node.child.append(node)  # 加入到孩子队列heapq.heappush(open_table, node)  # 加入到堆中def print_path(node):'''输出路径:param node: 最终的节点:return: None'''num = node.gndef show_block(block):print("---------------")for b in block:print(b)stack = []  # 模拟栈while node.par is not None:stack.append(node.state)node = node.parstack.append(node.state)while len(stack) != 0:t = stack.pop()show_block(t)return numdef A_start(start, end, distance_fn, generate_child_fn, time_limit=10):'''A*算法:param start: 起始状态:param end: 终止状态:param distance_fn: 距离函数,可以使用自定义的:param generate_child_fn: 产生孩子节点的函数:param time_limit: 时间限制,默认10秒:return: None'''root = State(0, 0, start, hash(str(BLOCK)), None)  # 根节点end_state = State(0, 0, end, hash(str(GOAL)), None)  # 最后的节点if root == end_state:print("start == end !")OPEN.append(root)heapq.heapify(OPEN)node_hash_set = set()  # 存储节点的哈希值node_hash_set.add(root.hash_value)start_time = datetime.datetime.now()while len(OPEN) != 0:top = heapq.heappop(OPEN)if top == end_state:  # 结束后直接输出路径return print_path(top)# 产生孩子节点,孩子节点加入OPEN表generate_child_fn(cur_node=top, end_node=end_state, hash_set=node_hash_set,open_table=OPEN, dis_fn=distance_fn)cur_time = datetime.datetime.now()# 超时处理if (cur_time - start_time).seconds > time_limit:print("Time running out, break !")print("Number of nodes:", SUM_NODE_NUM)return -1print("No road !")  # 没有路径return -1def read_block(block, line, N):'''读取一行数据作为原始状态:param block: 原始状态:param line: 一行数据:param N: 数据的总数:return: None'''pattern = re.compile(r'\d+')  # 正则表达式提取数据res = re.findall(pattern, line)t = 0tmp = []for i in res:t += 1tmp.append(int(i))if t == N:t = 0block.append(tmp)tmp = []if __name__ == '__main__':try:file = open("./infile.txt", "r")except IOError:print("can not open file infile.txt !")exit(1)f = open("./infile.txt")NUMBER = int(f.readline()[-2])n = 1for i in range(NUMBER):l = []for j in range(NUMBER):l.append(n)n += 1GOAL.append(l)GOAL[NUMBER - 1][NUMBER - 1] = 0for line in f:  # 读取每一行数据OPEN = []  # 这里别忘了清空BLOCK = []read_block(BLOCK, line, NUMBER)SUM_NODE_NUM = 0start_t = datetime.datetime.now()# 这里添加5秒超时处理,可以根据实际情况选择启发函数length = A_start(BLOCK, GOAL, manhattan_dis, generate_child, time_limit=10)end_t = datetime.datetime.now()if length != -1:print("length =", length)print("time = ", (end_t - start_t).total_seconds(), "s")print("Nodes =", SUM_NODE_NUM)

上述的N=4的情况为例,输出结果:

---------------
[5, 1, 2, 4]
[9, 6, 3, 8]
[13, 15, 10, 11]
[14, 0, 7, 12]
---------------
[5, 1, 2, 4]
[9, 6, 3, 8]
[13, 0, 10, 11]
[14, 15, 7, 12]
---------------
[5, 1, 2, 4]
[9, 6, 3, 8]
[13, 10, 0, 11]
[14, 15, 7, 12]
---------------
[5, 1, 2, 4]
[9, 6, 3, 8]
[13, 10, 7, 11]
[14, 15, 0, 12]
---------------
[5, 1, 2, 4]
[9, 6, 3, 8]
[13, 10, 7, 11]
[14, 0, 15, 12]
---------------
[5, 1, 2, 4]
[9, 6, 3, 8]
[13, 10, 7, 11]
[0, 14, 15, 12]
---------------
[5, 1, 2, 4]
[9, 6, 3, 8]
[0, 10, 7, 11]
[13, 14, 15, 12]
---------------
[5, 1, 2, 4]
[0, 6, 3, 8]
[9, 10, 7, 11]
[13, 14, 15, 12]
---------------
[0, 1, 2, 4]
[5, 6, 3, 8]
[9, 10, 7, 11]
[13, 14, 15, 12]
---------------
[1, 0, 2, 4]
[5, 6, 3, 8]
[9, 10, 7, 11]
[13, 14, 15, 12]
---------------
[1, 2, 0, 4]
[5, 6, 3, 8]
[9, 10, 7, 11]
[13, 14, 15, 12]
---------------
[1, 2, 3, 4]
[5, 6, 0, 8]
[9, 10, 7, 11]
[13, 14, 15, 12]
---------------
[1, 2, 3, 4]
[5, 6, 7, 8]
[9, 10, 0, 11]
[13, 14, 15, 12]
---------------
[1, 2, 3, 4]
[5, 6, 7, 8]
[9, 10, 11, 0]
[13, 14, 15, 12]
---------------
[1, 2, 3, 4]
[5, 6, 7, 8]
[9, 10, 11, 12]
[13, 14, 15, 0]
length = 14
time =  0.094114 s
Nodes = 2955

上述结果同时输出了每一个步骤。总共需要14步,耗时0.094114秒,搜索树总共包含了2955个节点

假设我们不适用启发函数,也就是说把启发函数返回值改成0,那么就是宽度优先搜索。因为输出步骤一样,所以在这里仅仅给出时间结果:

length = 14
time =  4.628868 s
Nodes = 174650

还是14步,耗时4.628868秒,搜索树总共有174650个节点。注意这里最好是把默认的5秒限制延长一下!

综上可以看出:BFS耗费的时间是A∗A^*A∗的50倍左右,节点的个数是60倍左右,启发式效果还是很明显的!

算法优势

可以适合自定义的启发函数和自定义大小的数码问题;判重复的哈希操作可以有效避免重复节点的展开;OPENOPENOPEN表使用了堆的结构,每次插入的时间都是log⁡2N\log_{2}^{N}log2N​,NNN是OPENOPENOPEN表中节点的个数;使用setsetset这个哈希方式的集合,使得每次的查询重复和插入新节点的时间都是O(1)O(1)O(1);可以根据实际情况限时搜索时间!

算法存在一些不足

在hash函数上,可能会有哈希冲突,导致有些未被展开的节点永远不会倍展开,这可能导致无解,节点判断重复可以有改进的空间。

Python实现A*算法解决N数码问题相关推荐

  1. Python利用A*算法解决八数码问题

    资源下载地址:https://download.csdn.net/download/sheziqiong/86790565 资源下载地址:https://download.csdn.net/downl ...

  2. Astar、A星算法解决八数码问题--python实现

    一.问题描述 数码问题又称9宫问题,与游戏"华容道"类似.意在给定的3*3棋格的8个格子内分别放一个符号,符号之间互不相同,余下的一格为空格.并且通常把8个符号在棋格上的排列顺序称 ...

  3. A*算法解决八数码问题 Java语言实现

    A*算法解决八数码问题 Java语言实现 参考文章: (1)A*算法解决八数码问题 Java语言实现 (2)https://www.cnblogs.com/beilin/p/5981483.html ...

  4. 题目2:隐式图的搜索问题(A*算法解决八数码)

    数据结构课程实践系列 题目1:学生成绩档案管理系统(实验准备) 题目2:隐式图的搜索问题(A*算法解决八数码) 题目3:文本文件单词的检索与计数(实验准备) 文章目录 数据结构课程实践系列 题目1:学 ...

  5. 广度优先算法解决8数码问题【c++】

    8数码问题 (广度优先算法解决----c++) 8数码问题问题描述 八数码问题也称为九宫问题,在3×3的棋盘上,摆有八个棋子,每个棋子上标有1至8的某一数字.棋盘中留有一个空格(空格用0来表示),空格 ...

  6. A*算法解决8数码问题python实现

    1.A*的通俗理解 很多游戏特别是rts,rpg类游戏,都需要用到寻路.寻路算法有深度优先搜索(DFS),广度优先搜索(BFS),A星算法等,而A星算法是一种具备启发性策略的算法,效率是几种算法中最高 ...

  7. Nilsson's sequence score算法解决八数码问题解释

    解决了最近一个人工智能关于解决八数码难题的作业. 图可能看不清,除了黑块外其他位置是英文字母ABCDEFGH A*:f(n)=g(n)+h(n) 其中f为总花费,g为已知花费(深度),h为估计花费 关 ...

  8. 全局择优搜索、A*算法、宽度优先算法解决八数码问题

    1问题描述 使用盲目搜索中的宽度优先搜索算法或者使用启发式搜索中的全局择优搜索或A算法,对任意的八数码问题给出求解结果.例如:对于如下具体的八数码问题: 通过设计启发函数,编程实现求解过程,如果问题有 ...

  9. A*算法解决八数码问题 人工智能原理实验报告 启发式搜索 python

    目录 一.实验主要步骤 ①.设计界面输入规则 ②.判断是否有解 ③.求解 二.实验结果展示 三.附录 完整实验程序代码: 一.实验主要步骤 ①.设计界面输入规则 有且仅有9位数字代表数码和空格,从左到 ...

最新文章

  1. ios开发读取剪切板的内容_iOS中管理剪切板的UIPasteboard粘贴板类用法详解
  2. linux空间管理,教你玩转Linux—磁盘管理
  3. 2013\National _C_C++_A\1.填算式
  4. Asia Yokohama Regional Contest 2018 G题 What Goes Up Must Come Down(树状数组求逆序对)
  5. lambdas for_Java 8发布了! — Lambdas教程
  6. 消息称百度网盘青春版降速23倍:从52MB/s降至2.2MB/s
  7. 【Elasticsearch】Elasticsearch高级调优方法论之——根治慢查询!
  8. python json()是什么函数_python 处理 json 四个函数dumps、loads、dump、load的区别
  9. 关于Decorator模式
  10. 常见水果日文名称整理:
  11. erp管理系统软件价格
  12. 【Java】——命名规范
  13. Java指导书练习题——抽象类
  14. MaxEnt软件的下载与安装
  15. 使用Hydra通过ssh破解密码
  16. TensorRT 命令行程序trtexec常用用法
  17. 小程序实现语音识别歌曲的功能,对接讯飞的api,踩坑篇!!
  18. 指标详解(1)-- 轨道线指标(ENE)详解
  19. 十大关键词道破2016年安防状况
  20. yarn : 无法加载文件 D:\config\node\node_global\yarn.ps1,因为在此系统上禁止运行脚本。

热门文章

  1. STL常用函数总结-map
  2. 2018: 跑图(深搜)
  3. 素数判定 [2009年哈尔滨工业大学计算机研究生机试真题]
  4. Linux中使用tar打包解包查看的使用方法
  5. Python3.x Numpy中的array数组_矩阵操作
  6. stm32F1的JTAG、SWJ作为普通引脚使用。禁用JTAG、SWJ。
  7. LeetCode 415. 字符串相加 (逢十进一模版字符处理)
  8. Struts2一个诡异问题的解决
  9. 敏捷开发一千零一问系列之十四:敏捷开发加班吗?
  10. 员工管理系统————员工修改模块