八数码问题求解

BFS算法

编写思路

     将初始状态作为独立一层,由初始状态直接变换得来的其他状态为第二层,由第二层直接转换得来的状态为第三层……直至遍历到目标状态结束。
     先根据初始状态进行搜索,遍历其可以更改的状态,将其添加到 L1 中。为了避免新生成的状态直接进行遍历,以至于同一层的状态还未遍历结束就开始下一层,于是将 L1 中列表表示是否遍历过的值分为三种情况,新录入的状态值为-1,且当当前整层状态遍历结束后 再改为 0,方便进行下一轮遍历。

代码

# -*- coding: utf-8 -*-
"""
Created on Mon Mar 29 19:01:21 2021

@author: Lenovo
"""

#建立字典 对应九宫格 可移动的位置‘
Move={0:[1,3],1:[0,2,4],2:[1,5],3:[0,4,6],
      4:[1,3,5,7],5:[2,4,8],6:[3,7],7:[4,6,8],
      8:[5,7]}

#交换值
def swap(c,i,j):
    t=c[i]
    c[i]=c[j]
    c[j]=t
    return c

def shift(L):
    if search(End, L) or len(L)>200:  #列表中有目标状态 结束遍历
        return
    else:
        #未遍历到目标状态 继续遍历
        #搜寻列表中未被遍历的列表
        for l in L:
            if l[11]==0:    #该元素未被遍历 该轮遍历
                pos=l[:9].index(0)  #pos未当前未被遍历棋盘的0 的位置
                for x in Move[pos]:
                    V=l.copy()  
                    V=swap(V,pos,x)[:9] #V为转换位置后的列表
                    if search(V,L):    #转换后的V在列表中 即遍历生成过或者v回头
                        continue
                    else:
                        L.append(V[:9]+[len(L),l[9],-1]) #添加一个
                    if search(End, L) :  #列表中有目标状态 结束遍历
                        return
                #将一个未遍历过的元素完全遍历 将其value末尾变为1
                L[l[9]][11]=1
        #将当前列表中未遍历的元素遍历 字典更新 有新的未遍历的元素 -1
        #将新列表元素末尾-1值变为0 进行下一轮遍历
        for l in L:
            if l[11]==-1:
                L[l[9]][11]=0
        shift(L)    #遍历新列表
def search(a,L):
    p=0
    for i in L:
        if i[:9]==a[:9]:
            p=1
            break
    if p:
        return i
    else:
        return []
def Print(a):
    n=0;
    for i in a:
        print(i,end=' ')
        n+=1;
        if len(a)%n==0 and n!=1:
            n=0
            print()
    return 0
#输入起始状态
Start=[]
while len(Start)<9:
    x=input()
    if x=="":
        continue
    Start+=x.split(" ") 
Start = [int(Start[i]) for i in range(len(Start))]

End=[]
while len(End)<9:
    x=input()
    if x=="":
        continue
    End+=x.split(" ") 
End = [int(End[i]) for i in range(len(End))]

a=Start+[0,0,0] #当前下标 存放父节点在列表中的位置 0--未遍历 1--遍历 -1代表新遍历生成的 本次不进行遍历
L1=[] #用于存储由初始状态所能达到的所有状态
L1.append(a)
shift(L1)
Exist=search(End, L1)  #返回End在列表中的其余部分
if Exist:
    #回溯路径  
    num=[Exist[9]] #num存放路径的下标
    while num[len(num)-1]!=0:
        parent=num[len(num)-1]
        num.append(L1[parent][10])
    num=list(set(num)) #除去初始状态即目标状态导致的[0,0]状况
    #使用list()后num变得无序
    num.sort()
    if len(num)==1:
        print('initial')
        Print(Start)
        print('step',1)
        Print(L1[num[0]][:9])
    else:
        for i in range(len(num)):
            if i==0:
                print('initial')
            else:
                print('step',i)
            Print(L1[num[i]][:9])
else:
    print('no answer')



A*算法

A*算法介绍

