八数码问题解析

八数码的问题描述为:

在3×3的棋盘上,摆有八个棋子,每个棋子上标有1至8的某一数字。棋盘中留有一个空格,空格用-1来表示。空格周围的棋子可以移到空格中。要求解的问题是:给出一种初始布局(初始状态)和目标布局,找到一种最少步骤的移动方法,实现从初始布局到目标布局的转变。

解决八数码的方法很多,本文采用1.广度优先搜索的策略,和A星算法两种比较常用的算法思想解决此问题

广度优先搜索的策略一般可以描述为以下过程:

状态空间的一般搜索过程

OPEN表:用于存放刚生成的节点

CLOSE表:用于存放将要扩展或已扩展的节点

1) 把初始节点S0放入OPEN表,并建立只含S0的图,记为G

OPEN:=S0,G:=G0(G0=S0)

2) 检查OPEN表是否为空,若为空则问题无解,退出

LOOP:IF(OPEN)=() THEN EXIT(FAIL)

3) 把OPEN表的第一个节点取出放入CLOSE表,记该节点为节点n

N:=FIRST(OPEN),REMOVE(n,OPEN),ADD(n,CLOSE)

4) 观察节点n是否为目标节点,若是,则求得问题的解,退出

IF GOAL(n) THEN EXIT(SUCCESS)

5) 扩展节点n,生成一组子节点.把其中不是节点n先辈的那些子节点记作集合M,并把这些节点作为节点n的子节点加入G中.

EXPAND(n)-->M(mi),G:=ADD(mi,G)

7) 转第2步

下面贴出代码:

import time
import copy

class list():
    def __init__(self,info):
        self.info = info
        self.front = None

class Solution():
    def __init__(self):
        self.open = []
        self.closed = []
        self.co = 0

    def msearch(self,S0, Sg):
        head = list(S0)
        self.open.append(head)
        while self.open:
            n = self.open.pop(0)
            self.co += 1
            print(‘取得节点n:‘,n.info)
            if n.info == Sg:
                print(‘得到问题的解!‘)
                print(‘一共进行了‘,self.co,‘次查找‘)
                print(‘该问题的解为:‘) #对n进行
                while n:
                    print(n.info)
                    n = n.front
                return
            if n in self.closed:
                #节点判定是否为扩展问题
                print(‘该结点不可扩展‘)
            else:
                print(‘该节点可扩展‘)
                #扩展节点n,
                #将其子节点放入open的尾部,
                #为每一个子节点设置指向父节点的指针
                nkongdi, nkongdj = 0, 0
                for i in range(3):
                    for j in range(3):
                        if n.info[i][j] == -1:
                            nkongdi = i
                            nkongdj = j
                ln,un,rn,dn =copy.deepcopy(n.info),copy.deepcopy(n.info),copy.deepcopy(n.info),copy.deepcopy(n.info)
                if nkongdj != 0: #right
                    rn[nkongdi][nkongdj],rn[nkongdi][nkongdj-1] = rn[nkongdi][nkongdj-1],rn[nkongdi][nkongdj]
                    rn = self.link(n,rn)
                    if rn not in self.closed:
                        self.open.append(rn)
                if nkongdi != 0: #down
                    dn[nkongdi][nkongdj],dn[nkongdi-1][nkongdj] = dn[nkongdi-1][nkongdj],dn[nkongdi][nkongdj]
                    dn = self.link(n,dn)
                    if dn not in self.closed:
                        self.open.append(dn)
                if nkongdj != 2: #left
                    ln[nkongdi][nkongdj],ln[nkongdi][nkongdj+1] = ln[nkongdi][nkongdj+1],ln[nkongdi][nkongdj]
                    ln = self.link(n,ln)
                    if ln not in self.closed:
                        self.open.append(ln)
                if nkongdi != 2: #up
                    un[nkongdi][nkongdj],un[nkongdi+1][nkongdj] =  un[nkongdi+1][nkongdj],un[nkongdi][nkongdj]
                    un = self.link(n,un)
                    if un not in self.closed:
                        self.open.append(un)
            self.closed.append(n)

    def link(self, n ,willn):
        willnn = list(willn)
        willnn.front = n
        return willnn

