实验二 A算法求解八数码问题实验

实验二 A*算法求解八数码问题实验

一、实验目的:

熟悉和掌握启发式搜索的定义、估价函数和算法过程,并利用A*算法求解N数码难题,理解求解流程和搜索顺序。

二、 实验内容

以8数码问题和15数码问题为例实现A*算法的求解程序(编程语言不限,如Python等),要求设计两种不同的估价函数。

三、实验要求

1.设置相同的初始状态和目标状态,针对不同的估价函数,求得问题的解,比较它们对搜索算法性能的影响,包括扩展节点数、生成节点数等,填入表3。

2.设置与上述1相同的初始状态和目标状态,用宽度优先搜索算法(即令估计代价h(n)=0的A*算法)求得问题的解,以及搜索过程中的扩展节点数、生成节点数,填入表3。

3.实现A*算法求解15数码问题的程序,设计两种不同的估价函数,然后重复上述1和2的实验内容,把结果填入表4。

四、实验结果与分析

(1)设置相同的初始状态和目标状态,针对不同的估价函数,求得问题的解,比较它们对搜索算法性能的影响,包括扩展节点数、生成节点数等,填入表3。

表3 不同启发函数h(n)求解8数码问题的结果比较
启发函数h(n)
不在位数不在位棋子的曼哈顿距离之和0
初始状态283064175283064175
目标状态123804765123804765
最优解第一层:
2 8 3
1 0 4
7 6 5
F= 4 G= 0 H= 4
第二层:
2 0 3
1 8 4
7 6 5
F= 5 G= 1 H= 4
第三层:
0 2 3/
1 8 4
7 6 5
F= 6 G= 2 H= 4
第四层:
1 2 3
0 8 4
7 6 5
F= 5 G= 3 H= 2
第五层:
1 2 3
8 0 4
7 6 5
F= 4 G= 4 H= 0
depth: 0
[2, 8, 3]
[1, 0, 4]
[7, 6, 5]
----------
depth: 1
[2, 0, 3]
[1, 8, 4]
[7, 6, 5]
----------
depth: 2
[0, 2, 3]
[1, 8, 4]
[7, 6, 5]
----------
depth: 3
[1, 2, 3]
[0, 8, 4]
[7, 6, 5]
----------
depth: 4
[1, 2, 3]
[8, 0, 4]
[7, 6, 5]
----------
扩展节点数427
生成节点数55
运行时间114.9ms2.05ms

估价函数(曼哈顿距离):

#G是深度,也就是走的步数
    def fG(self):
        if(self.pre!=None):
            self.G=self.pre.G+1
        else:
            self.G=0

    #H是和目标状态距离之和
    def fH(self):
        self.H=0
        for i in range(3):
            for j in range(3):
                targetX=self.target[i][j]
                nowP=self.findx(targetX)
                #曼哈顿距离之和
                self.H+=abs(nowP[0]-i)+abs(nowP[1]-j)

    #F是启发函数,F=G+H
    def fF(self):
        self.F=self.G+self.H

运行结果如下:

A*曼哈顿八数码

2.设置与上述1相同的初始状态和目标状态,用宽度优先搜索算法(即令估计代价h(n)=0的A*算法)求得问题的解,以及搜索过程中的扩展节点数、生成节点数,填入表3。

估价函数:

    # G是深度,也就是走的步数
    def fG(self):
        if self.pre != None:
            self.G = self.pre.G + 1
        else:
            self.G = 0

    # H是和目标状态距离之和,可以用来判断是否达到最优解
    def fH(self):
        self.H = 0
        for i in range(3):
            for j in range(3):
                targetX = self.target[i][j]
                nowP = self.findx(targetX)
                self.H += abs(nowP[0] - i) + abs(nowP[1] - j)

宽度优先算法运行结果:

BFS八数码

3.实现A*算法求解15数码问题的程序,设计两种不同的估价函数,然后重复上述1和2的实验内容,把结果填入表4。

15数码A*算法的估价函数(曼哈顿距离):

def cal_M_distence(cur_state):
    '''
    计算曼哈顿距离
    :参数 state: 当前状态,4*4的列表, State.state
    :返回: M_cost 每一个节点计算后的曼哈顿距离总和
    '''
    M_cost = 0
    for i in range(4):
        for j in range(4):
            if cur_state[i][j] == SG[i][j]:
                continue
            num = cur_state[i][j]
            if num == 0:
                x, y = 3, 3
            else:
                x = num / 4  # 理论横坐标
                y = num - 4 * x - 1  # 理论的纵坐标
                M_cost += (abs(x - i) + abs(y - j))
    return M_cost

