首页 > 编程语言 >A*算法利用曼哈顿距离作为评价指标解决八数码问题(python)

A*算法利用曼哈顿距离作为评价指标解决八数码问题(python)

时间:2023-01-02 09:35:35浏览次数:55  
标签:node arr python 空格 曼哈顿 数码 print 节点

1. 题目说明

在一个3*3的方棋盘上放置着1,2,3,4,5,6,7,8八个数码,每个数码占一格,且有一个空格。这些数码可以在棋盘上移动,其移动规则是:与空格相邻的数码方格可以移入空格。现在的问题是:对于指定的初始棋局和目标棋局,给出数码的移动序列。

例:

 

题目要求:使用启发式规则

2. 设计思路

正如前面题目所言八数码问题是将一个带空格的八个数码块从初始状态通过空格之间上下左右的方块交换变换成数码的目标状态(此时我们将空格用数字0来表示)

首先我们可以把每一个变化状态想象成一个节点,这个节点包含了它所代表的状态序列,这样我们就把八数码问题转换为从一个节点到目标节点的问题了。这显然进入到图论上的问题了,此时我们考虑的便是节点到节点可不可达的问题,通过一些资料的查找发现当出发节点的序列和目的节点的序列的逆序数同为奇数或者同为偶数便是可达的,否则不可达。

此时我们处理好路径可不可达的问题之后,若可达进入搜索序列程序,不可达则直接退出即可。

搜索主程序主要在如下步骤,先初始化一个节点为根节点(初始序列),通过调用节点类定义的方法获取它相应的子节点(表示空格能与周边移动的单元),而每次变换都是一个新的状态,也就是为一个新的节点。

这个时候便会引申出来一个问题,在每个节点都有其子节点我们要以何种方式来进行寻找呢?

此时在对节点搜索上在大方向上分为两块:

一个是传统的搜索算法,它主要是通过一定固定规则进行遍历查找,例如广度搜索,深度搜索等,它们在不断递归搜索没有相应的变通,在数据规模较小的情况下可能可以迅速得出结果,但是在处理问题规模大的时候它便变得笨拙起来了。所谓笨拙是因为没有利用一些有用的信息为自己筛选规划,不管什么信息它都会进行处理,这也就占用了大量的时空复杂度。

第二个是启发式规则的方法来搜索。所谓启发式规则在我的理解看来就是它在搜索过程中较为智能,有一定的判断依据或者说是判优过程,优先选择优先级高即代价更低、优先选择更高概率的搜索路径,经过一系列的判断执行过程最终找到目标。上面说的那些流程也同样引申出来一个问题,如何判优?有什么相对应的规则来进行?

对于上面规则的回答也就对应在启发式上两种不同的算法分支:贪婪算法和A*算法

贪婪算法在解决从出发点到目的地最优规划上,它通过本节点到目的节点计算的最优路径上选择一个离目的节点更近的节点作为下一跳,不断此番选择,最后找到目的节点。不过它存在着的问题也很严重,那便是如此选择的不一定是最优的路线,以及选择路线上可能会陷入循环回路之中导致一直在路上的子环搜索。

所以说对于以上问题A*算法应运而生,不过在我看来它跟贪婪算法区别在于在评价方式方面加入了从出发节点到本节点的代价估量,所以说A*算法加入了前面说的估量值可以让代价选择上是不断增加的最终找到目的节点为止。

本题我便是用的A*算法,在初始节点到目前节点的代价评估计算用的是深度值,每向下一层我便代价值加一,而现节点到目的节点的损失代价有两种不同的评估方式,一个是以不在位数作为评估,一个是利用曼哈顿距离来评估。我们在这里探讨这两种方式之间差异,不在位数的评估利用的是跟目的状态比对没有在对于位置的数码数,而曼哈顿距离同样也是比对不过它是利用曼哈顿距离来计算代价,要说谁提供的代价信息更为可靠,当属于经典图论上曼哈顿距离莫属了,因为它处理的就是空间中的两点在非欧几里得方式下最优到目的地的距离。