A*算法需要明白以下定义:

  •   G:从开始状态变换到当前状态的总耗费
  •   H:从当前状态到达目标状态的预计耗费
  •   F:F=G+H 代表当前状态的总耗费
  •   Open:存放当前未进行搜索的状态集合
  •   Close:存放当前已经搜索过的状态集合

A*算法的大致步骤如下:

  1. 将初始状态S添加到Open表中
  2. 搜索Open表中F值最小的状态(第一次为起始状态S)
  3. 搜索S周围状态(第一次搜索,不会出现重复状态),将其周围状态添加到Open表中
  4. 将S从Open中删除,并将S添加到Close表中
  5. 从Open表中选出F值最小的状态m,执行第6步
  6. 判断m是否在Close表中:若在,已经搜索过 将m从Open表中删除,执行上一步;否则执行下一步
  7. 搜索m周围状态,记一个周围状态为A,A有三种可能:
    (1)A在Clsoe中,可以添加到Open中(即使添加到Open中,后期也会从Open中删除,而不会再添加到Close中)
    (2)A在Open中且A的F值大于Open表中a的F值:舍弃当前周围状态A,Open表不做改变
    (3)A在Open中且A的F值小于Open表中a的F值:更新Open表中a的F值,F值取A的F值(较小的F值)
  8. 对状态m搜索完毕,重复第5步直至目标状态E在Open表中(每次执行循环前可以判断)——目标状态已到达;或者Open表中无数据;亦可以设定一个约束条件结束循环

可参考https://blog.csdn.net/dujuancao11/article/details/114842884?utm_source=app&app_version=4.5.8

编写思路

       A*算法需要进行耗费估价,我将 g 设置为从初始状态‘移动’到当前状态所进行的‘移动步数’(即变换次数),h 值设为当前状态与目标状态各个位置不同元素值得个数,总耗费 f 为 g 与 h 之和。
       A*搜索算法建立两个表:Open 表和 Close 表。Open 表用于存放未搜索得状态,Close 表用于存放已经搜索完成的状态。先将初始状态添加到 Open 表中,再搜寻 Open 表中 f 值最小的状态进行扩展,同时将该状态从 Open 表中删除,添加到 Close 表中,重复以上搜索、扩展操作,直至 Open 表中出现目标状态或 Open 表为空。
       我建立两个字典用于表示 Open 表和 Close 表,字典 key 值为状态,用元组表示;字典 value 值为一个存放 g,h,f,pos,parent 等值的列表。
       将 Start(初始状态)添加到 Open 中,进行搜索字典中 value 中 f 所对应的 key 值MinFS,对 MinFsOpen 中删除,添加到 Close 中,再对状态 MinFs 进行搜索,扩展其周围状态,计算其周围状态 value 值。若其周围状态不在字典 Open 中,则将该状态添加到字典Open 中;若其周围状态在 Open 中已存在,则比较其 f 值:若字典中的 f 值较大,则更新其value 值;反之,不进行任何操作,进行下一状态的判别。对当前 MinFs 的周围状态扩展完毕后进行下一轮搜索最小 f 值,重复以上操作,直至 End(目标状态)出现在 Open 中,或者字典 Close 长度超过设定值。(避免状态搜索过多 9!)。
       当 FindPath 函数进行完毕后,字典 Close 更新完毕,进行是否能够由初始状态到达目标状态的判别。能到达则在 Close 中根据 End 所对应的 value 中的 parent 值进行回溯;否则输出‘no answer’。

代码

# -*- coding: utf-8 -*-
"""
Spyder Editor

This is a temporary script file.
"""
#A*算法 八数码问题
#初始状态 目标状态
Move={0:[1,3],1:[0,2,4],2:[1,5],3:[0,4,6],4:[1,3,5,7],
      5:[2,4,8],6:[3,7],7:[4,6,8],8:[5,7]}

#交换值 元组
def swap(c,i,j):
    c=list(c)
    t=c[i]
    c[i]=c[j]
    c[j]=t
    return tuple(c)

def Print(a):
    n=0;
    for i in a:
        print(i,end=' ')
        n+=1;
        if len(a)%n==0 and n!=1:
            n=0
            print()
    return 0