运行结果:

A*15数码

15数码BFS估价函数:


def generate_child(sn_node, sg_node, hash_set, open_table):
    '''
    生成子节点函数
    :参数 sn_node:  当前节点
    :参数 sg_node:  最终状态节点
    :参数 hash_set:  哈希表,用于判重
    :参数 open_table: OPEN表
    :返回: None
    '''
    if sn_node == sg_node:
        heapq.heappush(open_table, sg_node)
        print('已找到终止状态!')
        return
    for i in range(0, 4):
        for j in range(0, 4):
            if sn_node.state[i][j] != 0:
                continue
            for d in ['up', 'down', 'left', 'right']:  # 四个偏移方向
                x = i + MOVE[d][0]
                y = j + MOVE[d][1]
                if x < 0 or x >= 4 or y < 0 or y >= 4:  # 越界了
                    continue
                state = copy.deepcopy(sn_node.state)  # 复制父节点的状态
                state[i][j], state[x][y] = state[x][y], state[i][j]  # 交换位置
                h = hash(str(state))  # 哈希时要先转换成字符串
                if h in hash_set:  # 重复了
                    continue
                hash_set.add(h)  # 加入哈希表

                # 记录扩展节点的个数
                global SUM_NODE_NUM
                SUM_NODE_NUM += 1

                deepth = sn_node.deepth + 1  # 已经走的距离函数
                node = State(deepth, state, h, sn_node)  # 新建节点
                sn_node.child.append(node)  # 加入到孩子队列
                heapq.heappush(open_table, node)  # 加入到堆中

                # show_block(state, deepth) # 打印每一步的搜索过程

运行结果:

BFS15数码