if __name__ == ‘__main__‘:
    S0 = [[2,8,3],
         [1,-1,4],
         [7,6,5]]
    S1 = [[1,2,3],
          [8,-1,4],
          [7,6,5]]
    Solution().msearch(S0,S1)

代码的一些问题:

1.对于八数码这种状态,可以采用一些常用的压缩策略来减少对内存空间的使用。本文为简单其间,并为采用压缩策略,直接以数组表示。

2.对于查找n的后继节点,应该是可以采用一个循环来减少代码冗余的。

此方法优点,缺点:

1.完备的策略->必定会找到一个解

2.找到的解必定是路径最短的解

3.盲目性大,搜索效率低

为了解决以上,盲目性大,搜索效率低的问题:我们引出A算法。 A算法的原理如下:

A* [1] (A-Star)算法是一种静态路网中求解最短路径最有效的直接搜索方法,也是许多其他问题的常用启发式算法。注意——是最有效的直接搜索算法,之后涌现了很多预处理算法(如ALT,CH,HL等等),在线查询效率是A*算法的数千甚至上万倍。

公式表示为: f(n)=g(n)+h(n),

其中, f(n) 是从初始状态经由状态n到目标状态的代价估计,

g(n) 是在状态空间中从初始状态到状态n的实际代价,

h(n) 是从状态n到目标状态的最佳路径的估计代价。

可以看出,A星算法比上述算法的最大的区别,就是多了这个 估价函数f(n) = 实际代价g(n ) + 估计代价h(n)

A*算法的好处如下:

其实A算法也是一种最好优先的算法

只不过要加上一些约束条件罢了。由于在一些问题求解时,我们希望能够求解出状态空间搜索的最短路径,也就是用最快的方法求解问题,A就是干这种事情的!

我们先下个定义,如果一个估价函数可以找出最短的路径,我们称之为可采纳性。A算法是一个可采纳的最好优先算法。A算法的估价函数可表示为:

f‘(n) = g‘(n) + h‘(n)

这里,f‘(n)是估价函数,g‘(n)是起点到节点n的最短路径值,h‘(n)是n到目标的最短路经的启发值。由于这个f‘(n)其实是无法预先知道的,所以我们用前面的估价函数f(n)做近似。g(n)代替g‘(n),但 g(n)>=g‘(n)才可(大多数情况下都是满足的,可以不用考虑),h(n)代替h‘(n),但h(n)<=h‘(n)才可(这一点特别的重要)。可以证明应用这样的估价函数是可以找到最短路径的,也就是可采纳的。我们说应用这种估价函数的最好优先算法就是A算法。

举一个例子,其实广度优先算法就是A算法的特例。其中g(n)是节点所在的层数,h(n)=0,这种h(n)肯定小于h‘(n),所以由前述可知广度优先算法是一种可采纳的。实际也是。当然它是一种最臭的A*算法。

再说一个问题,就是有关h(n)启发函数的信息性。h(n)的信息性通俗点说其实就是在估计一个节点的值时的约束条件,如果信息越多或约束条件越多则排除的节点就越多,估价函数越好或说这个算法越好。这就是为什么广度优先算法的不甚为好的原因了,因为它的h(n)=0,没有一点启发信息。但在游戏开发中由于实时性的问题,h(n)的信息越多,它的计算量就越大,耗费的时间就越多。就应该适当的减小h(n)的信息,即减小约束条件。但算法的准确性就差了,这里就有一个平衡的问题。

总结下来,其实就是A算法的重点为:在众多的估计代价函数中的最优的即为A(需证明是最优的)

有了以上的一些结论,可以迅速的改改上述代码,得到A*算法:

import time
import copy

class list():
    def __init__(self,info):
        self.info = info
        self.fn = 0   #估价:越小越好
        self.front = None

