Pycharm(python3.8)
启发式搜索算法A*解决八数码或十五数码问题
(1)输入字符串,将输入的字符串转换为数组,确定初始状态start、目标状态end、数组大小size、open表opened、close表closed,将空格用数字0替代,用空格左移、上移、右移、下移实现数字位置的移动
(2)根据初始状态和目标状态的逆序数奇偶性是否相同来判断是否有解(定义了一个函数condition()计算一个数组的逆序数)
(3)
无解,输出“无解”;
有解:
① 初始化首结点(构造一个Node类,用来构造结点)
② 将首结点放入open表中
③ 算法1:f(n) = d(n) + w(n),d(n)为搜索树的深度,w(n)为放错位置的数字个数
a. 定义一个函数w(),用来计算w(n)
b. 定义一个函数function1(),用来描述A*算法
c. 从function1()返回的目标结点回溯寻找路径,并逆序输出(定义了一个函数path(),用来寻找路径)
d.将结果打印输出
④算法2:f(n) = d(n) + p(n),d(n)为搜索树的深度,p(n)为每个数字达到目标状态的曼哈顿距离总和
a. 定义一个函数p(),用来计算p(n)
b. 定义一个函数function2(),用来描述A*算法
c. 从function2()返回的目标结点回溯寻找路径,并逆序输出(定义了一个函数path(),用来寻找路径)
d.将结果打印输出
import numpy as np
import prettytable
import time
import math
#把空格用数字0表示
s = input("输入确定初始状态的数字串(空格分隔):")
e = input("输入确定目标状态的数字串(空格分隔):")
st = s.split(" ")
st1 = []
for i in st:
st1.append(int(i))
st = np.array(st1)
en = e.split(" ")
en1 = []
for i in en:
en1.append(int(i))
en = np.array(en1)
size = int(math.sqrt(len(st)))
start = st.reshape(size, size) #初始状态
end = en.reshape(size, size) #目标状态
print(start)
print(end)
opened = []
closed = []
#计算逆序数
def condition(num):
l = []
con = 0
for i in range(size):
for j in range(size):
l.append(num[i][j])
for m in range(len(l)):
for n in range(m):
if l[m] != 0 and l[m] < l[n]:
con += 1
return con
#确定0的位置,返回坐标
def find_zero(num):
num_x, num_y = np.where(num == 0)
return num_x[0], num_y[0]
#确定0与哪个方向的数字进行交换
def swap(num1, direction):
x, y = find_zero(num1)
num2 = np.copy(num1) #用来记录交换后的数组
#0左移
if direction == 'left':
if y == 0:
return num2
else:
num2[x][y] = num2[x][y-1]
num2[x][y-1] = 0
return num2
#0上移
if direction == 'up':
if x == 0:
return num2
else:
num2[x][y] = num2[x-1][y]
num2[x-1][y] = 0
return num2
#0右移
if direction == 'right':
if y == size-1:
return num2
else:
num2[x][y] = num2[x][y+1]
num2[x][y+1] = 0
return num2
#0下移
if direction == 'down':
if x == size-1:
return num2
else:
num2[x][y] = num2[x+1][y]
num2[x+1][y] = 0
return num2
#优先级的计算
#f(n) = d(n) + w(n),d(n)为搜索树的深度,w(n)为放错位置的数字个数
#计算w(n)
def w(num):
con = 0#记录个数
for i in range(size):
for j in range(size):
first_num = num[i][j]
second_num = end[i][j]
if first_num != 0 and first_num != second_num:
con += 1
return con
#f(n) = d(n) + p(n),d(n)为搜索树的深度,p(n)为每个数字达到目标状态的曼哈顿距离总和
def p(num):
list = []
for i in range(size):
for j in range(size):
for k in range(size):
for l in range(size):
if num[i][j] != 0 and num[i][j] == end[k][l]:
dx = abs(i - k)
dy = abs(j - l)
list.append(dx + dy)
distance = sum(list)
return distance
#opened表排序
def sort1():
opened.sort(key=lambda node1: node1.f1, reverse=False)
def sort2():
opened.sort(key=lambda node2: node2.f2, reverse=False)
#寻找路径
def path(node):
all_node = [node]
for i in range(node.d):
parent_node = node.parent
all_node.append(parent_node)
node = parent_node
return reversed(all_node)#可以返回一个逆序序列的迭代器
#A*算法
def function1():
while len(opened) != 0:
sort1()
node = opened[0]
if (node.arr == end).all():#Numpy对逻辑表达式判别不清楚,它可以返回False如果等号两边两个式子是数值相等,也可以返回True因为等号两边两个式子是逻辑相等。它觉得这是模棱两可的,因此放弃做判断,统一用a.any()进行或比较,或a.all()进行与比较。
return node
else:
opened.pop(0)
closed.append(node)
for action in ['left', 'up', 'right', 'down']:
next_node = swap(node.arr, action)
children_node = Node(next_node, node.d + 1, p(next_node), w(next_node), node)
if children_node not in closed and children_node not in opened:
children_node.parent = node
opened.append(children_node)
else:
print("无解")
def function2():
while len(opened) != 0:
sort2()
node = opened[0]
if (node.arr == end).all():
return node
else:
opened.pop(0)
closed.append(node)
for action in ['left', 'up', 'right', 'down']:
next_node = swap(node.arr, action)
children_node = Node(next_node, node.d + 1, p(next_node), w(next_node), node)
if children_node not in closed and children_node not in opened:
children_node.parent = node
opened.append(children_node)
else:
print("无解")
#构造节点对象
class Node:
def __init__(self, arr, d=0, pn=0, wn=0, parent=None):
self.arr = arr
self.parent = parent
self.d = d
self.p = pn
self.w = wn
self.f1 = w(arr) + d
self.f2 = p(arr) + d
if condition(start) % 2 == condition(end) % 2:
print("有解")
start_node = Node(start, 0, p(start), w(start), None)
opened.append(start_node)
time1 = time.perf_counter()
result1 = list(path(function1()))#list() 函数,将 reversed() 函数逆序返回的迭代器,直接转换成列表
tb1 = prettytable.PrettyTable()
tb1.field_names = ['深度', '状态', 'f1', 'f2']
for node in result1:
tb1.add_row([node.d, node.arr, node.f1, node.f2])
print("算法1(w)")
print(tb1)
time1_ = time.perf_counter() - time1
print("Time used:", time1_)
time2 = time.perf_counter()
result2 = list(path(function2()))
tb2 = prettytable.PrettyTable()
tb2.field_names = ['深度', '状态', 'f1', 'f2']
for node in result2:
tb2.add_row([node.d, node.arr, node.f1, node.f2])
print("算法2(p)")
print(tb2)
time2_ = time.perf_counter() - time2
print("Time used:", time2_)
else:
print("无解")
输入确定初始状态的数字串(空格分隔):2 8 3 1 6 4 7 0 5
输入确定目标状态的数字串(空格分隔):1 2 3 8 0 4 7 6 5
初始状态
[[2 8 3]
[1 6 4]
[7 0 5]]
目标状态
[[1 2 3]
[8 0 4]
[7 6 5]]
有解
算法1(w)
+------+-----------+----+----+
| 深度 | 状态 | f1 | f2 |
+------+-----------+----+----+
| 0 | [[2 8 3] | 4 | 5 |
| | [1 6 4] | | |
| | [7 0 5]] | | |
| 1 | [[2 8 3] | 4 | 5 |
| | [1 0 4] | | |
| | [7 6 5]] | | |
| 2 | [[2 0 3] | 5 | 5 |
| | [1 8 4] | | |
| | [7 6 5]] | | |
| 3 | [[0 2 3] | 5 | 5 |
| | [1 8 4] | | |
| | [7 6 5]] | | |
| 4 | [[1 2 3] | 5 | 5 |
| | [0 8 4] | | |
| | [7 6 5]] | | |
| 5 | [[1 2 3] | 5 | 5 |
| | [8 0 4] | | |
| | [7 6 5]] | | |
+------+-----------+----+----+
Time used: 0.005396399999995083
算法2(p)
+------+-----------+----+----+
| 深度 | 状态 | f1 | f2 |
+------+-----------+----+----+
| 0 | [[2 8 3] | 4 | 5 |
| | [1 6 4] | | |
| | [7 0 5]] | | |
| 1 | [[2 8 3] | 4 | 5 |
| | [1 0 4] | | |
| | [7 6 5]] | | |
| 2 | [[2 0 3] | 5 | 5 |
| | [1 8 4] | | |
| | [7 6 5]] | | |
| 3 | [[0 2 3] | 5 | 5 |
| | [1 8 4] | | |
| | [7 6 5]] | | |
| 4 | [[1 2 3] | 5 | 5 |
| | [0 8 4] | | |
| | [7 6 5]] | | |
| 5 | [[1 2 3] | 5 | 5 |
| | [8 0 4] | | |
| | [7 6 5]] | | |
+------+-----------+----+----+
Time used: 0.000934299999997279
Process finished with exit code 0
对比两种算法的运行时间,无论是八数码问题还是十五数码问题,都是第二种算法所用时间较短,即
估价函数为f(n) = d(n) + p(n),d(n)为搜索树的深度,p(n)为每个数字达到目标状态的曼哈顿距离总和
时,搜索效率更高。