表4 不同启发函数h(n)求解15数码问题的结果比较
启发函数h(n)
不在位数不在位数(曼哈顿距离)0
初始状态[[5, 1, 2, 4],
[9, 6, 3, 8],
[13, 15, 10, 11],
[0, 14, 7, 12]]
[[5, 1, 2, 4],
[9, 6, 3, 8],
[13, 15, 10, 11],
[0, 14, 7, 12]]
目标状态[[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12],
[13, 14, 15, 0]]
[[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12],
[13, 14, 15, 0]]
最优解------ 0 --------
[5, 1, 2, 4]
[9, 6, 3, 8]
[13, 15, 10, 11]
[0, 14, 7, 12]
------ 1 --------
[5, 1, 2, 4]
[9, 6, 3, 8]
[0, 15, 10, 11]
[13, 14, 7, 12]
------ 2 --------
[5, 1, 2, 4]
[0, 6, 3, 8]
[9, 15, 10, 11]
[13, 14, 7, 12]
------ 3 --------
[0, 1, 2, 4]
[5, 6, 3, 8]
[9, 15, 10, 11]
[13, 14, 7, 12]
------ 4 --------
[1, 0, 2, 4]
[5, 6, 3, 8]
[9, 15, 10, 11]
[13, 14, 7, 12]
------ 5 --------
[1, 2, 0, 4]
[5, 6, 3, 8]
[9, 15, 10, 11]
[13, 14, 7, 12]
------ 6 --------
[1, 2, 3, 4]
[5, 6, 0, 8]
[9, 15, 10, 11]
[13, 14, 7, 12]
------ 7 --------
[1, 2, 3, 4]
[5, 0, 6, 8]
[9, 15, 10, 11]
[13, 14, 7, 12]
------ 8 --------
[1, 2, 3, 4]
[5, 15, 6, 8]
[9, 0, 10, 11]
[13, 14, 7, 12]
------ 9 --------
[1, 2, 3, 4]
[5, 15, 6, 8]
[9, 10, 0, 11]
[13, 14, 7, 12]
------ 10 --------
[1, 2, 3, 4]
[5, 15, 6, 8]
[9, 10, 7, 11]
[13, 14, 0, 12]
------ 11 --------
[1, 2, 3, 4]
[5, 15, 6, 8]
[9, 10, 7, 11]
[13, 0, 14, 12]
------ 12 --------
[1, 2, 3, 4]
[5, 15, 6, 8]
[9, 0, 7, 11]
[13, 10, 14, 12]
------ 13 --------
[1, 2, 3, 4]
[5, 0, 6, 8]
[9, 15, 7, 11]
[13, 10, 14, 12]
------ 14 --------
[1, 2, 3, 4]
[5, 6, 0, 8]
[9, 15, 7, 11]
[13, 10, 14, 12]
------ 15 --------
[1, 2, 3, 4]
[5, 6, 7, 8]
[9, 15, 0, 11]
[13, 10, 14, 12]
------ 16 --------
[1, 2, 3, 4]
[5, 6, 7, 8]
[9, 0, 15, 11]
[13, 10, 14, 12]
------ 17 --------
[1, 2, 3, 4]
[5, 6, 7, 8]
[9, 10, 15, 11]
[13, 0, 14, 12]
------ 18 --------
[1, 2, 3, 4]
[5, 6, 7, 8]
[9, 10, 15, 11]
[13, 14, 0, 12]
------ 19 --------
[1, 2, 3, 4]
[5, 6, 7, 8]
[9, 10, 0, 11]
[13, 14, 15, 12]
------ 20 --------
[1, 2, 3, 4]
[5, 6, 7, 8]
[9, 10, 11, 0]
[13, 14, 15, 12]
------ 21 --------
[1, 2, 3, 4]
[5, 6, 7, 8]
[9, 10, 11, 12]
[13, 14, 15, 0]
------ 0 --------
[5, 1, 2, 4]
[9, 6, 3, 8]
[13, 15, 10, 11]
[0, 14, 7, 12]
------ 1 --------
[5, 1, 2, 4]
[9, 6, 3, 8]
[13, 15, 10, 11]
[14, 0, 7, 12]
------ 2 --------
[5, 1, 2, 4]
[9, 6, 3, 8]
[13, 0, 10, 11]
[14, 15, 7, 12]
------ 3 --------
[5, 1, 2, 4]
[9, 6, 3, 8]
[13, 10, 0, 11]
[14, 15, 7, 12]
------ 4 --------
[5, 1, 2, 4]
[9, 6, 3, 8]
[13, 10, 7, 11]
[14, 15, 0, 12]
------ 5 --------
[5, 1, 2, 4]
[9, 6, 3, 8]
[13, 10, 7, 11]
[14, 0, 15, 12]
------ 6 --------
[5, 1, 2, 4]
[9, 6, 3, 8]
[13, 10, 7, 11]
[0, 14, 15, 12]
------ 7 --------
[5, 1, 2, 4]
[9, 6, 3, 8]
[0, 10, 7, 11]
[13, 14, 15, 12]
------ 8 --------
[5, 1, 2, 4]
[0, 6, 3, 8]
[9, 10, 7, 11]
[13, 14, 15, 12]
------ 9 --------
[0, 1, 2, 4]
[5, 6, 3, 8]
[9, 10, 7, 11]
[13, 14, 15, 12]
------ 10 --------
[1, 0, 2, 4]
[5, 6, 3, 8]
[9, 10, 7, 11]
[13, 14, 15, 12]
------ 11 --------
[1, 2, 0, 4]
[5, 6, 3, 8]
[9, 10, 7, 11]
[13, 14, 15, 12]
------ 12 --------
[1, 2, 3, 4]
[5, 6, 0, 8]
[9, 10, 7, 11]
[13, 14, 15, 12]
------ 13 --------
[1, 2, 3, 4]
[5, 6, 7, 8]
[9, 10, 0, 11]
[13, 14, 15, 12]
------ 14 --------
[1, 2, 3, 4]
[5, 6, 7, 8]
[9, 10, 11, 0]
[13, 14, 15, 12]
------ 15 --------
[1, 2, 3, 4]
[5, 6, 7, 8]
[9, 10, 11, 12]
[13, 14, 15, 0]
扩展节点数2117195811
生成节点数2115
运行时间37.63ms3.49s

五、实验总结

(1)分析不同的估价函数对A*算法性能的影响。

  1. 低质量估价函数:

    如果h(n)的值比较低(接近于 0),则 A* 算法会表现得像广度优先搜索,这会导致大量节点被扩展,从而效率低下。

  2. 良好估价函数:

    如果h(n)估计相对准确,使得 A* 算法能够快速接近目标,扩展的节点数会明显减少,从而提高效率。

  3. 高成本估价函数:

    如果h(n)的值高于目标状态到节点任务的实际成本(即函数不符合可接受性),则算法仍然能找到最优解,但可能扩展更多的节点,导致计算成本上升。

(2)根据宽度优先搜索算法和A*算法求解8、15数码问题的结果,分析启发式搜索的特点。

  1. A* 算法通常比 BFS 更高效,因为它优先扩展那些更有可能接近目标的节点。对于 8 数码和 15 数码问题,A* 使用合适的启发式函数(曼哈顿距离),会显著减少扩展的节点数量。

  2. BFS 将需要访问大量的无用节点,尤其是在树的深度较大时,导致搜索效率低下。

  3. 启发式搜索的特点

    启发式搜索依赖于问题的特殊性质,能够快速引导搜索。设计良好的启发式函数在很多情况下可以显著提高效率。

    启发式搜索对不同问题能进行适配,可以通过不同的启发式函数适应多种情况。

    通过选择具体的启发式函数,开发者可以控制算法的搜索策略,达到平衡效率和解质量的目的。

    尽管启发式搜索能高效进入目标区域,但启发式函数的计算也会产生开销,尤其在状态数目庞大时,如何优化估价函数的计算成为关键问题。

