八数码或十五数码问题启发式搜索python

文章目录

  • 一、运行环境
  • 二、主要内容
  • 三、算法设计思想
  • 四、代码
  • 五、结果示例
  • 总结

一、运行环境

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*算法

    • 如果open表不为空,给open里的节点按f(n)的值升序排序(定义了一个给open表排序的函数sort1())
    • 取出open表中的第一个结点n
    • 如果n为目标结点,返回该结点
    • 如果n不为目标结点,将n从open删除并放入close表
    • 遍历n的所有邻近结点m(定义了一个函数swap()用来返回交换后的结果),将邻近结点中不在close表且不在open表里的结点加入open表,将它的父结点指向n结点

    c. 从function1()返回的目标结点回溯寻找路径,并逆序输出(定义了一个函数path(),用来寻找路径)
    d.将结果打印输出
    ④算法2:f(n) = d(n) + p(n),d(n)为搜索树的深度,p(n)为每个数字达到目标状态的曼哈顿距离总和
    a. 定义一个函数p(),用来计算p(n)
    b. 定义一个函数function2(),用来描述A*算法

    • 如果open表不为空,给open里的节点按f(n)的值升序排序(定义了一个给open表排序的函数sort2())
    • 取出open表中的第一个结点n
    • 如果n为目标结点,返回该结点
    • 如果n不为目标结点,将n从open删除并放入close表
    • 遍历n的所有邻近结点m(定义了一个函数swap()用来返回交换后的结果),将邻近结点中不在close表且不在open表里的结点加入open表,将它的父结点指向n结点

    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)为每个数字达到目标状态的曼哈顿距离总和
    时,搜索效率更高。

你可能感兴趣的:(作业记录,python)