润哥人工智能-启发式搜索-A*算法

发布于 2021-06-28  138 次阅读


启发性信息和评估函数

如果在选择节点时能充分利用与问题有关的特征信息,估计出节点的重要性,就能在搜索时选择重要性较高的节点,以利于求得最优解。这个过程称为启发式搜索

与被解问题的某些特征值有关的控制信息(如解的出现规律、解的结构特征等)称为搜索的启发信息。它反映在评估函数中,评估函数的作用是评估待扩展各节点在问题求解中的价值,即评估节点的重要性。

评估函数 : f(x) = g(x) + h(x)

g(x)是从初始节点到一个节点x的实际代价。

h(x)是这个节点x到目标节点的最优路径的估计代价,体现了问题的启发式信息,h(x)称为启发式函数。

启发式搜索算法 A算法

如果OPEN表是用评估函数f(x)来排序,找到最先/优/小出表的节点,就称为A算法。

搜索算法的可采纳性(Admissibility)A*算法

如果搜索法总能找到最短(代价最小)的解答路径,则称算法具有可采纳性。宽度优先搜索就是可采纳的,只不过效率不高。

可采纳性/可容纳性/可容

h(n) <= h’(n)

其中h’(n)是实际代价,h(n)是估计代价 – 保证了可采纳性

A*算法的步骤为:

1.把S放人OPEN表,记f=h,令CLOSE为空表。

2.若OPEN为空表,则宣告失败并退出 。

3.选取OPEN 表中未设置过的具有最小f值的节点为最佳节点BESTNODE,并把它放人CLOSE表。

4.若BESTNODE为一目标节点,则成功求得一解并退出。

5.若BESTNODE不是目标节点,则扩展之,产生后继节点SUCCSSOR。

6.对每个SUCCSSOR进行下列过程:

  • ①建立从SUCCSSOR返回 BESTNODE的指针;
  • ②计算g(SUC)=g(BES)+g(BES,SUC);
  • ③如果SUCCSSOR ∈ OPEN,则此节点为OLD,并把它添至BESTNODE的后继节古表中;
  • ④比较新旧路径代价。如果g(SUC) < g(OLD),则重新确定OLD的父辈节点为BESTNODE,记下较小代价g(OLD),并修正f(OLD)值;
  • ⑤若至OLD节点的代价较低或一样,则停止扩展节点;
  • ⑥若SUCCSSOR不在OPEN表中,则看其是否在CLOSE表中;
  • ⑦若SUCCSSOR在CLOSE表中,则转向③步骤;
  • ⑧若SUCCSSOR既不在OPEN表中,又不在CLOSE表中,则把它放人OPEN 表中,并添人BESTNODE后裔表,然后转向第(7)步

7.计算f值,然后转人第(2)步 。

启发式函数的强弱及其影响

可以用h(x)接近h*(x)的程度去衡量启发式函数的强弱。

当h(x) < h*(x)时,h(x)过弱,从而导致OPEN表中节点排序的误差较大,易于产生较大的搜索图。

当h(x) > h*(x)时,h(x)过强,使算法A*失去可采纳性,从而不能确保找到最短解答路径。

当h(x)=h* (x) 最为理想,其确保产生最小的捜索图(因为OPEN表中节点的排序正确)且捜索到的解答路径是最短的。

但设计h(x)=h* (x)是不可能的,因此设计接近,又总是h(x) <= h*(x)成为应用A*算法捜索问题解答的关键。

h(x) <= h*(x) •h(x)包含的(启发式)信息越多越好,越接近h*(x)越好 •可以由N多个h(x)的设计方案,效果最好的那个,就是A*算法的,其他的都可以当是A算法的。

设计h(x)的实用考虑

对于评价函数f(x) = g(x) + h(x)

如果h(x) = 0, 搜索接近于宽度优先搜索

如果g(x) = 0, 搜索接近于深度优先搜索

加入一个权值ω使f(x) = g(x) + ωh(x)

当浅层时,使ω取值较大,使g(x)占比小,突出启发函数,加速纵向(深度)搜索

当深层时,使ω取值较小,使g(x)占比大,加速横向(广度)搜索

迭代加深A*算法: IDA*

设置一个限制值 c

只有当节点n的子节点n’的f(n’) < c时,才扩展n’节点放到栈中,并且更新c的值(取它和f(n’)较小的那个值)

算法终止的条件是: •找到目标节点 •栈为空并且限制值c’= ∞ (可以理解成是一个巨大的值,如100000000)

使用IDA*是为了优化空间,节省内存。

示例:

用启发式搜索算法A或者A*解决下面的八数码问题,并给出搜索结束时OPEN表和CLOSE表内容

       评价函数 f(x) = g(x) + h(x)
       其中h(x)的含义可以自己定义

h(n):已经移动的步数

g(n):此状态与目标状态九宫格中相异数字的个数

代码:

import copy
start = ['1','3','4','8','6','2','7','x','5']
end = ['1','2','3','8','x','4','7','6','5']
#start = input().split()
#end = input().split()
start = [start[0:3], start[3:6], start[6:9]]
end = [end[0:3], end[3:6], end[6:9]]
open  = []
close = []

def value_Fx(hx, start): # 求Fx
    global end
    gx = 0
    for i in range(3):
        for j in range(3):
            if(start[i][j] != end[i][j]):
                gx += 1
    return gx + hx

def search_Position(lst):
    position = (-1, -1)
    for i in range(3):
        if "x" in lst[i]:
            position = (i, lst[i].index('x'))
    return position

def find_Next(orient, hx, A0):
    next = copy.deepcopy(A0)
    position = search_Position(next)
    #print(position)
    # orient 代表拓展节点的方向,上下左右
    if orient == 1:
        next_position = (position[0]-1, position[1])
    if orient == 2:
        next_position = (position[0]+1, position[1])
    if orient == 3:
        next_position = (position[0], position[1]-1)
    if orient == 4:
        next_position = (position[0], position[1]+1)
    if (next_position[0] < 3 and next_position[1] < 3 and next_position[0] >= 0 and next_position[1] >= 0):
        next[position[0]][position[1]], next[next_position[0]][next_position[1]] = next[next_position[0]][next_position[1]], next[position[0]][position[1]]
        return [next, value_Fx(hx, next)]
    else:
        # return (next,10) # 取10就是最大值了,存入不需要处理,后续用False代替
        return False


hx = 0
position = search_Position(start)
#print(position)

open.append([start,value_Fx(hx, start)])
while True:
    hx += 1
    if not open: # Open表空失败退出
        print("No answer!")
    A = open.pop() # A是带fn的
    close.append(A)
    if A[0] == end: # 判断A是否目标节点
        # print(close)
        break
    for i in range(1,5):
        A_Next = find_Next(i, hx, A[0])
        if A_Next: # 保证合法
            lst = [i[0] for i in open]
            lst += [i[0] for i in close]
            if(A_Next[0] not in lst):
                open.append(A_Next) # 两表中都不存在的节点存入open表中
        else:
            pass

    open = sorted(open, key = lambda x:x[1], reverse = True) # reverse = True 降序排列,第一个节点使用pop取出即可

print("close表内容:")
for i in close:
    print(i[1])
    for j in i[0]:
        print(j)
print("open表内容")
for i in open:
    print(i[1])
    for j in i[0]:
        print(j)

运行结果:

如堕五里雾中
最后更新于 2023-02-12