2024人工智能结课作业-DFS/BFS/Astar解决数码问题

news2024/9/23 12:10:31

1 深度优先遍历搜索(DFS)
1.1算法介绍
深度优先搜索算法(Depth-First-Search,DFS)是一种用于遍历或搜索树或图的算法。沿着树的深度遍历树的节点,尽可能深的搜索树的分支。当节点v的所在边都己被探寻过,搜索将回溯到发现节点v的那条边的起始节点。这一过程一直进行到已发现从源节点可达的所有节点为止。如果还存在未被发现的节点,则选择其中一个作为源节点并重复以上过程,整个进程反复进行直到所有节点都被访问为止。属于盲目搜索。

以上图为例,简述DFS的过程。首先从根节点"1"出发,按一定的顺序遍历其子节点,这里我们假设优先遍历左边的。所以,在遍历"1"之后,我们到了节点"2",此时"2"仍有子节点,所以应继续向下遍历,下一个节点是"3",然后是"4"。到了"4"之后,没有子节点了,说明我们已经将这一条路遍历完了,接着我们应该回溯,应该回到"4"的父节点,也就是"3"。因为"3"还有一个子节点"5"没有遍历,所以下一个我们应该遍历的是"5"。遍历完"5"之后又发现一条路到头了,再次回溯依然回溯到其父节点"3",此时"3"的所有子节点都已经遍历完了,因该接着回溯到"3"的父节点"2",然后检查"2"是否有没有遍历完的子节点。按照这样的规则,完成所有节点的遍历。最终得到的遍历顺序是"1-2-3-4-5-6-7-8-9-10-11-12"

在介绍了DFS在遍历树的应用后,我们将其应用于八数码问题的解决。八数码问题也称为九宫问题。在3×3的棋盘,摆有八个棋子,每个棋子上标有1至8的某一数字,不同棋子上标的数字不相同。棋盘上还有一个空格,与空格相邻的棋子可以移到空格中。要求解决的问题是:给出一个初始状态和一个目标状态,找出一种从初始转变成目标状态的移动棋子步数最少的移动步骤。所谓问题的一个状态就是棋子在棋盘上的一种摆法。棋子移动后,状态就会发生改变。解八数码问题实际上就是找出从初始状态到达目标状态所经过的一系列中间过渡状态。

上面说的DFS遍历的树是已经存在的,我们只需要按照规定的遍历方法就能完成遍历,而对于八数码问题,没有已经存在的路径供我们遍历,需要我们从初始状态向下延伸(也就是上下左右移动)才能构造出类似的树。

以上图为例。在使用DFS进行搜索时,每个状态都会按照一定的顺序进行上下左右移动(在上图中是下、左、右、上的顺序),一次移动后会产生一个新的状态,然后以新状态为起点继续按约定的顺序(例如先向下)移动。终止的条件是找到解或者达到深度界限。那么如果按照图中下、左、右、上的顺序搜索后的结果将会是最左边的一条路一直是优先向下移动,如果不能向下则依次会是左、右、上的一种。

1.2实验代码
 

