启发式搜索中的全局择优搜索解决8数码问题

求解算法设计

全局优先搜索算法

(1) 把初始节点S0放入OPEN表,f(S0);
(2) 如果OPEN表为空,则问题无解,退出;
(3) 把OPEN表的第一个节点(记为节点n)取出放入CLOSED表;
(4) 考察节点n是否为目标节点:若是,则求得问题的解,退出;
(5) 若节点n不可扩展,则转第(2)步;
(6) 扩展节点n,用估计函数f(x)计算每个子节点的估价值,并为每个子节点配置指向父节点的指针;把这些子节点都送入OPEN表中,然后对OPEN表中的全部节点按估价值从小到大的顺序进行排序,然后转第(2)步。

节点扩展算法

(1)如图1,定义8数码的索引号。
(2)定义0代表8数码中的空格。0的移动即为空格的移动。
(3)定义:-3(0向上移),3(0向下移动),-1(0向左移动),1(向右移动)
(4)如果0号节点的索引除以3不为0,且该节点不是由父节点向右得到的,则该节点可以向左移动。
(5)如果0号节点的索引+1除以3不为0,且该节点不是由父节点向左得到的,则该节点可以向右移动。
(6)如果0号节点的索引大于2,且该节点不是由父节点向下得到的,则该节点可以向上移动。
(7)如果0号节点的索引小于6,且该节点不是由父节点向上得到的,则该节点可以向下移动。

两个点的最短移动距离

将indexA作为较小值
如果indexB-indexA≥3,result=1+(indexA+3与indexB的曼哈顿距离);
如果indexB-indexA=2,result=2;
如果indexB-indexA=0,result=0;
如果以上均不符合,那么计算(indexA+1)模3的值,如果为0,result取3,否则取1。

求解算法实现

8数码结构

将8数码保存在列表中,并将列表置于当前节点中。
self.S0=[] #初始状态
self.Sg=[1,2,3,8,0,4,7,6,5] #目标状态
node.list=[] 当前节点的8数码

节点数据结构

将Node类作为节点的数据,其包含的数据如下:
class Node():
def init(self):
self.index=0 #节点的序号,作为节点的唯一标识
self.parentIndex=0 #节点父亲的序号
self.zeroIndex=0 #0号的位置
self.list=[] #该节点的8数码(type=list)
self.fund=0 #该节点的估计值
self.depth=0 #该节点的深度
self.direction=0 #该节点有父亲节点如何移动得到 -3(0向上移),3(0向下移动),-1(0向左移动),1(向右移动)

扩展实验结果比对

用例解路径<6

在这里插入图片描述

用例解路径>10

在这里插入图片描述

用例解路径>20

在这里插入图片描述

启发函数3在不同权重下的结果

在这里插入图片描述
在这里插入图片描述

实验代码

'''
Created on 2020年10月24日
启发式搜素——全局优先搜索解决8数码问题
@author: xxxxxx
'''
import  GlobalOptimal  as GO
if __name__=='__main__':
    glo=GO.globalOptimal()
    #list=[1,3,2,4,0,5,6,7,8 ]
    print("输入起始状态")
    for i in range(9):
        a=input("第"+str(i)+"个数")
        list.append(int(a))

    glo.start(S0=list)
'''
Created on 2020年10月24日
保存待扩展的节点
@author: xxxxxxxx
'''
import operator
import NODE  as Node
class Open():

    def __init__(self):
        self.nodelist=[] #保存节点的列表
        self.cmpfun = operator.attrgetter('fund','depth') #open表排序条件 估计值优先,其次是深度

    def sortfun(self):
        '''
        对open表进行排序
        :return:
        '''
        #根据条件对open表进行排序
        self.nodelist.sort(key=self.cmpfun)



    def append(self,node):
        '''
        向节点列表中添加节点
        :param node:
        :return:
        '''
        #节点列表中添加一个新的节点
        self.nodelist.append(node)
        #对节点列表(open表)进行排序
        self.sortfun()

    def pop(self):
        '''
        从open表中取一个节点
        :return:node
        '''
        #从open表中取出一个节点
        return self.nodelist.pop(0)


    def isEmpty(self):
        '''
        判断open表是否为空
        :return: true 为空,false 不为空
        '''
        if self.nodelist:
            return False
        else:
            return True