(3)画出A*算法求解N数码问题的流程图。

![流程图](https://i-blog.csdnimg.cn/direct/f372143cc7f84493885471a66c45b83b.png#pic_center)
流程图

(4)遇到的问题和解决方法

  1. 问题1:在设计启发函数时,如果选择的函数计算复杂度较高或者不够准确,可能导致搜索效率非常低下,甚至无法找到可行的解决方案。
    解决方法:对于 8 数码问题和 15 数码问题,常见的有效启发函数包括“曼哈顿距离”和“汉明距离”。我需要评估这些启发函数的特性,选择更适合的函数。
  2. 问题2:在实现 A* 算法时,可能会遇到逻辑错误,特别是在优先队列的管理和节点的添加、更新方面。条件判断错误可能导致程序无法找到正确的路径。
    解决方法:在实际实现过程中,我采取逐步调试的方法,使用 print 语句输出关键变量的值,包括当前节点的 f(n)、g(n) 和 h(n) 值。这些输出帮助我快速定位问题所在。

(5)总结实验心得体会。

  1. 通过本次实验,我深刻感受到启发式搜索在路径寻找和优化问题上的强大能力。使用适当的启发式函数可以明显提高搜索效率,尤其在面对复杂的状态空间时。

  2. 估价函数的合理设计直接影响到 A* 算法的性能。我的实验结果表明,不同的启发函数可以在扩展节点数和生成节点数上有显著差异,进而影响到运行时间。

Astart曼哈顿8数码

import copy
import time


class grid:
    def __init__(self, stat):
        self.pre = None
        self.target = [[1, 2, 3], [8, 0, 4], [7, 6, 5]]
        self.stat = stat
        self.find0()
        self.update()

    def update(self):
        self.fH()
        self.fG()
        self.fF()

    def fG(self):
        if self.pre is not None:
            self.G = self.pre.G + 1
        else:
            self.G = 0

    def fH(self):
        self.H = 0
        for i in range(3):
            for j in range(3):
                targetX = self.target[i][j]
                nowP = self.findx(targetX)
                self.H += abs(nowP[0] - i) + abs(nowP[1] - j)

    def fF(self):
        self.F = self.G + self.H

    def see(self):
        for i in range(3):
            print(self.stat[i])
        print("F=", self.F, "G=", self.G, "H=", self.H)
        print("-" * 10)

    def seeAns(self):
        ans = []
        ans.append(self)
        p = self.pre
        while p:
            ans.append(p)
            p = p.pre
        ans.reverse()
        for i in ans:
            i.see()

    def findx(self, x):
        for i in range(3):
            if x in self.stat[i]:
                j = self.stat[i].index(x)
                return [i, j]

    def find0(self):
        self.zero = self.findx(0)

    def expand(self):
        i = self.zero[0]
        j = self.zero[1]
        gridList = []

        # 检查上下左右的移动
        if i > 0:  # 上移
            gridList.append(self.up())
        if i < 2:  # 下移
            gridList.append(self.down())
        if j > 0:  # 左移
            gridList.append(self.left())
        if j < 2:  # 右移
            gridList.append(self.right())

        return gridList

    def move(self, row, col):
        newStat = copy.deepcopy(self.stat)
        # 确保不会越界
        if (0 <= self.zero[0] + row < 3) and (0 <= self.zero[1] + col < 3):
            # 进行移动
            newStat[self.zero[0]][self.zero[1]] = newStat[self.zero[0] + row][self.zero[1] + col]
            newStat[self.zero[0] + row][self.zero[1] + col] = 0
        return newStat

    def up(self):
        return self.move(-1, 0)

    def down(self):
        return self.move(1, 0)

    def left(self):
        return self.move(0, -1)

    def right(self):
        return self.move(0, 1)


def isin(g, gList):
    gstat = g.stat
    statList = [i.stat for i in gList]
    if gstat in statList:
        return [True, statList.index(gstat)]
    else:
        return [False, 0]


def N(nums):
    N = 0
    for i in range(len(nums)):
        if nums[i] != 0:
            for j in range(i):
                if nums[j] > nums[i]:
                    N += 1
    return N


def judge(src, target):
    N1 = N([num for row in src for num in row])
    N2 = N([num for row in target for num in row])
    return (N1 % 2) == (N2 % 2)


def Astar(startStat):
    start_time = time.time()

    open = []
    closed = []

    g = grid(startStat)

    if not judge(startStat, g.target):
        print("所给八数码无解,请检查输入")
        exit(1)

    open.append(g)
    time_taken = 0

    while open:
        open.sort(key=lambda G: G.F)
        minFStat = open[0]

        if minFStat.H == 0:
            print("扩展节点数:", time_taken, "最佳移动距离:", minFStat.G)
            minFStat.seeAns()
            break

        open.pop(0)
        closed.append(minFStat)
        expandStats = minFStat.expand()

        for stat in expandStats:
            tempG = grid(stat)
            tempG.pre = minFStat
            tempG.update()

            findstat = isin(tempG, open)
            findstat2 = isin(tempG, closed)

            if findstat2[0] and tempG.F < closed[findstat2[1]].F:
                closed[findstat2[1]] = tempG
                open.append(tempG)
                time_taken += 1

            if findstat[0] and tempG.F < open[findstat[1]].F:
                open[findstat[1]] = tempG
                time_taken += 1

            if not findstat[0] and not findstat2[0]:
                open.append(tempG)
                time_taken += 1

    end_time = time.time()
    # 转换为毫秒输出
    elapsed_time_ms = (end_time - start_time) * 1000
    print("运行时间: {:.16f} 毫秒".format(elapsed_time_ms))


# 示例八数码初始状态
initial_stat = [[2, 8, 3], [1, 0, 4], [7, 6, 5]]
Astar(initial_stat)

Astart15数码

import heapq
import copy
import time
import math
import argparse

# 初始状态
# S0 = [[11, 9, 4, 15],
#       [1, 3, 0, 12],
#       [7, 5, 8, 6],
#       [13, 2, 10, 14]]
S0 = [[5, 1, 2, 4],
      [9, 6, 3, 8],
      [13, 15, 10, 11],
      [0, 14, 7, 12]]

# 目标状态
SG = [[1, 2, 3, 4],
      [5, 6, 7, 8],
      [9, 10, 11, 12],
      [13, 14, 15, 0]]

# 上下左右四个方向移动
MOVE = {'up': [1, 0],
        'down': [-1, 0],
        'left': [0, -1],
        'right': [0, 1]}

# OPEN表
OPEN = []

# 节点的总数
SUM_NODE_NUM = 0

# 状态节点
class State(object):
    def __init__(self, deepth=0, rest_dis=0.0, state=None, hash_value=None, father_node=None):
        '''
        初始化
        :参数 deepth: 从初始节点到目前节点所经过的步数
        :参数 rest_dis: 启发距离
        :参数 state: 节点存储的状态 4*4的列表
        :参数 hash_value: 哈希值,用于判重
        :参数 father_node: 父节点指针
        '''
        self.deepth = deepth
        self.rest_dis = rest_dis
        self.fn = self.deepth + self.rest_dis
        self.child = []  # 孩子节点
        self.father_node = father_node  # 父节点
        self.state = state  # 局面状态
        self.hash_value = hash_value  # 哈希值

    def __lt__(self, other):  # 用于堆的比较,返回距离最小的
        return self.fn < other.fn

    def __eq__(self, other):  # 相等的判断
        return self.hash_value == other.hash_value

    def __ne__(self, other):  # 不等的判断
        return not self.__eq__(other)


def cal_M_distence(cur_state):
    '''
    计算曼哈顿距离
    :参数 state: 当前状态,4*4的列表, State.state
    :返回: M_cost 每一个节点计算后的曼哈顿距离总和
    '''
    M_cost = 0
    for i in range(4):
        for j in range(4):
            if cur_state[i][j] == SG[i][j]:
                continue
            num = cur_state[i][j]
            if num == 0:
                x, y = 3, 3
            else:
                x = num / 4  # 理论横坐标
                y = num - 4 * x - 1  # 理论的纵坐标
                M_cost += (abs(x - i) + abs(y - j))
    return M_cost

def cal_E_distence(cur_state):
    '''
    计算欧式距离
    :参数 state: 当前状态,4*4的列表, State.state
    :返回: M_cost 每一个节点计算后的曼哈顿距离总和
    '''
    E_cost = 0
    for i in range(4):
        for j in range(4):
            if cur_state[i][j] == SG[i][j]:
                continue
            num = cur_state[i][j]
            if num == 0:
                x, y = 3, 3
            else:
                x = num / 4  # 理论横坐标
                y = num - 4 * x - 1  # 理论的纵坐标
                E_cost += math.sqrt((x - i)*(x - i) + (y - j)*(y - j))
    return E_cost

def generate_child(sn_node, sg_node, hash_set, open_table, cal_distence):
    '''
    生成子节点函数
    :参数 sn_node:  当前节点
    :参数 sg_node:  最终状态节点
    :参数 hash_set:  哈希表,用于判重
    :参数 open_table: OPEN表
    :参数 cal_distence: 距离函数
    :返回: None
    '''
    if sn_node == sg_node:
        heapq.heappush(open_table, sg_node)
        print('已找到终止状态!')
        return
    for i in range(0, 4):
        for j in range(0, 4):
            if sn_node.state[i][j] != 0:
                continue
            for d in ['up', 'down', 'left', 'right']:  # 四个偏移方向
                x = i + MOVE[d][0]
                y = j + MOVE[d][1]
                if x < 0 or x >= 4 or y < 0 or y >= 4:  # 越界了
                    continue
                state = copy.deepcopy(sn_node.state)  # 复制父节点的状态
                state[i][j], state[x][y] = state[x][y], state[i][j]  # 交换位置
                h = hash(str(state))  # 哈希时要先转换成字符串
                if h in hash_set:  # 重复了
                    continue
                hash_set.add(h)  # 加入哈希表

                # 记录扩展节点的个数
                global SUM_NODE_NUM
                SUM_NODE_NUM += 1

                deepth = sn_node.deepth + 1  # 已经走的距离函数
                rest_dis = cal_distence(state)  # 启发的距离函数
                node = State(deepth, rest_dis, state, h, sn_node)  # 新建节点
                sn_node.child.append(node)  # 加入到孩子队列
                heapq.heappush(open_table, node)  # 加入到堆中

                # show_block(state, deepth) # 打印每一步的搜索过程


def show_block(block, step):
    print("------", step, "--------")
    for b in block:
        print(b)

def print_path(node):
    '''
    输出路径
    :参数 node: 最终的节点
    :返回: None
    '''
    print("最终搜索路径为:")
    steps = node.deepth

    stack = []  # 模拟栈
    while node.father_node is not None:
        stack.append(node.state)
        node = node.father_node
    stack.append(node.state)
    step = 0
    while len(stack) != 0:
        t = stack.pop()
        show_block(t, step)
        step += 1
    return steps


def A_start(start, end, distance_fn, generate_child_fn):
    '''
    A*算法
    :参数 start: 起始状态
    :参数 end: 终止状态
    :参数 distance_fn: 距离函数,可以使用自定义的
    :参数 generate_child_fn: 产生孩子节点的函数
    :返回: 最优路径长度
    '''
    root = State(0, 0, start, hash(str(S0)), None)  # 根节点
    end_state = State(0, 0, end, hash(str(SG)), 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)
    while len(OPEN) != 0:
        top = heapq.heappop(OPEN)
        if top == end_state:  # 结束后直接输出路径
            return print_path(top)
        # 产生孩子节点,孩子节点加入OPEN表
        generate_child_fn(sn_node=top, sg_node=end_state, hash_set=node_hash_set,
                          open_table=OPEN, cal_distence=distance_fn)
    print("无搜索路径!")  # 没有路径
    return -1
if __name__ == '__main__':
    time1 = time.time()  # 开始计时
    length = A_start(S0, SG, cal_M_distence, generate_child)  # 使用曼哈顿距离
    time2 = time.time()  # 结束计时

    if length != -1:
        print("采用曼哈顿距离计算启发函数")
        print("搜索最优路径长度为", length)
        elapsed_time_ms = (time2 - time1) * 1000  # 转换为毫秒
        print("搜索时长为 {:.2f} 毫秒".format(elapsed_time_ms))  # 格式化为小数点后两位
        print("共检测节点数为", SUM_NODE_NUM)  # 输出检测节点数
    else:
        print("无搜索路径,检测节点数为", SUM_NODE_NUM)  # 当没有路径时也输出节点数
# if __name__ == '__main__':
#
#     # 可配置式运行文件
#     parser = argparse.ArgumentParser(description='选择距离计算方法')
#     parser.add_argument('--method', '-m', help='method 选择距离计算方法(cal_E_distence or cal_M_distence)', default = 'cal_M_distence')
#     args = parser.parse_args()
#     method = args.method
#
#     time1 = time.time()
#     if method == 'cal_E_distence':
#         length = A_start(S0, SG, cal_E_distence, generate_child)
#     else:
#         length = A_start(S0, SG, cal_M_distence, generate_child)
#     time2 = time.time()
#     if length != -1:
#         if method == 'cal_E_distence':
#             print("采用欧式距离计算启发函数")
#         else:
#             print("采用曼哈顿距离计算启发函数")
#         print("搜索最优路径长度为", length)
#         print("搜索时长为", (time2 - time1), "s")
#         print("共检测节点数为", SUM_NODE_NUM)



BFS8数码

import copy
import time

# 棋盘的类,实现移动和扩展状态
class grid:
    def __init__(self, stat):
        self.pre = None
        self.target = [[1, 2, 3], [8, 0, 4], [7, 6, 5]]
        self.stat = stat
        self.find0()
        self.update()

    # 更新深度和距离和
    def update(self):
        self.fH()
        self.fG()

    # G是深度,也就是走的步数
    def fG(self):
        if self.pre != None:
            self.G = self.pre.G + 1
        else:
            self.G = 0

    # H是和目标状态距离之和,可以用来判断是否达到最优解
    def fH(self):
        self.H = 0
        for i in range(3):
            for j in range(3):
                targetX = self.target[i][j]
                nowP = self.findx(targetX)
                self.H += abs(nowP[0] - i) + abs(nowP[1] - j)

    # 查看当前状态
    def see(self):
        print("depth:", self.G)
        for i in range(3):
            print(self.stat[i])
        print("-" * 10)

    # 查看找到的解是如何从头移动的
    def seeAns(self):
        ans = []
        ans.append(self)
        p = self.pre
        while p:
            ans.append(p)
            p = p.pre
        ans.reverse()
        for i in ans:
            i.see()

    # 找到数字x的位置,返回其坐标
    def findx(self, x):
        for i in range(3):
            if(x in self.stat[i]):
                j = self.stat[i].index(x)
                return [i, j]

    # 找到0,也就是空白格的位置
    def find0(self):
        self.zero = self.findx(0)

    # 对当前状态进行所有可能的扩展,返回一个扩展状态的列表
    def expand(self):
        i = self.zero[0]
        j = self.zero[1]
        gridList = []

        if(j == 2 or j == 1):
            gridList.append(self.left())
        if(i == 2 or i == 1):
            gridList.append(self.up())
        if(i == 0 or i == 1):
            gridList.append(self.down())
        if(j == 0 or j == 1):
            gridList.append(self.right())

        return gridList

    # deepcopy多维列表的复制,防止指针赋值将原列表改变
    # move只能移动行或列,即row和col必有一个为0
    # 对当前状态进行移动
    def move(self, row, col):
        newStat = copy.deepcopy(self.stat)
        tmp = self.stat[self.zero[0]+row][self.zero[1]+col]
        newStat[self.zero[0]][self.zero[1]] = tmp
        newStat[self.zero[0]+row][self.zero[1]+col] = 0
        return newStat

    def up(self):
        return self.move(-1, 0)

    def down(self):
        return self.move(1, 0)

    def left(self):
        return self.move(0, -1)

    def right(self):
        return self.move(0, 1)

# 计算逆序数之和
def N(nums):
    N = 0
    for i in range(len(nums)):
        if(nums[i] != 0):
            for j in range(i):
                if(nums[j] > nums[i]):
                    N += 1
    return N

# 根据逆序数之和判断所给八数码是否可解
def judge(src, target):
    N1 = N(src)
    N2 = N(target)
    return N1 % 2 == N2 % 2

# 初始化状态
startStat = [[2, 8, 3], [1, 0, 4], [7, 6, 5]]
g = grid(startStat)

if not judge(startStat, g.target):
    print("所给八数码无解,请检查输入")
    exit(1)

visited = []
queue = [g]
time_count = 0

# 记录开始时间
start_time = time.time()

while queue:
    time_count += 1
    v = queue.pop(0)
    # 判断是否找到解
    if v.H == 0:
        print("扩展节点数:", time_count, "最佳移动距离:", v.G)
        # 查看找到的解是如何从头移动的
        v.seeAns()
        break
    else:
        # 对当前状态进行扩展
        visited.append(v.stat)
        expandStats = v.expand()
        for stat in expandStats:
            tmpG = grid(stat)
            tmpG.pre = v
            tmpG.update()
            if stat not in visited:
                queue.append(tmpG)

# 记录结束时间和计算运行时间
end_time = time.time()
# 计算运行时间(毫秒)
execution_time_ms = (end_time - start_time) * 1000
print("运行时间为: {:.2f} 毫秒".format(execution_time_ms))

BFS15数码

#-*-coding:utf-8-*-

import heapq
import copy
import time

# 初始状态
# S0 = [[11, 9, 4, 15],
#       [1, 3, 0, 12],
#       [7, 5, 8, 6],
#       [13, 2, 10, 14]]
S0 = [[5, 1, 2, 4],
      [9, 6, 3, 8],
      [13, 15, 10, 11],
      [0, 14, 7, 12]]

# 目标状态
SG = [[1, 2, 3, 4],
      [5, 6, 7, 8],
      [9, 10, 11, 12],
      [13, 14, 15, 0]]

# 上下左右四个方向移动
MOVE = {'up': [1, 0],
        'down': [-1, 0],
        'left': [0, -1],
        'right': [0, 1]}

# OPEN表
OPEN = []

# 节点的总数
SUM_NODE_NUM = 0

# 状态节点
class State(object):
    def __init__(self, deepth=0, state=None, hash_value=None, father_node=None):
        '''
        初始化
        :参数 deepth: 从初始节点到目前节点所经过的步数
        :参数 state: 节点存储的状态 4*4的列表
        :参数 hash_value: 哈希值,用于判重
        :参数 father_node: 父节点指针
        '''
        self.deepth = deepth
        self.child = []  # 孩子节点
        self.father_node = father_node  # 父节点
        self.state = state  # 局面状态
        self.hash_value = hash_value  # 哈希值

    def __lt__(self, other):  # 用于堆的比较,返回距离最小的
        return self.deepth < other.deepth

    def __eq__(self, other):  # 相等的判断
        return self.hash_value == other.hash_value

    def __ne__(self, other):  # 不等的判断
        return not self.__eq__(other)


def generate_child(sn_node, sg_node, hash_set, open_table):
    '''
    生成子节点函数
    :参数 sn_node:  当前节点
    :参数 sg_node:  最终状态节点
    :参数 hash_set:  哈希表,用于判重
    :参数 open_table: OPEN表
    :返回: None
    '''
    if sn_node == sg_node:
        heapq.heappush(open_table, sg_node)
        print('已找到终止状态!')
        return
    for i in range(0, 4):
        for j in range(0, 4):
            if sn_node.state[i][j] != 0:
                continue
            for d in ['up', 'down', 'left', 'right']:  # 四个偏移方向
                x = i + MOVE[d][0]
                y = j + MOVE[d][1]
                if x < 0 or x >= 4 or y < 0 or y >= 4:  # 越界了
                    continue
                state = copy.deepcopy(sn_node.state)  # 复制父节点的状态
                state[i][j], state[x][y] = state[x][y], state[i][j]  # 交换位置
                h = hash(str(state))  # 哈希时要先转换成字符串
                if h in hash_set:  # 重复了
                    continue
                hash_set.add(h)  # 加入哈希表

                # 记录扩展节点的个数
                global SUM_NODE_NUM
                SUM_NODE_NUM += 1

                deepth = sn_node.deepth + 1  # 已经走的距离函数
                node = State(deepth, state, h, sn_node)  # 新建节点
                sn_node.child.append(node)  # 加入到孩子队列
                heapq.heappush(open_table, node)  # 加入到堆中

                # show_block(state, deepth) # 打印每一步的搜索过程


def show_block(block, step):
    print("------", step, "--------")
    for b in block:
        print(b)

def print_path(node):
    '''
    输出路径
    :参数 node: 最终的节点
    :返回: None
    '''
    print("最终搜索路径为:")
    steps = node.deepth

    stack = []  # 模拟栈
    while node.father_node is not None:
        stack.append(node.state)
        node = node.father_node
    stack.append(node.state)
    step = 0
    while len(stack) != 0:
        t = stack.pop()
        show_block(t, step)
        step += 1
    return steps


def A_start(start, end, generate_child_fn):
    '''
    A*算法
    :参数 start: 起始状态
    :参数 end: 终止状态
    :参数 generate_child_fn: 产生孩子节点的函数
    :返回: 最优路径长度
    '''
    root = State(0, start, hash(str(S0)), None)  # 根节点
    end_state = State(0, end, hash(str(SG)), 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)
    while len(OPEN) != 0:
        top = heapq.heappop(OPEN)
        if top == end_state:  # 结束后直接输出路径
            return print_path(top)
        # 产生孩子节点,孩子节点加入OPEN表
        generate_child_fn(sn_node=top, sg_node=end_state, hash_set=node_hash_set,
                          open_table=OPEN)
    print("无搜索路径!")  # 没有路径
    return -1

if __name__ == '__main__':

    time1 = time.time()
    length = A_start(S0, SG, generate_child)
    time2 = time.time()
    if length != -1:
        print("搜索最优路径长度为", length)
        print("搜索时长为", (time2 - time1), "s")
        print("共检测节点数为", SUM_NODE_NUM)



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值