class Solution():
    def __init__(self):
        self.open = []
        self.closed = []
        self.co = 0
    #A算法的重点:估价函数f(n) = 实际代价g(n ) + 估计代价h(n)
    #A*算法的重点为:在众多的估计代价函数中的最优的即为A*(需证明是最优的)

    def Asearch(self,S0, Sg):
        head = list(S0)
        head.fn = self.getfn(head,Sg)
        self.open.append(head)
        while self.open:
            #对open表的全部节点按照fn从小到大排序~~~~
            self.open.sort(key=lambda ele:ele.fn)  #默认从小到大
            n = self.open.pop(0)
            self.co += 1
            print(‘取得节点n:‘,n.info)
            if n.info == Sg:
                print(‘得到问题的解!‘)
                print(‘一共进行了‘,self.co,‘次的查找‘)
                print(‘该问题的解为:‘) #对n进行
                while n:
                    print(n.info)
                    n = n.front
                return
            if n in self.closed:
                #节点判定是否为扩展问题
                print(‘该结点不可扩展‘)
            else:
                print(‘该节点可扩展‘)
                #扩展节点n,
                #将其子节点放入open的尾部,
                #为每一个子节点设置指向父节点的指针
                nkongdi, nkongdj = 0, 0
                for i in range(3):
                    for j in range(3):
                        if n.info[i][j] == -1:
                            nkongdi = i
                            nkongdj = j
                ln,un,rn,dn =copy.deepcopy(n.info),copy.deepcopy(n.info),copy.deepcopy(n.info),copy.deepcopy(n.info)
                if nkongdj != 0: #right
                    rn[nkongdi][nkongdj],rn[nkongdi][nkongdj-1] = rn[nkongdi][nkongdj-1],rn[nkongdi][nkongdj]
                    rn = self.link(n,rn)
                    if rn not in self.closed:
                        #计算子节点估值~~~
                        rn.fn = self.getfn(rn,Sg)
                        self.open.append(rn)
                if nkongdi != 0: #down
                    dn[nkongdi][nkongdj],dn[nkongdi-1][nkongdj] = dn[nkongdi-1][nkongdj],dn[nkongdi][nkongdj]
                    dn = self.link(n,dn)
                    if dn not in self.closed:
                        dn.fn = self.getfn(dn,Sg)
                        self.open.append(dn)
                if nkongdj != 2: #left
                    ln[nkongdi][nkongdj],ln[nkongdi][nkongdj+1] = ln[nkongdi][nkongdj+1],ln[nkongdi][nkongdj]
                    ln = self.link(n,ln)
                    if ln not in self.closed:
                        ln.fn = self.getfn(ln,Sg)
                        self.open.append(ln)
                if nkongdi != 2: #up
                    un[nkongdi][nkongdj],un[nkongdi+1][nkongdj] =  un[nkongdi+1][nkongdj],un[nkongdi][nkongdj]
                    un = self.link(n,un)
                    if un not in self.closed:
                        un.fn = self.getfn(un,Sg)
                        self.open.append(un)
            self.closed.append(n)

    def link(self, n ,willn):
        willnn = list(willn)
        willnn.front = n
        return willnn

    def h(self, ninfo,Sg):
        #将不再位的A算法和A*算法个数,作为启发信息
        enlight = 0
        for i in range(3):
            for j in range(3):
                if ninfo[i][j] != Sg[i][j]:
                    enlight += 1
        return enlight

    def g(self,n):
        #g(n) = d(n)
        depth = 0
        while n:
            #print(n.info)
            n = n.front
            depth += 1
        return depth

    def getfn(self, n ,Sg):
        #传入进的是一个listn
        return self.g(n) + self.h(n.info,Sg)

if __name__ == ‘__main__‘:
    S0 = [[2,8,3],
         [1,-1,4],
         [7,6,5]]
    S1 = [[1,2,3],
          [8,-1,4],
          [7,6,5]]
    Solution().Asearch(S0,S1)

可以看出A星算法最为重要的就是启发函数h(n)的选取,h(n)选取的好坏直接关系到了A星算法的好坏。

ps:

A*算法也有一些优化,有兴趣的同学也可以看一下- - ~

原文地址:https://www.cnblogs.com/whyaza/p/9683587.html

时间: 2024-08-29 22:29:43

八数码问题解析的相关文章

hdu 1034 Eight 传说中的八数码问题。真是一道神题,A*算法+康托展开

Eight Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total Submission(s): 13506    Accepted Submission(s): 3855 Special Judge Problem Description The 15-puzzle has been around for over 100 years; even if you don'

使用Muduo完成数独和八数码问题求解服务器