import copy
#棋盘的类,实现移动和扩展状态
class grid:
    def __init__(self, stat):
        self.pre = None
        self.target = [[1, 2, 3], [8, 0, 4], [7, 6, 5]]
        self.stat = stat
        self.find0()
        self.update()
    #更新深度和距离和
    def update(self):
        self.fH()
        self.fG()

    # G是深度,也就是走的步数
    def fG(self):
        if (self.pre != None):
            self.G = self.pre.G + 1
        else:
            self.G = 0

    # H是和目标状态距离之和,可以用来判断是否找到解
    def fH(self):

        self.H = 0
        for i in range(3):
            for j in range(3):
                targetX = self.target[i][j]
                nowP = self.findx(targetX)
                self.H += abs(nowP[0] - i) + abs(nowP[1] - j)

    #以三行三列的形式输出当前状态
    def see(self):
        print("depth:", self.G)
        for i in range(3):
            print(self.stat[i])
        print("-" * 10)

    # 查看找到的解是如何从头移动的
    def seeAns(self):
        ans = []
        ans.append(self)
        p = self.pre
        while (p):
            ans.append(p)
            p = p.pre
        ans.reverse()
        for i in ans:
            i.see()
    #找到数字x的位置
    def findx(self, x):
        for i in range(3):
            if (x in self.stat[i]):
                j = self.stat[i].index(x)
                return [i, j]
    #找到0的位置,也就是空白格的位置
    def find0(self):
        self.zero = self.findx(0)

    #对当前状态进行扩展,也就是上下左右移动,返回的列表中是状态的二维列表,不是对象
    def expand(self):
        i = self.zero[0]
        j = self.zero[1]
        gridList = []
        if (j == 2 or j == 1):
            gridList.append(self.left())
        if (i == 2 or i == 1):
            gridList.append(self.up())
        if (i == 0 or i == 1):
            gridList.append(self.down())
        if (j == 0 or j == 1):
            gridList.append(self.right())
        return gridList

    # deepcopy多维列表的复制,防止指针赋值将原列表改变
    # move只能移动行或列,即row和col必有一个为0
    #对当前状态进行移动的函数
    def move(self, row, col):
        newStat = copy.deepcopy(self.stat)
        tmp = self.stat[self.zero[0] + row][self.zero[1] + col]
        newStat[self.zero[0]][self.zero[1]] = tmp
        newStat[self.zero[0] + row][self.zero[1] + col] = 0
        return newStat

    def up(self):
        return self.move(-1, 0)

    def down(self):
        return self.move(1, 0)

    def left(self):
        return self.move(0, -1)

    def right(self):
        return self.move(0, 1)

# 判断状态g是否在状态集合中,g是对象,gList是对象列表
#返回的结果是一个列表,第一个值是真假,如果是真则第二个值是g在gList中的位置索引
def isin(g, gList):
    gstat = g.stat
    statList = []
    for i in gList:
        statList.append(i.stat)
    if (gstat in statList):
        res = [True, statList.index(gstat)]
    else:
        res = [False, 0]
    return res

#计算逆序数之和
def N(nums):
    N=0
    for i in range(len(nums)):
        if(nums[i]!=0):
            for j in range(i):
                if(nums[j]>nums[i]):
                    N+=1
    return N

#根据逆序数之和判断所给八数码是否可解
def judge(src,target):
    N1=N(src)
    N2=N(target)
    if(N1%2==N2%2):
        return True
    else:
        return False

#初始状态
startStat = [[2, 8, 3], [1, 0, 4], [7, 6, 5]]
g = grid(startStat)
#判断所给的八数码受否有解
if(judge(startStat,g.target)!=True):
    print("所给八数码无解,请检查输入")
    exit(1)
#visited储存的是已经扩展过的节点
visited = []
time = 0
#用递归的方式进行DFS遍历
def DFSUtil(v, visited):
    global time
    #判断是否达到深度界限
    if (v.G > 4):
        return
    time+=1
    #判断是否已经找到解
    if (v.H == 0):
        print("found and times", time, "moves:", v.G)
        v.seeAns()
        exit(1)

    #对当前节点进行扩展
    visited.append(v.stat)
    expandStats = v.expand()
    w = []
    for stat in expandStats:
        tmpG = grid(stat)
        tmpG.pre = v
        tmpG.update()
        if (stat not in visited):
            w.append(tmpG)
    for vadj in w:
        DFSUtil(vadj, visited)
    #visited查重只对一条路,不是全局的,每条路开始时都为空
    #因为如果全局查重,会导致例如某条路在第100层找到的状态,在另一条路是第2层找到也会被当做重复
    #进而导致明明可能会找到解的路被放弃
    visited.pop()

DFSUtil(g, visited)
#如果找到解程序会在中途退出,走到下面这一步证明没有找到解
print("在当前深度下没有找到解,请尝试增加搜索深度")
 

1.3实验结果
以下面这个八数码为例,用DFS进行搜索。

将找出的解从初始状态一步一步输出到解状态。

可以看出总共进行了15次遍历,在某一条路的第4层找到了解。

下面我们来看一看DFS的所有15次遍历,以此来更深入的理解DFS的原理。稍微对代码进行改动,使其输出遍历次数和当前层数。由于结果太长,为了方便展示,下面将以树的形式展示。

上面输出的解就是按照红色路线标注找到的,从遍历次数可以看出DFS是一条道走到黑的找法,因为设置的深度界限是4,所以每一条路最多找到第4层。

1.4实验总结
1、为什么要设置深度界限?