'''
Created on 2020年10月24日
保存节点的相关数据
@author: xxxxxxxx
'''
class Node():
    def __init__(self):
        self.index=0  #节点的序号,作为节点的唯一标识
        self.parentIndex=0 #节点父亲的序号
        self.zeroIndex=0  #0号的位置
        self.list=[]  #该节点的8数码(type=list)
        self.fund=0 #该节点的估计值
        self.depth=0 #该节点的深度
        self.direction=0  #该节点有父亲节点如何移动得到 -3(0向上移),3(0向下移动),-1(0向左移动),1(向右移动)

    def construction(self,index,parentIndex,list,fund,depth,direction):
        '''
        对节点进行构造
        '''
        self.index=index
        self.parentIndex=parentIndex
        self.list=list
        self.fund=fund
        self.depth=depth
        self.direction=direction
        self.zeroIndex=self.list.index(0)
'''
Created on 2020年10月24日
全局优先搜锁
@author: xxxxxx
'''
import time
import  OPEN as Open
import NODE as Node
import HFunction as hf
class globalOptimal():
    '''
    全局优先搜索
    '''

    def __init__(self):
        self.open=Open.Open()
        self.S0=[]   #初始状态
        self.Sg=[1,2,3,8,0,4,7,6,5]   #目标状态
        self.Sindex=-1  #记录最后一个节点序号 -1尚没有节点,0初始节点
        self.closed={}
        self.targetPath=[]  #解路径


    def constructionNode(self,Sn,depth,parentIndex,direction):
        '''
        构造节点
        :param Sn: 该节点的8数码列表
        :param depth: 该节点的深度
        :param parentIndex: 该节点的父亲节点序号
        :param direction: 该节点如何得到
        :return:
        '''
        #新创建一个节点
        newnode=Node.Node()
        #为该节点分配一个序号
        self.Sindex=self.Sindex+1
        #计算机该节点的估计值
        hun=hf.hfun_3(Sn)
        fund=hun+depth
        #构造该节点
        newnode.construction(index=self.Sindex,parentIndex=parentIndex,list=Sn,fund=fund,depth=depth,direction=direction)
        #将该节点加入open表
        self.open.append(node=newnode)

    def closedPush(self,node):
        '''
        将从open表中取出的节点放到closed表中
        :param node: 要放置的节点
        :return:
        '''
        index=node.index
        self.closed[index]=node


    def isTarget(self,node):
        '''
        判断是否为目标解
        :param node: 当前节点
        :return: true,是目标节点,false 不是目标节点
        '''
        for i in range(9):
            if node.list[i]!=self.Sg[i]:
                return False
        return True


    def getTarget(self,node):
        '''
        求取解路径
        :param node:最终解的节点
        :return:
        '''
        self.targetPath.append(node)  #将解节点放入路径表中
        while node.parentIndex!=-1:  #当路径上的节点还有父节点时
            index=node.parentIndex   #获取父节点序号
            node=self.closed[index]  #获取父节点
            self.targetPath.append(node)  #将节点加入路径表


    def moveNoed(self,parNode,direction):
        '''
        8数码中空白格的移动
        :param parNode: 待移动的节点(父节点
        :param direction: 需要移动的方向
        :return:
        '''
        #构造移动后的8数码
        zeroIndex=parNode.zeroIndex
        ss=[i for i in parNode.list]
        tem=ss[zeroIndex+direction]
        ss[zeroIndex]=tem
        ss[zeroIndex+direction]=0
        #构造新节点
        self.constructionNode(Sn=ss,depth=parNode.depth+1,parentIndex=parNode.index,direction=direction)


    def extendNode(self,node):
        '''
        向下扩展的节点
        :param node: 待扩展的节点
        :return:
        '''
        zeroindex=node.zeroIndex
        #向左移动
        if zeroindex%3!=0 and node.direction!=1:
            self.moveNoed(node,direction=-1)
        #向右移动
        if (zeroindex+1)%3!=0 and node.direction!=-1:
            self.moveNoed(node,direction=1)
        #向上移动
        if zeroindex>2 and node.direction!=3:
            self.moveNoed(node,direction=-3)
        #向下移动
        if zeroindex<6 and node.direction!=-3:
            self.moveNoed(node,direction=3)


    def solve(self):
        '''
        对问题进行求解
        :return:
        '''
        # 构造初始节点,并将其加入open表
        self.constructionNode(Sn=self.S0,depth=0,parentIndex=-1,direction=0)
        #open表不为空,继续执行
        while(self.open.isEmpty()==False):
            #取open表中的第一个元素
            node=self.open.pop()
            #将该节点放入closed表中
            self.closedPush(node=node)
            if self.isTarget(node=node)==True:  #如果是解节点
                self.getTarget(node=node)  #求取解路径
                return True
            else:   #否则向下扩展
                self.extendNode(node=node)
        return False


    def start(self,S0):
        '''
        求解的起始函数,调用函数可以进行
        :param S0: 初始状态列表
        :return: 该函数无返回值,会控制台打印结果。
        '''
        self.S0=S0
        if hf.inverseNum(S0)%2!=1:
            print("无解")
        else:
            start = time.perf_counter()
            bool=self.solve()
            end = time.perf_counter()
            if bool==True:
                print("总扩展节点",self.Sindex,"   解路径长度",len(self.targetPath),"  用时",(end - start))
                print("路径如下")
                for i in self.targetPath:
                    print(i.list)
            else:
                print("error")