介绍了选择选择A*算法的哈曼顿方式来搜索之后,我用一张流程图来介绍其中流程。

 

3.作品设计完整代码

  1 import copy
  2 
  3 import numpy as np
  4 
  5 class Node:
  6 
  7     def __init__(self,node,parent,cost,distance):
  8 
  9         self.node=node
 10 
 11         self.parent=parent
 12 
 13         self.cost=cost
 14 
 15         self.distance = distance
 16 
 17     def get_children(self):
 18 
 19         children = []
 20 
 21         for i in canGoNodes(self.node):
 22 
 23             child = Node(node=get_child(self.node, i),
 24 
 25                          parent=self,
 26 
 27                          cost=self.cost + 1,
 28 
 29                          distance=self.distance + mhdDistance(self.node, goal_arr))
 30 
 31             children.append(child)
 32 
 33         return children
 34 
 35 # 计算逆序数
 36 
 37 def reverseOrdinalNum(numString):
 38 
 39     num=0
 40 
 41     for i in range(1, 9):
 42 
 43         temp=0
 44 
 45         for j in range(0, i):
 46 
 47             if numString[j] > numString[i] and numString[i] != '0':
 48 
 49                 temp += 1
 50 
 51         num = num+temp
 52 
 53     return num
 54 
 55 # 找到目标在arr节点的位置
 56 
 57 def findNode(arr,target):
 58 
 59     for i in arr:
 60 
 61         for j in i:
 62 
 63             if j==target:
 64 
 65                 return arr.index(i),i.index(j)
 66 
 67 # 获取能够和空格交换的子节点
 68 
 69 def canGoNodes(arr):
 70 
 71     nodes=[]
 72 
 73     # 首先得先寻找到空格在此节点的下标位置
 74 
 75     x,y=findNode(arr,'0')
 76 
 77     # 向上
 78 
 79     if x>0:
 80 
 81         nodes.append(arr[x-1][y])
 82 
 83     # 向左
 84 
 85     if y>0:
 86 
 87         nodes.append(arr[x][y-1])
 88 
 89     # 向下
 90 
 91     if x<2:
 92 
 93         nodes.append(arr[x+1][y])
 94 
 95     # 向右
 96 
 97     if y<2:
 98 
 99         nodes.append(arr[x][y+1])