因为理论上我们只需要一条路就可以找到解,只要不停地向下扩展就可以了。而这样做的缺点是会绕远路,也许第一条路找到第100层才找到解,但第二条路找两层就能找到解。从DFS的原理出发,我们不难看出这一点。还有一个问题是其状态数太多了,在不设置深度界限的情况下经常出现即使程序的栈满了依然没有找到解的情况。所以理论只是理论,在坚持"一条道走到黑"时,很可能因为程序"爆栈"而走到了黑还是没有找到解。

2、如何进行回溯?

在八数码问题中,我们回溯的条件只有一个,就是达到深度界限了。因为在找到解时会退出,找不到时会继续向下扩展。回溯的过程是先回溯到父节点,检查父节点是否还能扩展其他节点,如果能,就扩展新的节点并继续向下搜索,如果不能则递归地继续向上回溯。

3、出现重复状态怎么解决?

不难想出假如按照下、左、右、上这样地顺序进行搜索时,在第三层时就会出现和初始状态相同的情况。因为第二层向一个方向移动,第三层会有一个向反方向移动的状态也就是回到初始状态了。这样不仅增加了运算量,而且没有意义,会出现很多冗余步骤。所以我们应该设置一个查重的表,将已经遍历过的状态存入这个表中,当再次遇到这种情况时我们就跳过。

那么这个查重的表是该对于全局而言呢,还是每条路的查重表是独立的呢?在经过很多测试之后,我发现这个查重表对每条路独立是更好的。因为在一条路上出现的状态所需要的步数和另一条需要的步数不一定相同,也就是说我在第一条路上的第100层找到了某个状态,放入了查重表中,但是这个状态可能在另一条路上第2层就能找到,或许再下面几层就能找到解了,可是由于被放进了全局查重表中而放弃了这个条路的扩展,也损失了更快找到解的机会。所以一条路一个查重表是好的。

4、由于需要设置深度界限,每条路都会在深度界限处截至,而如果所给的八数码的最优解大于深度界限,就会出现遍历完所有情况都找不解。而在事先不知道最优解的深度的情况下这个深度界限很难确定,设置大了会增大搜索时间,设置小了会找不到解。这也是DFS的一个缺点。

5、DFS不一定能找到最优解。因为深度界限的原因,找到的解可能在最优解和深度界限之间。

2 广度优先遍历搜索(BFS)
2.1算法介绍
广度优先搜索算法(英语:Breadth-First-Search,缩写为BFS),是一种图形搜索算法。简单的说,BFS是从根节点开始,沿着树的宽度遍历树的节点。如果所有节点均被访问,则算法中止。BFS是一种盲目搜索法,目的是系统地展开并检查图中的所有节点,以找寻结果。

BFS会先访问根节点的所有邻居节点,然后再依次访问邻居节点的邻居节点,直到所有节点都访问完毕。在具体的实现中,使用open和closed两个表,open是一个队列,每次对open进行一次出队操作(并放入closed中),并将其邻居节点进行入队操作。直到队列为空时即完成了所有节点的遍历。closed表在遍历树时其实没有用,因为子节点只能从父节点到达。但在进行图的遍历时,一个节点可能会由多个节点到达,所以此时为了防止重复遍历应该每次都检查下一个节点是否已经在closed中了。

依然使用上面的这个例子,如果使用BFS进行遍历,那么节点的访问顺序是"1-2-7-8-3-6-9-12-4-5-10-11"。可以看出来BFS进行遍历时是一层一层的搜索的。

在应用BFS算法进行八数码问题搜索时需要open和closed两个表。首先将初始状态加入open队列,然后进行出队操作并放入closed中,对出队的状态进行扩展(所谓扩展也就是找出其上下左右移动后的状态),将扩展出的状态加入队列,然后继续循环出队-扩展-入队的操作,直到找到解为止。

上图这个例子中,红圈里的数字是遍历顺序。当找到解时一直往前找父节点即可找出求解的移动路线。

2.2实验代码
 