'''
Created on 2020年10月24日
启发式搜索中的启发函数的定义,还有对逆序数的求解,对最短移动距离的求解。
其中启发函数1,2,3,4为本实验的扩展内容
启发函数5为根据启发函数2改变的
@author: xxxxxxxxx
'''

Sg=[1,2,3,8,0,4,7,6,5]  #目标状态
def inverseNum(list):
    '''
    求取逆序数
    :param list:8数码列表
    :return:返回逆序数
    '''
    count = 0
    temlist = [i for i in list]
    temlist.remove(0)
    for i in range(7):
        for j in range(i + 1, 8):
            if temlist[i] > temlist[j]:
                count = count + 1
    return count

def minMove(indexA,indexB):
    '''
    两个点的最短移动距离
    :param indexA: 点A
    :param indexB: 点B
    :return: 移动的最短距离
    '''
    #将A作为较小
    if indexA>indexB:
        indexA,indexB=indexB,indexA
    differenceValue=indexB-indexA
    if(differenceValue>=3):   #如果差值大于3,则肯定不在同一行
        result=1+minMove(indexA+3,indexB)
    elif differenceValue==2:  #如果差值为2,则最短距离肯定为2
        result=2
    elif differenceValue==0:   #差值为0,则不用移动
        result=0
    else:                     #差值为1,可能移动1次也可能移动3次
        result=3 if (indexA+1)%3==0 else 1
    return result





def hfun_1(sn):
    '''
    启发函数_1,)启发函数 h(n)定义为当前节点与目标节点差异的度量:即当前节点与目标节点格局相比,位置不符的数字个数。
    :param sn:当前状态列表
    :return:启发函数值
    '''
    hun=0
    for i in range(9):
        if sn[i] != Sg[i] and sn[i] != 0:
            hun = hun + 1
    return hun

def hfun_2(sn):
    '''
    启发函数_2,)启发函数 h(n)定义为当前节点与目标节点距离的度量:当前节点与目标节点格局相比,位置不符的数字移动到目标节点中对应位置的最短距离之和。
    :param sn:当前状态列表
    :return:启发函数值
    '''
    count=0
    for i in range(9):
        if sn[i]!=Sg[i] and sn[i]!=0:
            count=count+minMove(i,Sg.index(sn[i]))
    return count



def hfun_3(sn):
    '''
     启发函数_3,启发函数 h(n)定义为每一对逆序数字乘以一个倍数。
    :param sn: 当前状态列表
    :return: 启发函数值
    '''
    inum=inverseNum(list=sn)
    return abs(inum-7)*3

def hfun_4(sn):
    '''
    启发函数_4,启发函数 h(n)定义为位置不符数字个数的总和与 3 倍数字逆序数目相加。
    :param sn:当前状态列表
    :return:启发函数值
    '''
    num1=inverseNum(list=sn)
    num2=hfun_1(sn)
    return num2+3*abs(num1-7)


def hfun_5(sn):
    '''
    启发函数_5,当前状态与所有目标状态位置不符的数字,在列表中的索引值的差值和
    :param sn:当前状态列表
    :return:启发函数值
    '''
    count = 0
    for i in range(9):
        if sn[i] != Sg[i] and sn[i] != 0:
            count=count+abs(i-Sg.index(sn[i]))
    return count
  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

丁拾陆

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

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

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

打赏作者

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

抵扣说明:

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

余额充值