def Differ(a,b):
    #返回列表a和列表b相应位置不同元素的个数 H值
    count_h=0
    for i in range(len(a)):
        if a[i]!=b[i]:
            count_h+=1
    return count_h

def get_key (dict, value):
    #根据value找key
    for k, v in dict.items(): 
        if v == value:
            return k
def find_minF(Open):
    #提取opened value中的f值 [g,h,f,pos,parent]
    L1=[]
    L2=[]
    for value in Open.values():
        L1.append(value)
    #L2存放与opened中key位置相对应的f值
    for x in L1:
        L2.append(x[2])
    minpos=L2.index(min(L2)) #找出L2中最小值的下标 同时opened中最小f的value找出,即L1[minpos]
    return get_key(Open,L1[minpos])     

def INI(Set,Dict):
    #判断key是否在Dict中
    if Set in Dict:
        return 1
    else:
        return 0
def FindPath(Open):
    #首先判断目标状态在不在opened中 如果在结束当前函数 否则进行状态扩展
    if INI(End,Open):
        Close[End]=Open[End]
        return 
    if len(Close)>50:
        return
    else:
        #进行opened扩展
        #第一步 找寻字典opened中f值最小的状态key
        MinFs=find_minF(Open)
        #遍历MInFS临近状态 并判断路径是否已经搜索过
        if INI(MinFS,Close):
            #路径已搜索过 Closed不再添加
            #该状态不在搜寻其周围状态
            del Open[MinFS]
        else:    
            Close[MinFS]=Open[MinFS] #添加到Closed中
            del Open[MinFS] #从Opened移除
            #搜索MinFS周围状态
            l=Close[MinFS] #存储父状态的值 [g,h,f,pos,parent]
            g=l[0]+1;parent=l[3]
            Pos=MinFS.index(0)
            for x in Move[Pos]:
                V=MinFS
                V=swap(V, Pos, x)   #一个状态搜寻完成
                #找出当前状态所对应的g,h,f,pos,parent
                h=Differ(V,End);    f=g+h
                pos=len(Open)+len(Close)
                #若该状态在opened中存在 比较f值大小
                if INI(V,Open):
                    if f<l[2]: #当前状态f值较小 更新Opened中key所对应的value
                        Open[V]=[g,h,f,pos,parent]
                    else:
                        #当前状态f值较大 不进行任何更改
                        continue
                else:   #V不在opened中,将其添加到Opened中
                    Open[V]=[g,h,f,pos,parent]
                #进行*次状态搜索和更新
            #MinFS状态周围状态已遍历完成 Opened更新完成
        #对Opened中value值进行下一次判断最小f值和End的判断
        FindPath(Open)
    
def BackTrack(Closed,Path):
    parent=Closed[Path[-1]][4]   #将当前状态的父状态位置赋值
    pos=Close[Path[-1]][3] 
    if parent==0 and pos==0:
        #回溯到初始状态 结束回溯
        return Path
    for x in Closed.values():
        if x[3]==parent:
            Path.append(get_key(Closed,x))
            break
    BackTrack(Closed,Path)


#输入起始状态
Start=[]
while len(Start)<9:
    x=input()
    if x=="":
        continue
    Start+=x.split(" ") 
Start = [int(Start[i]) for i in range(len(Start))]

End=[]
while len(End)<9:
    x=input()
    if x=="":
        continue
    End+=x.split(" ") 
End = [int(End[i]) for i in range(len(End))]
Start=tuple(Start)
End=tuple(End)
Open={};  Close={}
#将初始状态添加到Opened中 
#字典key为状态元组,value为[g,h,f,pos,parent]
g=0;    h=Differ(Start,End);    f=g+h
Open[Start]=[g,h,f,0,0]
FindPath(Open)
if INI(End,Close):
    Path=[End]
    BackTrack(Close,Path) #路径搜索完毕
    Path.reverse() #Path[:-1]为目标状态到初始状态路径 需逆序
    #打印
    print('initial')
    Print(Start)
    if len(Path)==1:
        print('step',1)
        Print(Path[0])
    else:
        for i in range(1,len(Path)):
            print('step',i)
            Print(Path[i])   
else:
    print('no answer')

代码尚有可优化之处,敬请指教。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值