- 原理
启发式搜索利用知识来引导搜索,达到减少搜索范围、降低问题复杂度的目的。
A*算法是一种启发式图搜索算法,其特点在于对估价函数的定义上。对于一般的启发式图搜索,总是选择估价函数f值最小的节点作为扩展节点。因此,f是根据需要找到一条最小代价路径的观点来估算节点的,所以,可考虑每个节点n的估价函数值为两个分量:从起始节点到节点n的实际代价g(n)以及从节点n到达目标节点的估价代价h(n),且hn≤h*n , h*n 为n节点到目的结点的最优路径的代价。
八数码问题也称为九宫问题。在3×3的棋盘,摆有八个棋子,每个棋子上标有1至8的某一数字,不同棋子上标的数字不相同。棋盘上还有一个空格,与空格相邻的棋子可以移到空格中。要求解决的问题是:给出一个初始状态和一个目标状态,找出一种从初始状态转变成目标状态的移动棋子步数最少的移动步骤。
-
有序搜索
-
有序搜索路径
- 步骤
- 代码
# 0,1,2 # 3,4,5 # 6,7,8 # 0,1,2,3,4,5,6,7,8 0为空格 layouts = {} layouts_deep = {} layouts_fn = {} # 每个位置可移动的到的位置 change_able = {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(a, i, j, deep, dist): if i > j: i, j = j, i b = a[:i] + a[j] + a[i + 1:j] + a[i] + a[j + 1:] fn = cal_num(b, dist) + deep return b, fn # 计算距离 def cal_num(src, dist): total = 0 a = src.index("0") for i in range(0, 9): if i != a: total = total + abs(i - dist.index(src[i])) return total # 求解 def progress(src, dist): # 计算逆序数 a = 0 b = 0 for i in range(1, 9): fist = 0 for j in range(0, i): if src[j] > src[i] != '0': fist = fist + 1 a += fist for i in range(1, 9): fist = 0 for j in range(0, i): if dist[j] > dist[i] != '0': fist = fist + 1 b += fist # 逆序数一奇一偶,无解 if (a % 2) != (b % 2): return -1, None layouts[src] = -1 layouts_deep[src] = 1 layouts_fn[src] = 1 + cal_num(src, dist) stack_layouts = [src] while len(stack_layouts) > 0: cur_layout = min(layouts_fn, key=layouts_fn.get) del layouts_fn[cur_layout] stack_layouts.remove(cur_layout) # 找到最小fn,并移除 if cur_layout == dist: # 判断当前状态是否为目标状态 lst_steps.append(cur_layout) break ind_slide = cur_layout.index("0") lst_shifts = change_able[ind_slide] # 当前可进行交换的位置集合 for nShift in lst_shifts: new_layout, fn = swap(cur_layout, nShift, ind_slide, layouts_deep[cur_layout] + 1, dist) if layouts.get(new_layout) is None: # 判断交换后的状态是否已经查询过 layouts_deep[new_layout] = layouts_deep[cur_layout] + 1 # 存入深度 layouts_fn[new_layout] = fn # 存入fn layouts[new_layout] = cur_layout # 定义前驱结点 stack_layouts.append(new_layout) # 存入集合 lst_steps = [cur_layout] while layouts[cur_layout] != -1: # 存入路径 cur_layout = layouts[cur_layout] lst_steps.append(cur_layout) lst_steps.reverse() return 0, lst_steps if __name__ == "__main__": src = input("输入初始值:") dic = input("输入目标值:") retCode, lst_steps = progress(src, dic) if retCode != 0: print("无解") else: for nIndex in range(len(lst_steps)): print("step #" + str(nIndex + 1)) print(lst_steps[nIndex][:3]) print(lst_steps[nIndex][3:6]) print(lst_steps[nIndex][6:])
- 运行结果截图
- 思考题
1.什么是宽度优先搜索?什么是有序搜索?
答:宽度优先搜索算法(又称广度优先搜索)是最简便的图的搜索算法之一,这一算法也是很多重要的图的算法的原型。Dijkstra单源最短路径算法和Prim最小生成树算法都采用了和宽度优先搜索类似的思想。其别名又叫BFS,属于一种盲目搜寻法,目的是系统地展开并检查图中的所有节点,以找寻结果。换句话说,它并不考虑结果的可能位置,彻底地搜索整张图,直到找到结果为止。
有序搜索(Ordered Search)又称之为最佳优先搜索(Best First Search),是一种启发式搜索算法,我们也可以将它看作广度优先搜索算法的一种改进;最佳优先搜索算法在广度优先搜索的基础上,用启发估价函数对将要被遍历到的点进行估价,然后选择代价小的进行遍历,直到找到目标节点或者遍历完所有的点,算法结束。
2.分析不同的估价函数对A*算法性能的影响。
答:
①当估算的距离h完全等于实际距离h'时,也就是每次扩展的那个点我们都准确的知道,如果选他以后,我们的路径距离是多少,这样我们就不用乱选了,每次都选最小的那个,一路下去,肯定就是最优的解,而且基本不用扩展其他的点。
②如果估算距离h小于实际距离h'时,我们到最后一定能找到一条最短路径(如果存在另外一条更短的评估路径,就会选择更小的那个),但是有可能会经过很多无效的点。极端情况,当h==0的时候,最终的距离函数就变成:
f(M)=g(M)+h(M)
=> f(M)=g(M)+0
=> f(M)=g(M)
③如果估算距离h大于实际距离h'时,有可能就很快找到一条通往目的地的路径,但是却不一定是最优的解。
因此,A*算法最后留给我们的,就是在时间和距离上需要考虑的一个平衡。如果要求最短距离,则一定选择h小于等于实际距离;如果不一定求解最优解,而是要速度快,则可以选择h大于等于实际距离。
最后:有误请指正!!!!!!!!!感谢观看!