在剖析完Muduo网络库源码之后,我们试着完成一个高效的数独和八数码问题求解服务器. 先说说为什么要选择这两个问题?数独问题一直是陈硕老师很喜欢的问题,在muduo网络库中多次提到并有示例.八数码问题是我很喜欢的问题,所以在此综合完成求解数独和八数码问题的高效服务端程序. 编写这样一个看似简单的服务程序的技术含量远高于所谓的控件堆砌型开发,虽然有muduo网络库帮助我们处理网络事件,我们只需要关注setConnectionCallback() 和setMessageCallback() 事件,但

1225 八数码难题

1225 八数码难题 时间限制: 1 s 空间限制: 128000 KB 题目等级 : 钻石 Diamond 题解 查看运行结果 题目描述 Description Yours和zero在研究A*启发式算法.拿到一道经典的A*问题,但是他们不会做,请你帮他们.问题描述 在3×3的棋盘上,摆有八个棋子,每个棋子上标有1至8的某一数字.棋盘中留有一个空格,空格用0来表示.空格周围的棋子可以移到空格中.要求解的问题是:给出一种初始布局(初始状态)和目标布局(为了使题目简单,设目标状态为123804765

HDU 1043 Eight八数码解题思路(bfs+hash 打表 IDA* 等)

题目链接 https://vjudge.net/problem/HDU-1043 经典的八数码问题,学过算法的老哥都会拿它练搜索 题意: 给出每行一组的数据,每组数据代表3*3的八数码表,要求程序复原为初始状态 思路: 参加网站比赛时拿到此题目,因为之前写过八数码问题,心中暗喜,于是写出一套暴力bfs+hash,结果TLE呵呵 思路一:bfs+hash(TLE) 1 #include <cstdio> 2 #include <cstring> 3 #include <queu

洛谷【P1379】八数码难题

P1379 八数码难题 题目描述 在3×3的棋盘上,摆有八个棋子,每个棋子上标有1至8的某一数字.棋盘中留有一个空格,空格用0来表示.空格周围的棋子可以移到空格中.要求解的问题是:给出一种初始布局(初始状态)和目标布局(为了使题目简单,设目标状态为123804765),找到一种最少步骤的移动方法,实现从初始布局到目标布局的转变. 输入输出格式 输入格式: 输入初试状态,一行九个数字,空格用0表示 输出格式: 只有一行,该行只有一个数字,表示从初始状态到目标状态需要的最少移动次数(测试数据中无特殊

八数码三种用时差距极大的写法

进化史,一种比一种长,一种比一种快.不过第三种似乎还不是最终形态. 第一种,傻逼级迭代加深. 去年十一月写的,那时候刚刚学迭代加深,敲了一个钟头才敲完,codevs上直接过,就没太管,觉得这是个水题.实际上呢,看后文. 1 #include<algorithm> 2 #include<iostream> 3 #include<cstring> 4 #include<cstdio> 5 using namespace std; 6 int sx,sy,lim,

八数码的八境界 [转载]

八数码的八境界   研究经典问题,空说不好,我们拿出一个实际的题目来演绎.八数码问题在北大在线测评系统中有一个对应的题,题目描述如下: Eight Time Limit: 1000MS    Memory Limit: 65536K  Special Judge Description The 15-puzzle has been aroundfor over 100 years; even if you don't know it by that name, you've seen it. I

【转】八数码问题及A*算法

一.八数码问题八数码问题也称为九宫问题.在3×3的棋盘,摆有八个棋子,每个棋子上标有1至8的某一数字,不同棋子上标的数字不相同.棋盘上还有一个空格,与空格相邻的棋子可以移到空格中.要求解决的问题是:给出一个初始状态和一个目标状态,找出一种从初始转变成目标状态的移动棋子步数最少的移动步骤.所谓问题的一个状态就是棋子在棋盘上的一种摆法.棋子移动后,状态就会发生改变.解八数码问题实际上就是找出从初始状态到达目标状态所经过的一系列中间过渡状态.八数码问题一般使用搜索法来解.搜索法有广度优先搜索法.深度优

HDU 3567 Eight II(八数码 II)

p.MsoNormal { margin: 0pt; margin-bottom: .0001pt; text-align: justify; font-family: Calibri; font-size: 10.5000pt } h1 { margin-top: 5.0000pt; margin-bottom: 5.0000pt; text-align: center; font-family: 宋体; color: rgb(26,92,200); font-weight: bold; fo