import copy
#棋盘的类,实现移动和扩展状态
class grid:
    def __init__(self,stat):
        self.pre=None
        self.target=[[1,2,3],[8,0,4],[7,6,5]]
        self.stat=stat
        self.find0()
        self.update()
    #更新深度和距离和
    def update(self):
        self.fH()
        self.fG()
    #G是深度,也就是走的步数
    def fG(self):
        if(self.pre!=None):
            self.G=self.pre.G+1
        else:
            self.G=0
    #H是和目标状态距离之和,可以用来判断是否达到最优解
    def fH(self):
        self.H=0
        for i in range(3):
            for j in range(3):
                targetX=self.target[i][j]
                nowP=self.findx(targetX)
                self.H+=abs(nowP[0]-i)+abs(nowP[1]-j)
    #查看当前状态
    def see(self):
        print("depth:",self.G)
        for i in range(3):
             print(self.stat[i])
        print("-"*10)
    #查看找到的解是如何从头移动的
    def seeAns(self):
        ans=[]
        ans.append(self)
        p=self.pre
        while(p):
            ans.append(p)
            p=p.pre
        ans.reverse()
        for i in ans:
            i.see()
    #找到数字x的位置,返回其坐标
    def findx(self,x):
        for i in range(3):
            if(x in self.stat[i]):
                j=self.stat[i].index(x)
                return [i,j]
    #找到0,也就是空白格的位置
    def find0(self):
            self.zero=self.findx(0)
    #对当前状态进行所有可能的扩展,返回一个扩展状态的列表
    def expand(self):
        i=self.zero[0]
        j=self.zero[1]
        gridList=[]

        if(j==2 or j==1):
            gridList.append(self.left())
        if(i==2 or i==1):
            gridList.append(self.up())
        if(i==0 or i==1):
            gridList.append(self.down())
        if(j==0 or j==1):
            gridList.append(self.right())

        return gridList


    #deepcopy多维列表的复制,防止指针赋值将原列表改变
    #move只能移动行或列,即row和col必有一个为0
    #对当前状态进行移动
    def move(self,row,col):
        newStat=copy.deepcopy(self.stat)
        tmp=self.stat[self.zero[0]+row][self.zero[1]+col]
        newStat[self.zero[0]][self.zero[1]]=tmp
        newStat[self.zero[0]+row][self.zero[1]+col]=0
        return newStat

    def up(self):
        return self.move(-1,0)

    def down(self):
        return self.move(1,0)

    def left(self):
        return self.move(0,-1)

    def right(self):
        return self.move(0,1)


#计算逆序数之和
def N(nums):
    N=0
    for i in range(len(nums)):
        if(nums[i]!=0):
            for j in range(i):
                if(nums[j]>nums[i]):
                    N+=1
    return N

#根据逆序数之和判断所给八数码是否可解
def judge(src,target):
    N1=N(src)
    N2=N(target)
    if(N1%2==N2%2):
        return True
    else:
        return False

#初始化状态
startStat=[[2,8,3],[1,0,4],[7,6,5]]
g=grid(startStat)
if(judge(startStat,g.target)!=True):
    print("所给八数码无解,请检查输入")
    exit(1)

visited=[]
queue=[g]
time=0
while(queue):
    time+=1
    v=queue.pop(0)
    #判断是否找到解
    if(v.H==0):
        print("found and times:",time,"moves:",v.G)
        #查看找到的解是如何从头移动的
        v.seeAns()
        break
    else:
        #对当前状态进行扩展
        visited.append(v.stat)
        expandStats=v.expand()
        for stat in expandStats:
            tmpG=grid(stat)
            tmpG.pre=v
            tmpG.update()
            if(stat not in visited):
                queue.append(tmpG)
 

2.3实验结果
仍然用相同的例子,用BFS进行搜索。

将找出的解从初始状态一步一步输出到解状态。

从结果中可以看出总共进行了27次遍历,并在第4层时找到了解状态。

下面我们来看一看BFS的所有27次遍历,以此来更深入的理解BFS的原理。稍微对代码进行改动,使其输出遍历次数和当前层数。由于结果太长,为了方便展示,下面将以树的形式展示。

上面输出的解就是按照红色路线标注找到的,从遍历次数可以看出是一层一层的找。

2.4实验总结
由于BFS是一层一层找的,所以一定能找到解,并且是最优解。虽然能找到最优解,但它的盲目性依然是一个很大的缺点。从上面的遍历树状图中,每一层都比上一层元素更多,且是近似于指数型的增长。也就是说,深度每增加一,这一层的搜索速度就要增加很多。

