BFS实现八数码难题

这是一个非常经典的实验,作为人工智能导论课的第一个题目,思路上似乎不是很难,但是实现起来还是有点复杂,接下来讲解一下我的思路

BFS实现思路(源码会给在最下方)

八数码问题python源码 八数码难题python_bfs

BFS实现方法

  1. 大致思路

使用BFS实现还是比较简单的,只需要用队列的方式来储存每一个节点即可,使用python来存储这些类对象还是很简单的,然后就用while循环一层层的去对照节点的状态如何,直到队列空了则退出循环,视为查询失败,如果找到了目标节点,则提前退出while循环并返回该节点

该节点会存储着parent的指针,指向父类,这样就通过该节点向上找到整条路径,最后反过来输出就可以知道路径是怎么样的了

八数码问题python源码 八数码难题python_八数码问题python源码_02

2. 代码实现

  • 所需包:numpy,copy
  • 所需数据结构队列(这里我自己写了一个封装好的Myqueue类)
    其实直接用list也可以,用queue来作为OPEN表装节点
  • Main函数内容:
    首先是初始化最初状态和结束状态,这里我直接用一个一维数组来表示,可以方便扩展八数码问题
  • 然后建立OPEN表和CLOSE表,用队列作为数据结构
  • 将初始化State类,并将初始状态放入该类中

然后使用State类当中的Find()函数搜索

如果查询成功的话返回的是包含与TargetState相同的矩阵的State类

如果失败则返回一个没有parent的与OriginState相同矩阵的State类

State类构造以及Find函数

  • State类的成员函数

八数码问题python源码 八数码难题python_数据结构_03

parent指的是父亲节点,dir指的是一个字符串含有上一个节点到这一个节点的方向,默认为None

CuState是存储当前状态的数字一维矩阵,index表示八数码当中0(也就是空位)所处的索引位置

  • Find函数讲解
    首先是初始化的准备,全局引入一些变量,然后将初始节点入队

然后会通过计算逆序数来判断这个初始状态是否是有解的,也就是最终能否到达目标节点的状态

八数码问题python源码 八数码难题python_数据结构_04

def calculate_Reverse(self,Matrix): #计算逆序数
        State_Matrix = []
        for i in range(9):
            State_Matrix.append(Matrix[i])
        State_Matrix.remove(0)
        reverse_number = 0
        for cur in State_Matrix:
            index = State_Matrix.index(cur)
            # 遍历cur后面的数字
            for after_cur in State_Matrix[index:]:
                if cur > after_cur:
                    reverse_number += 1
        if reverse_number % 2 == 0:
            return 0
        else:
            return 1
    
    def is_Solvable(self,Tar_State): #判断该状态是否有解
        init_Reverse_Num = self.calculate_Reverse(self.Matrix)
        Tar_Reverse_Num = self.calculate_Reverse(Tar_State)
        if init_Reverse_Num == Tar_Reverse_Num:
            print("The State is solvable")
            return 1
        else:
            print("The State is unsolvable")
            return 0

接下来就可以开始进行while循环,每一次循环loop都是对队列的头节点取出来并且进行产生新的分支节点

八数码问题python源码 八数码难题python_python_05

!!这里要注意用深拷贝的方法取得一个新的结点,因为Fir_State同时指向了上一个节点的多个State对象(主要是懒得换名字)

下面是四个状态的代码(其实代码是差不多的,主要还是判断条件有点不太一样)

