八数码问题(python实现)

问题的表示

在这里插入图片描述

我们使用一个二维数组arr表示一个状态空间,数组的元素含有0-8,0表示可以移动的空格,1-8表示其余待排序的方块。【一个二维数组表示八数码的每个位置的可移动方向】通过对空格0的上、下、左、右移动,得到最终的目标状态。

为实现BFS和DFS搜索算法,我们需要实现一些辅助函数:

① Cal_sameNum(self, state):传入一个状态state,返回当前节点”在位“的棋格数。

② NeedStop(self, state, stop):传入一个状态state和一个状态列表stop,若状态state与列表stop中某一状态完全一致,则返回True,意味着终止搜索。

③ Move(self):将self.arr所在状态的下一步可能移动到的状态的集合,存入一个列表Move_list并返回。

【Move函数可以用字典进行优化】

问题的解

奇偶性一致才有解

http://www.voidcn.com/article/p-gvpoulek-dd.html

DFS深度优先

通过栈stack(先进后出)进行实现。

① 起始状态放入栈中。

② 判断栈是否为空,栈空则直接无解,失败退出。

③ 显示栈顶元素的状态

④ 判断栈顶元素状态是否到达目标状态,若到达则意味着找到了最终解,未到达则继续循环操作。

⑤ 通过Move函数得到栈顶元素的下一步状态列表,然后将下一步状态依次入栈,计数+1

由于仅使用上述实现方法效率过低,甚至会进入死循环,我们将其进行优化

每次入栈前增加两个判断:

① 是否超出一个节点拓展的最大深度,如最大深度为5。

② 是否在stop列表中出现,防止出现与前面出现过的状态相同,以此进入死循环。

BFS广度优先

通过队列queue(先进先出)进行实现,其余通DFS。

UCS一致代价搜索

https://blog.csdn.net/Suyebiubiu/article/details/101194332

改进优先队列

A*启发式搜索

f(x) = g(x) + h(x)

g(x)为节点的深度。

h(x)可以有多种选择,例如“不在位”数、曼哈顿距离、欧氏距离等。

完整代码如下:

# -*- encoding: utf-8 -*-
# %%
import numpy as np
import time
import queue

# @ 判断是否有解->计算状态的逆序数,若逆序数为奇数则有解(与目标状态的逆序数同为奇数)
def HaveSolution(arr):
    numList, sum = [], 0
    for i in range(3):
        for j in range(3):
            numList.append(arr[i, j])
    for index, val in enumerate(numList):
        if index < 9:
            for i in range(index, 9):
                if val > numList[i] and numList[i] != 0:
                    sum += 1
    return sum % 2 == 1

# @ 展示到达目标的最短路径
def Display_path(res, count, node):
    result = []
    if res:
        print('经过%d次变换结束' % count)
        while node:
            result.append(node)
            node = node.parent
        result.reverse()
        for node in result:
            for i in range(3):
                for j in range(3):
                    print(node.arr[i,j], end='\t')
                print("\n")
            print('->')
    else:
        print('未找到合适路径!')
    print('----------------------------------------------')

# @ 计算"不在位"的棋个数
def Cal_diffNum(arr, target):
    pos = np.where(arr != target)
    return len(arr[pos])

# @ A*中优先队列的pop(根据"不在位"棋个数)
def pop_smallest_costNum(nodes,target):
    cost_list = []
    for node in nodes:
        cost_list.append(Cal_diffNum(node.arr,target))
    temp = nodes[cost_list.index(min(cost_list))]
    nodes.remove(nodes[cost_list.index(min(cost_list))])
    return temp

# @ 计算某个状态下每个棋子到目标位置的曼哈顿距离之和
def Cal_distance(arr, taregt):
    sum = 0
    for cols in taregt:
        for val in cols:
            i,j = np.where(arr==val)[0], np.where(arr==val)[1]
            si,sj = np.where(taregt==val)[0], np.where(taregt==val)[1]
            sum += abs(i-si)+abs(j-sj)
    return sum

# @ A*中优先队列的pop(根据曼哈顿距离之和)
def pop_smallest_costDis(nodes, target):
    cost_list = []
    for node in nodes:
        cost_list.append(Cal_distance(node.arr, target))
    temp = nodes[cost_list.index(min(cost_list))]
    nodes.remove(nodes[cost_list.index(min(cost_list))])
    return temp