3 A*算法实现8数码问题
3.1算法介绍
Astar算法是一种求解最短路径最有效的直接搜索方法,也是许多其他问题的常用启发式算法。它的启发函数为f(n)=g(n)+h(n),其中,f(n) 是从初始状态经由状态n到目标状态的代价估计,g(n) 是在状态空间中从初始状态到状态n的实际代价,h(n) 是从状态n到目标状态的最佳路径的估计代价。

h(n)是启发函数中很重要的一项,它是对当前状态到目标状态的最小代价h*(n)的一种估计,且需要满足

h(n)<=h*(n)

也就是说h(n)是h*(n)的下界,这一要求保证了Astar算法能够找到最优解。这一点很容易想清楚,因为满足了这一条件后,启发函数的值总是小于等于最优解的代价值,也就是说寻找过程是在朝着一个可能是最优解的方向或者是比最优解更小的方向移动,如果启发函数值恰好等于实际最优解代价值,那么搜索算法在一直尝试逼近最优解的过程中会找到最优解;如果启发函数值比最优解的代价要低,虽然无法达到,但是因为方向一致,会在搜索过程中发现最优解。

h是由我们自己设计的,h函数设计的好坏决定了Astar算法的效率。h值越大,算法运行越快。但是在设计评估函数时,需要注意一个很重要的性质:评估函数的值一定要小于等于实际当前状态到目标状态的代价。否则虽然程序运行速度加快,但是可能在搜索过程中漏掉了最优解。相对的,只要评估函数的值小于等于实际当前状态到目标状态的代价,就一定能找到最优解。所以,在这个问题中我们可以将评估函数设定为1-8八数字当前位置到目标位置的曼哈顿距离之和。

Astar算法与BFS算法的不同之处在于每次会根据启发函数的值来进行排序,每次先出队的是启发函数值最小的状态。

Astar算法可以被认为是Dijkstra算法的扩展。Dijkstra算法在搜索最短距离时是已知了各个节点之间的距离,而对于Astar而言,这个已知的距离被启发函数值替换。

3.2实验代码
 

import copy
#棋盘的类,实现移动和扩展状态
class grid:
    def __init__(self,stat):
        self.pre=None
        #目标状态
        self.target=[[1,2,3],[8,0,4],[7,6,5]]
        #stat是一个二维列表
        self.stat=stat
        self.find0()
        self.update()
    #更新启发函数的相关信息
    def update(self):
        self.fH()
        self.fG()
        self.fF()

    #G是深度,也就是走的步数
    def fG(self):
        if(self.pre!=None):
            self.G=self.pre.G+1
        else:
            self.G=0

    #H是和目标状态距离之和
    def fH(self):
        self.H=0
        for i in range(3):
            for j in range(3):
                targetX=self.target[i][j]
                nowP=self.findx(targetX)
                #曼哈顿距离之和
                self.H+=abs(nowP[0]-i)+abs(nowP[1]-j)

    #F是启发函数,F=G+H
    def fF(self):
        self.F=self.G+self.H

    #以三行三列的形式输出当前状态
    def see(self):
        for i in range(3):
             print(self.stat[i])
        print("F=",self.F,"G=",self.G,"H=",self.H)
        print("-"*10)

    #查看找到的解是如何从头移动的
    def seeAns(self):
        ans=[]
        ans.append(self)
        p=self.pre
        while(p):
            ans.append(p)
            p=p.pre
        ans.reverse()
        for i in ans:
            i.see()

    #找到数字x的位置
    def findx(self,x):
        for i in range(3):
            if(x in self.stat[i]):
                j=self.stat[i].index(x)
                return [i,j]

    #找到0,也就是空白格的位置
    def find0(self):
            self.zero=self.findx(0)

    #扩展当前状态,也就是上下左右移动。返回的是一个状态列表,也就是包含stat的列表
    def expand(self):
        i=self.zero[0]
        j=self.zero[1]
        gridList=[]
        if(j==2 or j==1):
            gridList.append(self.left())
        if(i==2 or i==1):
            gridList.append(self.up())
        if(i==0 or i==1):
            gridList.append(self.down())
        if(j==0 or j==1):
            gridList.append(self.right())
        return gridList


    #deepcopy多维列表的复制,防止指针赋值将原列表改变
    #move只能移动行或列,即row和col必有一个为0
    #向某个方向移动
    def move(self,row,col):
        newStat=copy.deepcopy(self.stat)
        tmp=self.stat[self.zero[0]+row][self.zero[1]+col]
        newStat[self.zero[0]][self.zero[1]]=tmp
        newStat[self.zero[0]+row][self.zero[1]+col]=0
        return newStat

    def up(self):
        return self.move(-1,0)

    def down(self):
        return self.move(1,0)

    def left(self):
        return self.move(0,-1)

    def right(self):
        return self.move(0,1)