if ((Index + 1) % 3 != 0):  #右边界条件
                Next_State = self.Create_Next_State(Index, Index + 1, First_State, "Right")
                if not self.is_State_Existed(CLOSE,Next_State):
                    print("Move Right")# 代表可以向右移动
                    stateNum += 1
                    if (Next_State.Matrix == TargetState).all():
                        return Next_State
                    else:
                        #Next_State.ShowState()
                        OPEN.push(Next_State)

            if (Index % 3 != 0):  # 代表可以向左移动
                Next_State = self.Create_Next_State(Index, Index - 1, First_State, "Left")
                if not self.is_State_Existed(CLOSE,Next_State):
                    print("Move Left")
                    stateNum += 1
                    if (Next_State.Matrix == TargetState).all():
                        return Next_State
                    else:
                        #Next_State.ShowState()
                        OPEN.push(Next_State)

            if (Index - 3 >= 0):  # 代表可以向上移动
                Next_State = self.Create_Next_State(Index, Index - 3, First_State, "Up")
                if not self.is_State_Existed(CLOSE,Next_State):
                    print("Move Up")
                    stateNum += 1
                    if (Next_State.Matrix == TargetState).all():
                        return Next_State
                    else:
                        #Next_State.ShowState()
                        OPEN.push(Next_State)

            if (Index + 3 <= 8):  # 代表可以向下移动
                Next_State = self.Create_Next_State(Index, Index + 3, First_State, "Down")
                if not self.is_State_Existed(CLOSE,Next_State):
                    print("Move Down")
                    stateNum += 1
                    if (Next_State.Matrix == TargetState).all():
                        return Next_State
                    else:
                        #Next_State.ShowState()
                        OPEN.push(Next_State)
  • State类的成员函数(除了Find之外)
def ShowState(self):#打印矩阵信息
        print("Move ",self.Direction)
        for i in range(3):
            for j in range(3):
                print(self.Matrix[j + i * 3],end=" ")
            print()
        print()

    def Swap(self, Cu_index, Next_index, NState):#交换矩阵当中的两个数
        temp = NState[Cu_index]
        NState[Cu_index] = NState[Next_index]
        NState[Next_index] = temp

    def Create_Next_State(self, Cu_index, Next_index, Fir_State, Dir):#通过上一个节点来产生一个新的State类,含有新的方向和数组
        Next_State = State(
            CuState=Fir_State.Matrix.copy(),
            index=Next_index,
            parent=Fir_State, #指向上一个节点
            dir=Dir)
        self.Swap(Cu_index, Next_index, Next_State.Matrix)
        return Next_State

    def is_State_Existed(self,CLOSE,Next_State):#用来判读新的状态是否和CLOSE表里面的有重叠
        for node in CLOSE._queue:
            if(Next_State.Matrix == node.Matrix).all():
                return 1 #表示已经有元素存在,不会将该状态入队
        return 0

比较重要的还是is_State_Existed() 用来判重,其他的基本就是字面意思,都很好理解

最后退出循环并且打印,没有就返回

成功返回状态节点后打印

八数码问题python源码 八数码难题python_数据结构_06


遇到的难点

  • 首先是思路的构建,如何让代码更加简洁的表示出来吧,目前代码还是感觉重写性很高,不够优美。
First_State = copy.deepcopy(Fir_State) #这里用深拷贝是因为之前四个状态都被同一个名字指向了

为什么这里要用深拷贝呢,是因为python它的局部变量竟然会保留并且跑出来,这让我如何是好,指针指向了多个对象实例,于是一旦更改就全部更改了,所以只能深拷贝一个防止修改了全部了


最后附上全部代码

import numpy as np
import copy
class MyQueue:  # 队列数据结构
    def __init__(self, size):
        self.front = -1
        self.rear = -1
        self._size = size
        self._queue = []

    def push(self, data):
        if not (self.isFull()):
            self._queue.append(data)
            self.rear += 1

    def pop(self):
        if not (self.isEmpty()):
            self._queue.pop(0)
            self.rear -= 1

    def isFull(self):  # 队列满时返回1
        return self.rear == self._size - 1

    def isEmpty(self):  # 队列空时返回1
        return self.rear == -1

    def getFont(self):
        if (self.isEmpty() == 0):
            return self._queue[0]