# @ 计算cost
def compute_cost(depth, type, arr, target):
    if type=='DFS' or type=='BFS':
        return depth+1
    elif type=='UCS':
        return Cal_diffNum(arr, target)
    elif type=='A_star1':
        return depth + Cal_diffNum(arr, target)
    elif type=='A_star2':
        return depth + Cal_distance(arr, target)
    else:
        print('ERROR!')
        return 0

# @ 八数码节点类,即某一状态
class EightNumArr(object):
    directions = ['left','up','right','down']
    directions2 = ['down','right','up','left']
    def __init__(self, arr, target, type, cost=0, parent=None, depth=0):
        self.arr = arr
        self.target = target
        self.type = type
        self.cost = cost
        self.parent = parent
        self.depth = depth

    def __lt__(self, other):
        # return Cal_diffNum(self.arr, self.target)<Cal_diffNum(other.arr, other.target)
        return compute_cost(self.depth,self.type,self.arr,self.target) < compute_cost(other.depth,self.type,other.arr,other.target)

    # @ 打印八数码
    def Display(self):
        for i in range(3):
            for j in range(3):
                print(self.arr[i, j], end='\t')
            print("\n")
        print('->')

    # @ 返回该状态与目标状态中数字位置相同的个数-->返回9时则结束
    def Cal_sameNum(self, state, target):
        pos = np.where(state.arr == target)
        return len(state.arr[pos])

    # @ 计算曼哈顿距离,作为后续A*2中每轮循环下排序的标准
    def Cal_Dis(self, state, target):
        sum = 0
        for cols in target:
            for val in cols:
                i, j = np.where(state.arr == val)[0], np.where(state.arr== val)[1]
                si, sj = np.where(target == val)[0], np.where(target == val)[1]
                sum += abs(i - si) + abs(j - sj)
        return sum

    # @ 是否在close表中已出现,若已出现还放入open表,则会进入死循环。
    def isInClose(self, state, stop):
        for val in stop:
            pos = np.where(state.arr == val.arr)
            if len(state.arr[pos]) == 9:
                return True
        return False
    # @ 某一状态下进行四个方向上的移动空格
    def Move(self, type):
        row, col = np.where(self.arr == 0)
        if type=='DFS':
            directs, Move_list = self.directions2,[]
        else:
            directs, Move_list = self.directions,[]
        for direct in directs:
            if 'left' == direct and col > 0:
                arr_copy = self.arr.copy()
                arr_copy[row, col], arr_copy[row, col - 1] = arr_copy[row, col - 1], arr_copy[row, col]
                Move_list.append(EightNumArr(arr_copy, self.target,type,compute_cost(self.depth, type, arr_copy, target),self, self.depth + 1))
            elif 'up' == direct and row > 0:
                arr_copy = self.arr.copy()
                arr_copy[row, col], arr_copy[row - 1, col] = arr_copy[row - 1, col], arr_copy[row, col]
                Move_list.append(EightNumArr(arr_copy, self.target,type,compute_cost(self.depth, type, arr_copy, target), self, self.depth + 1))
            elif 'down' == direct and row < 2:
                arr_copy = self.arr.copy()
                arr_copy[row, col], arr_copy[row + 1, col] = arr_copy[row + 1, col], arr_copy[row, col]
                Move_list.append(EightNumArr(arr_copy, self.target,type,compute_cost(self.depth, type, arr_copy, target), self, self.depth + 1))
            elif 'right' == direct and col < 2:
                arr_copy = self.arr.copy()
                arr_copy[row, col], arr_copy[row, col + 1] = arr_copy[row, col + 1], arr_copy[row, col]
                Move_list.append(EightNumArr(arr_copy, self.target,type,compute_cost(self.depth, type, arr_copy, target), self, self.depth + 1))
        return Move_list

    # @ DFS深度优先搜索,返回True/False, count搜索次数
    def DFS(self,max_depth):
        start = time.time()
        stack, close, count, node = [], [], 0, None
        stack.append(self)
        while stack:
            node = stack.pop()               # 后进先出,pop出栈顶元素
            close.append(node)
            # node.Display()
            if self.Cal_sameNum(node,self.target) == 9:  # 到达目标状态,直接返回
                end = time.time()
                return True, count, node, end-start
            next_nodes = node.Move('DFS')    # 一次移动的状态列表
            for next_node in next_nodes:
                if next_node.depth < max_depth:       # 最大深度
                    if self.isInClose(next_node, close):
                        continue
                    stack.append(next_node)
            count += 1
        end = time.time()
        return False, count, node, end-start

    # @ BFS广度优先搜索,返回True/False, count搜索次数
    def BFS(self,max_depth):
        start = time.time()
        queue, close, count, node = [], [], 0, None
        queue.append(self)
        while queue:
            node = queue.pop(0)              # 先进先出,pop出队首元素
            close.append(node)
            # node.Display()
            if self.Cal_sameNum(node,self.target) == 9:  # 到达目标状态,直接返回
                end = time.time()
                return True, count, node, end-start
            next_nodes = node.Move('BFS')    # 一次移动的状态列表
            for next_node in next_nodes:
                if next_node.depth < max_depth:       # 最大深度
                    if self.isInClose(next_node, close):
                        continue
                    queue.append(next_node)
            count += 1
        end = time.time()
        return False, count, node, end-start

    # @ UCS一致代价搜索,返回True/False,count搜索次数
    def UCS(self,max_depth):
        start = time.time()
        close, count, node = [], 0, None
        que = queue.PriorityQueue()
        que.put(self)
        while que:
            node = que.get()
            close.append(node)
            # node.Display()
            if self.Cal_sameNum(node, self.target) == 9:  # 到达目标状态,直接返回
                end = time.time()
                return True, count, node, end-start
            next_nodes = node.Move('UCS')    # 一次移动的状态列表
            for next_node in next_nodes:
                if next_node.depth < max_depth:
                    if self.isInClose(next_node, close):
                        continue
                    que.put(next_node)
            count += 1
        end = time.time()
        return False, count, node, end-start

    # @ A_star启发式搜索--h(x):不在位数量/曼哈顿距离,返回True/False,count搜索次数
    def A_star(self,max_depth,A_starType):
        start = time.time()
        close, count, node = [], 0, None
        que = queue.PriorityQueue()
        que.put(self)
        while que:
            node = que.get()
            close.append(node)
            if self.Cal_sameNum(node, self.target) == 9:  # 到达目标状态,直接返回
                end = time.time()
                return True, count, node, end-start
            next_nodes = node.Move(A_starType)     # 一次移动的状态列表
            for next_node in next_nodes:
                if next_node.depth < max_depth:      # 最大深度
                    if self.isInClose(next_node, close):
                        continue
                    que.put(next_node)
            count += 1
        end = time.time()
        return False, count, node, end-start
  • 17
    点赞
  • 182
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 15
    评论