#判断状态g是否在状态集合中,g是对象,gList是对象列表
#返回的结果是一个列表,第一个值是真假,如果是真则第二个值是g在gList中的位置索引
def isin(g,gList):
    gstat=g.stat
    statList=[]
    for i in gList:
        statList.append(i.stat)
    if(gstat in statList):
        res=[True,statList.index(gstat)]
    else:
        res=[False,0]
    return res

#计算逆序数之和
def N(nums):
    N=0
    for i in range(len(nums)):
        if(nums[i]!=0):
            for j in range(i):
                if(nums[j]>nums[i]):
                    N+=1
    return N

#根据逆序数之和判断所给八数码是否可解
def judge(src,target):
    N1=N(src)
    N2=N(target)
    if(N1%2==N2%2):
        return True
    else:
        return False

#Astar算法的函数
def Astar(startStat):
    #open和closed存的是grid对象
    open=[]
    closed=[]
    #初始化状态
    g=grid(startStat)
    #检查是否有解
    if(judge(startStat,g.target)!=True):
        print("所给八数码无解,请检查输入")
        exit(1)

    open.append(g)
    #time变量用于记录遍历次数
    time=0
    #当open表非空时进行遍历
    while(open):
        #根据启发函数值对open进行排序,默认升序
        open.sort(key=lambda G:G.F)
        #找出启发函数值最小的进行扩展
        minFStat=open[0]
        #检查是否找到解,如果找到则从头输出移动步骤
        if(minFStat.H==0):
            print("found and times:",time,"moves:",minFStat.G)
            minFStat.seeAns()
            break

        #走到这里证明还没有找到解,对启发函数值最小的进行扩展
        open.pop(0)
        closed.append(minFStat)
        expandStats=minFStat.expand()
        #遍历扩展出来的状态
        for stat in expandStats:
            #将扩展出来的状态(二维列表)实例化为grid对象
            tmpG=grid(stat)
            #指针指向父节点
            tmpG.pre=minFStat
            #初始化时没有pre,所以G初始化时都是0
            #在设置pre之后应该更新G和F
            tmpG.update()
            #查看扩展出的状态是否已经存在与open或closed中
            findstat=isin(tmpG,open)
            findstat2=isin(tmpG,closed)
            #在closed中,判断是否更新
            if(findstat2[0]==True and tmpG.F<closed[findstat2[1]].F):
                closed[findstat2[1]]=tmpG
                open.append(tmpG)
                time+=1
            #在open中,判断是否更新
            if(findstat[0]==True and tmpG.F<open[findstat[1]].F):
                open[findstat[1]]=tmpG
                time+=1
            #tmpG状态不在open中,也不在closed中
            if(findstat[0]==False and findstat2[0]==False):
                open.append(tmpG)
                time+=1

stat=[[2, 8, 3], [1, 0 ,4], [7, 6, 5]]
Astar(stat)
 

3.3实验结果
仍然用相同的例子,用Astar进行搜索。

将找出的解从初始状态一步一步输出到解状态。

从结果中可以看出总共进行了9次遍历,并在第4层时找到了解状态。

下面我们来看一看Astar的所有9次遍历,以此来更深入的理解Astar的原理。稍微对代码进行改动,使其输出遍历次数和当前状态的启发信息(其中F是启发值,G是当前深度,H是不在位棋子的曼哈顿距离之和)。由于结果太长,为了方便展示,下面将以树的形式展示。

上面输出的解就是按照红色路线标注找到的,从遍历次数和相应状态的启发信息可以看出每次对启发函数值最小的状态进行扩展,依次进行搜索。

3.4实验总结
从三个算法的遍历次数可以看出Astar算法更加优秀,能够更快的找到解。但是因为上面给出的八数码题目太简单了,只需要4步就能解决问题,所以看起来优势没有那么明显。下面我们选择另一个比较难的,需要更多移动步数的题目,以此来体现启发式搜索相较于盲目搜索的优越性。用三种算法搜索下面八数码的解。