100 
101     return nodes
102 
103 # 将字符串转换为列表
104 
105 def string_to_list(str):
106 
107     str_list = list(str)
108 
109     return [str_list[i:i + 3] for i in range(0, len(str_list), 3)]
110 
111  
112 
113 def get_child(arr,i):
114 
115     node_new=copy.deepcopy(arr)
116 
117     x, y = findNode(node_new, '0')
118 
119     x1,y1=findNode(node_new,i)
120 
121     node_new[x][y],node_new[x1][y1]=node_new[x1][y1],node_new[x][y]
122 
123     return node_new
124 
125 def isSolution(initState,endState):
126 
127     initState=initState.replace(" ", "")
128 
129     endState=endState.replace(" ", "")
130 
131     if (reverseOrdinalNum(initState)%2)==(reverseOrdinalNum(endState)%2):
132 
133         return True
134 
135     else:
136 
137         return False
138 
139 # 计算曼哈顿距离
140 
141 def mhdDistance(node1,node2):
142 
143     distance = []
144 
145     for i in node1:
146 
147         for j in i:
148 
149             loc1 = findNode(node1, j)
150 
151             loc2 = findNode(node2, j)
152 
153             distance.append(abs(loc1[0] - loc2[0]) + abs(loc1[1] - loc2[1]))
154 
155     return sum(distance)
156 
157 # A*算法-曼哈顿距离
158 
159 def AStar_MHT(initial_arr, goal_arr):
160 
161     open = [initial_arr]
162 
163     close = []
164 
165     while len(open) > 0:  # OPEN表是否为空表
166 
167         open_1 = [i.node for i in open]
168 
169         close_1 = [i.node for i in close]
170 
171         n = open.pop(0)
172 
173         close.append(n)
174 
175         if n.node == goal_arr:
176 
177             print('最优路径如下:')
178 
179             print(np.array(n.node,dtype=object))  # 转换成矩阵打印最终节点
180 
181             best_path(n)
182 
183             break
184 
185         else:
186 
187             for i in n.get_children():  # 添加子节点进OPEN
188 
189                 if i.node not in open_1:
190 
191                     if i.node not in close_1:
192 
193                         open.insert(0, i)
194 
195                         open.sort(key=lambda x: x.distance + x.cost)  # 按曼哈顿距离+cost 进行排序
196 
197     print("=====================================================================================")
198 
199     print("=====================================================================================")
200 
201     print("=====================================================================================")
202 
203     search_line(close)
204 
205     print('搜索步数为:', len(close) - 1, '总代价为:', close[-1].cost + close[-1].distance)
206 
207 # 展示最优路径
208 
209 def best_path(n):
210 
211     if n.parent==None:
212 
213         return
214 
215     else:
216 
217         print("^")
218 
219         print("|")
220 
221         print("|")
222 
223         print(np.array(n.parent.node, dtype=object))
224 
225         best_path(n.parent)
226 
227 def search_line(close):
228 
229     print('搜索路径如下:')
230 
231     for i in close[:-1]:
232 
233         print(np.array(i.node,dtype=object))
234 
235         print("|")
236 
237         print("|")
238 
239         print("V")
240 
241     print(np.array(close[-1].node,dtype=object))
242 
243  
244 
245 if __name__=='__main__':
246 
247     print(
248 
249         """
250 
251         在一个在一个3*3的方棋盘上放置着1,2,3,4,5,6,7,8八个数码,每个数码占一格,且有一个空格。
252 
253         这些数码可以在棋盘上移动,其移动规则是:与空格相邻的数码方格可以移入空格。
254 
255         现在的问题是:对于指定的初始棋局和目标棋局,给出数码的移动序列。
256 
257         3*3的方棋盘上放置着1,2,3,4,5,6,7,8八个数码,每个数码占一格,且有一个空格。
258 
259         这些数码可以在棋盘上移动,其移动规则是:与空格相邻的数码方格可以移入空格。
260 
261         现在的问题是:对于指定的初始棋局和目标棋局,给出数码的移动序列。
262 
263         输入样例:
264 
265         输入序列:283 104 765
266 
267         输入目的序列:123 804 765
268 
269         """
270 
271     )
272 
273     initStr = input("输入序列:")
274 
275     endStr = input("输入目的序列:")
276 
277     if isSolution(initStr, endStr):
278 
279         init_arr = string_to_list(initStr)
280 
281         goal_arr = string_to_list(endStr)
282 
283         inital_arr = Node(init_arr, parent=None, cost=0,distance=mhdDistance(init_arr,goal_arr))
284 
285         AStar_MHT(inital_arr, goal_arr)
286 
287     else:
288 
289         print("此初始序列到不了目的序列,问题无解!")
290 
291     input("please input any key to exit!")

4.运行结果截图

 

 

 

5.设计中遇到的问题与解决          

设计之中遇到的问题有一下几个方面:

  1. 对启发式规则不太熟悉,解决问题的方案一时间不知道如何下手
  2. 在确定解决方法时代码功底不够,经常要将前面的翻到从来
  3. 程序出现BUG时,对程序查找错误迅速定位不够精炼。
  4. 将程序打包过程出现的解码错误使得.exe文件生成出来无法运行

解决过程:

我利用各大互联网开源平台(B站、CSDN、github等)先是仔细研习了启发式规则的相关知识点重新温习了相关思想,在具体落地实现上首先学会看懂他人的代码,在仔细琢磨其中的流程是否和自己理解的算法思想一致,是否流程上相一致。在具体到写代码时,是否对一些细节上的操作疏忽了导致程序并没有达到我们想要的预期效果,利用开源资源来一步步实现从无到实现出来。在对程序报错方面,我利用分割出不同的模块将错误不断定位更小的空间,直到查找到最终的错误所在。对程序进行打包也同样去阅读相应的函数开发文档找到参数的使用不同点以及能带来的情况变换是什么,对症下药调整配置文件将问题解决。

