实验8 搜索技术
一、实验目的
(1)掌握搜索技术的相关理论,能根据实际情况选取合适的搜索方法;
(2)进一步熟悉盲目搜索技术,掌握其在搜索过程中的优缺点;
(3)掌握启发式搜索的思想,能针对实际问题选取合适的评价函数;
(4)掌握问题归约的解决问题的思想,掌握与或图的搜索技术并能应用;
(5)深入理解博弈树搜索方法,并能应用于对弈类问题;
(6)根据自身情况,能选择合适的编程语言,实现启发式搜索、博弈树搜索方法、α-β剪枝算法,并能应用于实际AI问题。
二、实验内容
选择一种编程语言(最好为python或java),编程实现下面题目要求。
1、八数码难题
在3×3的棋盘上,摆有八个棋子,每个棋子上标有1至8的某一数字。棋盘中留有一个空格,空格可用0来表示。空格周围的棋子可以移到空格中。要求解的问题是:给出一种初始布局(初始状态)和目标布局,找到一种启发式移动方法,实现从初始布局到目标布局的转变,并与实验7 的盲目移动方法对比。
1、需求分析
在一个3×3的九宫格棋盘上,摆有8个正方形方块,每一个方块都标有1~8中的某一个数字。棋盘中留有一个空格,要求按照每次只能将与空格相邻的方块与空格交换的原则,将任意摆放的数码盘(初始状态)逐步摆成某种给定的数码盘的排列方式(目标状态)。
2、数据结构、功能模块设计与说明
运用启发式搜索,利用问题拥有启发信息引导搜索,以达到减小搜索范围、降低问题复杂度的目的。在启发式搜索过程中,要对Open表进行排序,这就要有一种方法来计算待扩展结点有希望通向目标结点的不同程度,人们总是希望找到最有可能通向目标结点的待扩展结点优先扩展。一种最常用的方法是定义一个评价函数对各个结点进行计算,其目的就是用来估算出“有希望”的结点。用f来标记评价函数,用f(n)表示结点n的评价函数值,并用f来排列等待扩展的结点,然后选择具有最小f值的结点作为下一个要扩展的结点。
A*算法是一种有序搜索算法,其特点在于对评价函数的定义上。这个评估函数f使得在任意结点上其函数值f(n)能估算出结点S到结点n的最小代价路径的代价与从节点n到某一目标节点的最小代价路径的代价的总和,也就是说f(n)是约束通过结点n的一条最小代价路径的代价的估计。
3、核心代码(不要将全部代码贴在报告上)与测试结果说明
核心代码:
定义A*算法
def A_start(start, end, distance_fn, generate_child_fn, time_limit=10):# 创建起始状态结点和目标状态结点对象,并分别计算其哈希值root = State(0, 0, start, hash(str(BLOCK)), None)end_state = State(0, 0, end, hash(str(GOAL)), None)# 检查起始状态是否就是目标状态,如果是,则直接输出提示信息if root == end_state:print("start == end !")# 将起始状态结点加入到OPEN表中,并对OPEN表进行堆化操作OPEN.append(root)heapq.heapify(OPEN)# 创建一个哈希集合,用于存储已经生成的状态结点的哈希值,并将起始状态结点的哈希值添加到集合中node_hash_set = set()node_hash_set.add(root.hash_value)# 记录算法开始的时间start_time = datetime.datetime.now()# 进入主循环,直到OPEN表为空(搜索完成)或达到时间限制while len(OPEN) != 0:top = heapq.heappop(OPEN)# 如果当前结点就是目标状态结点,则直接输出路径if top == end_state:return print_path(top)# 产生孩子节点,孩子节点加入OPEN表generate_child_fn(cur_node=top, end_node=end_state, hash_set=node_hash_set,open_table=OPEN, dis_fn=distance_fn)# 记录当前时间cur_time = datetime.datetime.now()# 超时处理,如果运行时间超过了设定的时间限制,则输出超时提示信息并返回if (cur_time - start_time).seconds > time_limit:print("Time running out, break !")print("Number of nodes:", SUM_NODE_NUM)return -1# 如果循环结束时OPEN表为空,则表示没有找到路径,输出提示信息并返回-1print("No road !") # 没有路径return -1
定义曼哈顿距离计算函数
def manhattan_dis(cur_node, end_node): # 定义一个名为manhattan_dis的函数,接受两个参数cur_node(当前结点)和end_node(目标结点)# 获取当前结点和目标结点的状态矩阵cur_state = cur_node.stateend_state = end_node.statedist = 0N = len(cur_state) # 获取状态矩阵的大小,假设为N# 遍历状态矩阵中的每个位置for i in range(N):for j in range(N):# 如果当前结点的值与目标结点的值相等,则跳过当前位置,因为这个位置已经在目标状态中if cur_state[i][j] == end_state[i][j]:continuenum = cur_state[i][j] # 获取当前结点在状态矩阵中的值# 如果当前结点的值为0(空白格),则将目标位置设置为状态矩阵的右下角if num == 0:x = N - 1y = N - 1# 如果当前结点的值不为0,则根据当前结点的值计算其目标位置,假设目标位置为(x,y)else:x = num / Ny = num - N * x - 1# 计算当前结点与目标位置之间的曼哈顿距离,并累加到总距离中dist += (abs(x - i) + abs(y - j))# 返回计算得到的曼哈顿距离作为当前结点到目标结点的估计代价
return dist
生成子结点函数
def generate_child(cur_node, end_node, hash_set, open_table, dis_fn):# 如果当前结点就是目标结点,则直接将目标结点假如OPEN表,并返回,表示已经找到了解if cur_node == end_node:heapq.heappush(open_table, end_node)return# 获取当前结点状态矩阵的大小num = len(cur_node.state)# 遍历当前结点状态矩阵的每一个位置for i in range(0, num):for j in range(0, num):# 如果当前位置不是空格,则跳过,因为空格是可以移动的位置if cur_node.state[i][j] != 0:continue# 遍历当前位置的四个邻居位置,即上下左右四个方向for d in direction:x = i + d[0]y = j + d[1]if x < 0 or x >= num or y < 0 or y >=num:continue# 记录生成的结点数量global SUM_NODE_NUMSUM_NODE_NUM += 1# 交换空格和邻居位置的数字,生成一个新的状态矩阵state = copy.deepcopy(cur_node.state)state[i][j], state[x][y] = state[x][y], state[i][j]# 计算新状态矩阵的哈希值,并检查是否已经在哈希集合中存在,如果存在则表示已经生成过相同的状态,跳过h = hash(str(state))if h in hash_set:continue# 将新状态的哈希值添加到哈希集合中,计算新状态结点的gn(从起始结点到当前结点的代价)和hn(当前结点到目标结点的估计代价)hash_set.add(h)gn = cur_node.gn + 1hn = dis_fn(cur_node, end_node)# 创建新的状态结点对象,并将其加入到当前结点的子结点列表中,并将其加入到OPEN表中。node = State(gn, hn, state, h, cur_node)cur_node.child.append(node)heapq.heappush(open_table, node)
结果:
4、实验存在的问题与体会
学会了用A算法解决搜索问题,在搜索的时候比之前的盲目搜索用时更少,在其他搜索问题上用A算法也可以比盲目搜索更有效率,但前提是找到合适的估计函数。
2、编程实现一字棋游戏人机对弈,在九宫格棋盘上人机轮流在棋盘上摆各自的棋子,每次一枚,谁先取得三子一线结果的一方获胜。 (请用极小极大值搜索算法或α-β剪枝算法实现)
1、需求分析
用极小极大值搜索算法或α-β剪枝算法编程实现一字棋游戏人机对弈,在九宫格棋盘上人机轮流在棋盘上摆各自的棋子,每次一枚,谁先取得三子一线结果的一方获胜。
2、数据结构、功能模块设计与说明
关于核心判断部分 maxmin()解释:
该函数根据当前游戏状态的得分,计算出下一步应该走的位置。该函数的参数如下:
board:表示当前的游戏状态,是一个 3x3 的二维数组。
depth:表示搜索的深度,即预测未来多少步。
alpha:表示当前最好的评分(对于 maximizing 玩家来说),初始化为负无穷。
beta:表示当前最好的评分(对于 minimizing 玩家来说),初始化为正无穷。
maximizing:表示当前是轮到哪个玩家走棋,True 表示 maximizing 玩家,False 表示 minimizing 玩家。
该函数的返回值包括两个元素:
best_score:表示当前的最优得分。
best_move:表示当前应该走的位置。
在函数内部,首先使用 check_win 和 check_tie 函数判断当前游戏状态,如果存在胜负或平局,则返回对应的得分。如果还没有到达搜索的深度,则进入下一步的搜索。
如果当前是 maximizing 玩家,则使用 for 循环遍历棋盘中所有空位,对于每个空位,都假设 maximizing 玩家会走这个位置,并计算下一步的得分。如果得分比当前最优得分更好,则更新最优得分和最优位置。同时,更新 alpha 的值为当前最优得分和 alpha 中的最大值。如果 beta 的值小于或等于 alpha,则可以停止搜索并返回结果。
如果当前是 minimizing 玩家,则与 maximizing 玩家的情况类似,只是更新的是 beta 的值。
最终返回当前的最优得分和最优位置。
3、核心代码(不要将全部代码贴在报告上)与测试结果说明
核心代码:
minmax函数
# 极大极小/α-β剪枝算法
def minmax(board, depth, alpha, beta, maximizing):if check_win(board, "X"):return -10, Noneif check_win(board, "O"):return 10, Noneif check_tie(board):return 0, Noneif maximizing:best_score = -math.infbest_move = Nonefor row in range(3):for col in range(3):if board[row][col] == " ":board[row][col] = "O"score, _ = minmax(board, depth - 1, alpha, beta, False)board[row][col] = " "if score > best_score:best_score = scorebest_move = (row, col)alpha = max(alpha, best_score)if beta <= alpha:breakreturn best_score, best_moveelse:best_score = math.infbest_move = Nonefor row in range(3):for col in range(3):if board[row][col] == " ":board[row][col] = "X"score, _ = minmax(board, depth - 1, alpha, beta, True)board[row][col] = " "if score < best_score:best_score = scorebest_move = (row, col)beta = min(beta, best_score)if beta <= alpha:breakreturn best_score, best_move
结果:
4、实验存在的问题与体会
掌握了极大极小值搜索算法和α-β算法的算法思想。