下面是比较的结果

可以看出来Astar在一百次遍历之内就找到了解,而另外两个盲目搜索算法则需要几千次才搜索到。

 https://blog.csdn.net/m0_62554628/article/details/141576880?spm=1001.2100.3001.7377&utm_medium=distribute.pc_feed_blog_category.none-task-blog-classify_tag-6-141576880-null-null.nonecase&depth_1-utm_source=distribute.pc_feed_blog_category.none-task-blog-classify_tag-6-141576880-null-null.nonecase

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2157486.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于SSM的“在线CRM管理系统”的设计与实现(源码+数据库+文档+开题报告)

基于SSM的“在线CRM管理系统”的设计与实现&#xff08;源码数据库文档开题报告) 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SSM 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 总体功能模块图 登录页面 后台管理页面 产品信息页面 客…

【Android】BottomSheet基本用法总结(BottomSheetDialog,BottomSheetDialogFragment)

BottomSheet BottomSheet 是一种位于屏幕底部的面板&#xff0c;用于显示附加内容或选项。提供了从屏幕底部向上滑动显示内容的交互方式。这种设计模式在 Material Design 中被广泛推荐&#xff0c;因为它可以提供一种优雅且不干扰主屏幕内容的方式来展示额外信息或操作。 具体…

大型综合医院供配电系统设计

摘要&#xff1a;众所周知&#xff0c;医院供配电系统关系着整个医院的正常运行&#xff0c;一旦出现故障将会对病患的安全造成威胁。基于此&#xff0c;本文就大型综合医院供配电系统设计进行深入探讨&#xff0c;以期为其他类似医院建筑供配电系统设计提供借鉴。 关键词&…

代理模式详解:控制对象访问的利器

代理模式&#xff08;Proxy Pattern&#xff09;是一种结构型设计模式&#xff0c;它为其他对象提供一种代理以控制对这个对象的访问。代理模式通常用于延迟对象的创建、控制对对象的访问或在访问对象时添加一些额外的操作。 代理模式通过引入一个代理对象来控制对目标对象的访…

超详细超实用!!!AI编程之cursor配置中文(一)

云风网 云风笔记 云风知识库 一、配置中文扩展 打开扩展&#xff0c;搜索chinese,安装chinese(simplified) 简体中文语言包,重启即可 二、配置中文方法二 使用快捷键组合【CtrlShiftp】&#xff0c;在搜索框中输入configure display language,选择中文也可配置中文 三、配置…

九、成功版--windows上安装artifactory配置postgressql

centos上搞不定&#xff0c;windows上搞定了 现阶段是想用java写程序控制制品库&#xff0c;等以后研究多了需要写一些脚本的时候&#xff0c;在研究linux上安装artifactory&#xff08;公司就用的linux安装的配置mysql&#xff0c;有空对着配一下linux的&#xff09; 源码地…

【实证数据】国家信息消费试点城市(2000年-2023年)

数据说明&#xff1a;2013年&#xff0c;国务院发布《促进信息消费若干意见》&#xff0c;旨在激发需求、拓宽市场及丰富服务内容。随后&#xff0c;工信部启动信息消费试点城市建设&#xff0c;分两批确立104个试点区域&#xff0c;旨在通过试点引领&#xff0c;促进信息消费增…

STM32—I2C通信外设

1.I2C外设简介 STM32内部集成了硬件I2C收发电路&#xff0c;可以由硬件自动执行时钟生成、起始终止条件生成、应答位收发、数据收发等功能&#xff0c;减轻CPU的负担支持多主机模型&#xff08;可变多主机&#xff09;支持7位/10位地址模式&#xff08;11110......)支持不同的通…

2024好评的开放式耳机排行榜10强?五款开放式蓝牙耳机推荐!

​开放式耳机目前非常流行&#xff0c;它们的设计不侵入耳道&#xff0c;长时间佩戴也不会感到不适&#xff0c;同时还能维护耳部卫生&#xff0c;这使得它们特别受到运动爱好者和耳机发烧友的喜爱。然而&#xff0c;市场上的开放式耳机品牌众多&#xff0c;质量参差不齐&#…

对HttpServletRequest中的Header进行增删