6.作品设计感受

通过这份期末大作业的整个完成过程,我对解决问题上的相关流程以及对问题的科学思考、理性分析和问题的转换都有了一定的锻炼。八数码问题上转换为图论相关求解,利用启发式规则来进行求解。首先是要想到解决方案,再而是将方案落地,用编程语言来实现,这开始考验的是你的代码能力,最后得出结果再进行验证是否可行,准确性如何,一切都可行之后才算是初步落地成功。虽然上面大体步骤就短短几句话,但是考查的是一个人综合能力,逻辑思维如何,代码功底怎么样,利用互联网查找资料的能力强不强等。出现问题无法解决时,心态稳定也是一个重要的因素,有一个稳定的心态是能解决一切的钥匙,要沉下心来去找到问题出错的答案和知识点,如果心态不好,也许会把问题越弄越糟糕。

最后特别感谢那些开源工作者们以及知识经验分享者们,也让我意识到构建一个完好的开源生态的有利之处,我以后也会积极成为贡献者之一,同样也是受益者,让知识没有围墙,让感兴趣的人都能够找到资源提升自己,看到这里的小伙伴必然能是未来计算机界中流砥柱(苗子),让我们共勉!

标签:node,arr,python,空格,曼哈顿,数码,print,节点
From: https://www.cnblogs.com/sangGou/p/17019407.html

相关文章

  • Python3 学习~
    Python3heapq#默认小根堆Heap=[]#初始化为空heapq.heapify(list)#将一个list原地转换为堆,线性时间heapq.heappush(Heap,item)#插入一个元素item,类型随意x......
  • Python之路【第九篇】:Python操作 RabbitMQ、Redis、Memcache、SQLAlchemy
    1.MemcachedMemcached是一个高性能的分布式内存对象缓存系统,用于动态Web应用以减轻数据库负载。它通过在内存中缓存数据和对象来减少读取数据库的次数,从而提高动态、......
  • python设计模式
    设计模式设计模式中使用了一个接口类abc:AbstractBaseClasses作用:在代码中定义和使用抽象基类进行API检查。​为什么使用abc模块Abstractbaseclasses由一组接口组......
  • Python__20-- 常见Bug
    1Bug一杯茶,一包烟,一个Bug改一天程序错误,即英文的Bug,也称为缺陷、臭虫,是指在软件运行中因为程序本身有错误而造成的功能不正常、死机、数据丢失、非正常中断等现象。早期的......
  • python 异常处理
    目录python异常处理异常机制本质try...except...try...except...except...try...except...elsetry...except...else...finally常见异常汇总和说明return语句和异常处理问......
  • python学习总结
    1、编码相关文件存储时,使用某种编码,打开时就需要使用相同的编码,否则就会乱码字符底层存储时,本质上都是01010二进制编码字符和二进制的对应关系(编码):ascii编码gb2312,g......
  • Django视频教程 - 基于Python的Web框架(全13集)
    Django是由Python驱动的开源模型-视图-控制器(MVC)风格的Web应用程序框架,使用Django可以在即可分钟内快速开发一个高品质易维护数据库驱动的应用程序。下面是一大坨关于Django......
  • Python中的容器类型(1)
    Python中的容器类型(第1篇)python中的容器类型包含字符串(str),元组(tuple),列表(list),集合(set)等类型。接下来是对它们的介绍。  1.序列。  序列(sequence),是......
  • Python之路【第八篇】:堡垒机实例以及数据库操作
    1.堡垒机前戏开发堡垒机之前,先来学习Python的paramiko模块,该模块机遇SSH用于连接远程服务器并执行相关操作 1.1SSHClient用于连接远程服务器并执行基本命令......
  • 使用python操作数据库
    实例1、创建SQLite数据库文件importsqlite3conn=sqlite3.connect('mrsoft.db')cursor=conn.cursor()cursor.execute('createtableuser(idint(10)primarykey,nameva......