实现八数码问题界面,可以使用 Python 的 tkinter 模块。以下是一个简单的示例代码: ```python import tkinter as tk class EightPuzzleGUI: def __init__(self, puzzle): self.puzzle = puzzle self.window = tk.Tk() self.window.title("八数码问题") self.buttons = [] for i in range(3): row = [] for j in range(3): button = tk.Button(self.window, text=str(puzzle[i*3+j]), font=("Helvetica", 32), width=3, height=1) button.grid(row=i, column=j) row.append(button) self.buttons.append(row) solveButton = tk.Button(self.window, text="解决", font=("Helvetica", 16), command=self.solve) solveButton.grid(row=3, column=1) def solve(self): # 这里添加求解八数码问题代码 pass def update(self): for i in range(3): for j in range(3): self.buttons[i][j].configure(text=str(self.puzzle[i*3+j])) def run(self): self.window.mainloop() ``` 在这个示例代码中,我们定义了一个 EightPuzzleGUI 类,用于显示八数码问题的界面。这个类接受一个长度为 9 的列表作为参数,表示当前的八数码问题状态。 在类的构造函数中,我们使用 tkinter 的 Button 组件创建了一个 3x3 的网格,每个格子都对应一个按钮,用于显示当前的八数码问题状态。在按钮的回调函数中,我们可以添加求解八数码问题代码。 为了显示界面,我们还定义了一个 run 方法,用于启动 tkinter 的主循环。在主循环中,tkinter 会不断更新界面,直到用户关闭窗口为止。 使用这个类的示例代码如下: ```python puzzle = [1, 2, 3, 4, 5, 6, 7, 8, 0] gui = EightPuzzleGUI(puzzle) gui.run() ``` 这个示例代码创建了一个初始状态为 [1, 2, 3, 4, 5, 6, 7, 8, 0] 的八数码问题界面,并启动了 tkinter 的主循环。你可以根据需要修改 puzzle 列表的值,以显示不同的八数码问题状态。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

路过的风666

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值