HttpServletRequest 没有提供修改/删除的 Api HttpServletRequest中定义的对 Header 的操作全是只读&#xff0c;没有修改。代码实现如下&#xff1a; public interface HttpServletRequest extends ServletRequest {...public long getDateHeader(String name);public String…

Isaac Sim 4.2.0 Windows版本打开报 fbgemm.dll 加载错误

方案一&#xff1a;下载缺少的dll复制到目录里即可 可以看到后台命令窗口出现了错误&#xff0c;发生在import pytorch的时候&#xff0c;根据提示&#xff0c;是因为fbgemm.dll缺少依赖&#xff0c;导致加载异常&#xff0c;一般情况是缺少 libomp140.x86_64.dll 这个文件&am…

前端工程化4:从0到1构建完整的前端监控平台

前言 一套完整的前端监控系统的主要部分&#xff1a; 数据上报方式数据上送时机性能数据采集错误数据采集用户行为采集定制化指标监控sdk 监控的目的&#xff1a; 一、数据上报方式 本文的方案是&#xff0c;优先navigator.sendBeacon&#xff0c;降级使用1x1像素gif图片…

C语言 | Leetcode C语言题解之第419题棋盘上的战舰

题目&#xff1a; 题解&#xff1a; int countBattleships(char** board, int boardSize, int* boardColSize){int row boardSize;int col boardColSize[0];int ans 0;for (int i 0; i < row; i) {for (int j 0; j < col; j) {if (board[i][j] X) {if (i > 0 &…

手机在网状态查询接口如何用PHP进行调用?

一、什么是手机在网状态查询接口&#xff1f; 手机在网状态查询接口&#xff0c;即输入手机号码查询手机号在网状态&#xff0c;返回有正常使用、停机、在网但不可用、不在网&#xff08;销号/未启用/异常&#xff09;、预销户等多种状态。 二、手机在网状态查询适用哪些场景…

C++ | Leetcode C++题解之第429题N叉树的层序遍历

题目&#xff1a; 题解&#xff1a; class Solution { public:vector<vector<int>> levelOrder(Node* root) {if (!root) {return {};}vector<vector<int>> ans;queue<Node*> q;q.push(root);while (!q.empty()) {int cnt q.size();vector<…

Comfyui海报工作流:出图快,质量高!

前言 工作流获取方式放在这里了 在快节奏的现代生活中&#xff0c;高效的工作流程对于企业和个人而言&#xff0c;无疑是提升竞争力的关键。 特别是在设计领域&#xff0c;能够快速而精准地完成海报设计&#xff0c;不仅意味着时间的节省&#xff0c;更代表着工作效率的飞跃。…

分治(快排系列)

一、基本思路 传统快排只是把数组分成两段进行排序&#xff0c;但是这样遇到重复数字多的数组就会超时&#xff0c;所以引入数组分三块&#xff1a; 流程&#xff1a; 1、初始化&#xff1a;l -1, r nums.size(); 2、先随机数选出 key 作为基准元素。srand(time(NULL)); in…

路径处理 | 关键点提取之Douglas–Peucker算法(附ROS C++/Python实现)

目录 0 专栏介绍1 路径关键点提取2 道格拉斯-普克算法Douglas–Peucker3 算法实现与可视化3.1 ROS C仿真3.2 Python仿真 0 专栏介绍 &#x1f525;课设、毕设、创新竞赛必备&#xff01;&#x1f525;本专栏涉及更高阶的运动规划算法轨迹优化实战&#xff0c;包括&#xff1a;…

深入理解并发原子性、可见性、有序性与JMM内存模型

1. 并发三大特性 并发编程Bug的源头&#xff1a;原子性、可见性和有序性问题 1.1 原子性 一个或多个操作&#xff0c;要么全部执行且在执行过程中不被任何因素打断&#xff0c;要么全部不执行。在 Java 中&#xff0c;对基本数据类型的变量的读取和赋值操作是原子性操作&…

HTB-GreenHorn 靶机笔记

GreenHorn 靶机笔记 概述 GreenHorn 是 HTB 上的一个 linux easy 难度的靶机&#xff0c;主要是通过信息搜集和代码审计找到对我们有用的信息。其中还包含了对pdf文件的修复技术 靶机地址&#xff1a;https://app.hackthebox.com/machines/GreenHorn 一丶 nmap 扫描 1&…