class State:
    def __init__(self, CuState, index, parent=None, dir=None):
        self.parent = parent  # 父亲节点
        self.Direction = dir
        self.Matrix = CuState
        self.CuIndex = index

    def ShowState(self):#打印矩阵信息
        print("Move ",self.Direction)
        for i in range(3):
            for j in range(3):
                print(self.Matrix[j + i * 3],end=" ")
            print()
        print()

    def Swap(self, Cu_index, Next_index, NState):#交换矩阵当中的两个数
        temp = NState[Cu_index]
        NState[Cu_index] = NState[Next_index]
        NState[Next_index] = temp

    def Create_Next_State(self, Cu_index, Next_index, Fir_State, Dir):#通过上一个节点来产生一个新的State类,含有新的方向和数组
        Next_State = State(
            CuState=Fir_State.Matrix.copy(),
            index=Next_index,
            parent=Fir_State, #指向上一个节点
            dir=Dir)
        self.Swap(Cu_index, Next_index, Next_State.Matrix)
        return Next_State

    def is_State_Existed(self,CLOSE,Next_State):#用来判读新的状态是否和CLOSE表里面的有重叠
        for node in CLOSE._queue:
            if(Next_State.Matrix == node.Matrix).all():
                return 1 #表示已经有元素存在,不会将该状态入队
        return 0 
    

    def Find(self):
        global OPEN,CLOSE,  TargetState
        OPEN.push(self)
        stateNum = 0
        Index = self.CuIndex  # 当前索引位置
        while True:
            if (OPEN.isEmpty()):  # 若表空,则表示查询失败
                break
            Fir_State = OPEN.getFont()
            First_State = copy.deepcopy(Fir_State) #这里用深拷贝是因为之前四个状态都被同一个名字指向了
            Index = First_State.CuIndex
            OPEN.pop()
            CLOSE.push(First_State)

            if ((Index + 1) % 3 != 0):  #右边界条件
                Next_State = self.Create_Next_State(Index, Index + 1, First_State, "Right")
                if not self.is_State_Existed(CLOSE,Next_State):
                    print("Move Right")# 代表可以向右移动
                    stateNum += 1
                    if (Next_State.Matrix == TargetState).all():
                        return Next_State
                    else:
                        #Next_State.ShowState()
                        OPEN.push(Next_State)

            if (Index % 3 != 0):  # 代表可以向左移动
                Next_State = self.Create_Next_State(Index, Index - 1, First_State, "Left")
                if not self.is_State_Existed(CLOSE,Next_State):
                    print("Move Left")
                    stateNum += 1
                    if (Next_State.Matrix == TargetState).all():
                        return Next_State
                    else:
                        #Next_State.ShowState()
                        OPEN.push(Next_State)

            if (Index - 3 >= 0):  # 代表可以向上移动
                Next_State = self.Create_Next_State(Index, Index - 3, First_State, "Up")
                if not self.is_State_Existed(CLOSE,Next_State):
                    print("Move Up")
                    stateNum += 1
                    if (Next_State.Matrix == TargetState).all():
                        return Next_State
                    else:
                        #Next_State.ShowState()
                        OPEN.push(Next_State)

            if (Index + 3 <= 8):  # 代表可以向下移动
                Next_State = self.Create_Next_State(Index, Index + 3, First_State, "Down")
                if not self.is_State_Existed(CLOSE,Next_State):
                    print("Move Down")
                    stateNum += 1
                    if (Next_State.Matrix == TargetState).all():
                        return Next_State
                    else:
                        #Next_State.ShowState()
                        OPEN.push(Next_State)
            print("One Layer through,now StateNum is ",stateNum)
        print()
        return Or_State


if __name__ == '__main__':
    # OriginStatus = input()
    if 0:
        OriginState = np.array([2, 8, 3,
                    1, 0, 4,
                    7, 6, 5])
    if 1:
        OriginState = np.array([1, 6, 2,
                    8, 0, 3,
                    7, 5, 4])                
    TargetState = np.array([1, 2, 3,
                   8, 0, 4,
                   7, 6, 5])         
    OPEN = MyQueue(300)
    CLOSE = MyQueue(300)
    Or_State = State(CuState=OriginState, index=4)
    EndState = Or_State.Find() #若查询失败返回Origin节点,没有父类,因此不输出
    Path = [] # 用来存储节点路径,用于正序输出
    while (EndState.parent is not None):
        Path.append(EndState)
        EndState = EndState.parent
    if(Path):
        Or_State.ShowState()
        Path.reverse()
        for i in Path:
            i.ShowState()