吃豆人实验

问题描述

实验二的主要目的是利用课程所学的几种搜索算法以解决吃豆人游戏中的一系列问题。其中包括如何让吃豆人搜索到地图中的糖豆以及如何让糖豆人以较短的路径吃到糖豆等,涉及到搜索算法以及数据结构的知识,并且考察对整体的把握能力。

该实验主要考察了我们以下几点知识或能力:

  • 对栈、队列等数据结构的理解和使用能力
  • 先深、先广、A*算法等的理解与实现
  • python中一些数据类型如列表、元组的理解,以及面向对象编程的知识

算法介绍

所用到的算法

该实验中涉及到的算法主要有深度优先算法、广度优先算法和A*算法,下面分别介绍这三个算法

深度优先算法
  • 算法介绍:

深度优先算法是搜索算法的一种,利用栈结构先进后出的特点完成对空间每个角落的搜索。其主要思路是:首先建立一个空数组,用来存放已经访问过的点。然后首先将遍历的第一个点压入栈中。并开始进行一个循环,首先将栈中的第一个元素弹出。如果这个点没有访问过,则将其标记为已访问,并将其下一步能到达的点压入栈中;如果该点已访问过则继续进行循环。当栈为空或者弹出的点是目标点时搜索结束。利用该方法进行搜索对内存的消耗较少,但不一定找到最短路径。

  • 算法的伪代码如下所示:
function depthFirstSearch(problem,stack) return a solution, or failure
	closed <- an empty set   #建立一个空集合
	stack <- INSERT(MAKE-NONE(INITIAL-STATE[problem]),fringe)#向栈中压入起点
	loop do           #循环体
	if stack is empty then return failure   #如果栈为空则结束
	node <- REMOVE-FRONT(fringe)        #弹栈
	if GOAL-TEST(stack,STATE[node]) then return node  #如果是目标节点则结束
	if STATE[node] is not in closed then   #如果该节点没有被遍历过
		add STATE[node] to closed     #将该节点加入已经遍历过的集合中
		for child-node in EXPAND(STATE[NODE],problem) do #遍历该节点周围所有能到达的节点		
			if child-node not in closed       #如果子节点没有被遍历过
				stack<- INSERT(child-node,stack)  #将其入栈
		end
	end

通过该算法,并将其转化为python语言,且选择列表结构将位置以及路径信息压入栈中便可完成目标搜索,具体实现在解题思路部分详细说明。

广度优先算法
  • 算法介绍:

​ 广度优先算法和深度优先算法的区别在于广度优先算法注重广度而不是深度,它依次将所有能遍历到的点进行遍历,然后再次进行循环。同深度优先算法相比,广度优先算法可以较快完成对全图的遍历,但是内存开销较大。在程序的编写方面,广度优先算法使用队列这种先进先出的特点作为数据结构,其他方面和深度优先算法一样。

  • 算法的伪代码如下所示:
function depthFirstSearch(problem,queue) return a solution, or failure
	closed <- an empty set   #建立一个空集合
	queue <- INSERT(MAKE-NONE(INITIAL-STATE[problem]),fringe)#向队列中压入起点
	loop do           #循环体
	if queue is empty then return failure   #如果队列为空则结束
	node <- REMOVE-FRONT(fringe)        #从队列的出口处弹出一个元素
	if GOAL-TEST(queue,STATE[node]) then return node  #如果是目标节点则结束
	if STATE[node] is not in closed then   #如果该节点没有被遍历过
		add STATE[node] to closed     #将该节点加入已经遍历过的集合中
		for child-node in EXPAND(STATE[NODE],problem) do #遍历该节点周围的所有能到达的节点		
			if child-node not in closed       #如果子节点没有被遍历过
				queue<- INSERT(child-node,queue)  #将其入队列
		end
	end
A*算法
  • 算法介绍:

A*算法是指已知起点和终点的位置来寻找最佳路径的启发式搜索算法。

常用的估算最短路径的算法还有Dijkstra算法,代价一致算法,贪心算法等。其中Dijkstra算法可以计算从起始点到其余所有点的最短路径,代价一致算法和Dijkstra思想一致,但当找到根节点和目标节点最短路径后便停止,贪心算法则是每一步都选择当前最有利的选择,是深度优先算法的一种特例,搜索速度较快,但不一定找到最优解。

A*算法结合了贪心算法和Dijkstra算法的优点,可以在用启发式搜索提高算法效率的同时找到最短路径。

在该算法中,最重要的部分为A*算法的估算函数,这里引用维基百科的解释:

用g(n)表示从起点到任意顶点n的实际距离,h(n)表示任意顶点n到目标顶点的估算距离,则A*算法的估算函数为:f(n) = g(n) + h(n)

h(n)的选择对算法的执行速度影响很大。如果h(n)不大于顶点n到目标顶点的实际距离,则一定可以求出最优解,且h(n)越小,需要计算的节点越多,算法效率越低,常见的评估函数有—欧几里得距离、曼哈顿距离、切比雪夫距离。其中曼哈顿距离为:

d(x,y) = |x1-x2|+|y1-y2|

如果h(n)=0,则转变为Dijkstra算法。如果g(n)=0,则转变为贪心算法,速度最快,但可能找不到最优解。

  • 伪代码实现

A*算法和先深和先广搜索的区别在于,A *算法需要计算出已经遍历过的点中的f(n)最小的点,然后计算该点周围点的f(n),然后在一次在所有的点中找到最小的f(n),然后不断循环,直到找到目标。因此,如果使用优先队列作为盛放所有点的数据结构,并将其f(n)作为有限度,那么每次弹出队列的就是要找的f(n)最小的那一个点,因此选择优先队列作为数据结构。伪代码如下所示:

function depthFirstSearch(problem,PriorityQueue) return a solution, or failure
	closed <- an empty set   #建立一个空集合
	PriorityQueue <- INSERT(MAKE-NONE(INITIAL-STATE[problem]),fringe)#向优先队列中压入起点
	loop do           #循环体
	if PriorityQueue is empty then return failure   #如果队列为空则结束
	node <- REMOVE-FRONT(fringe)        #从队列的出口处弹出一个元素
	if GOAL-TEST(queue,STATE[node]) then return node  #如果是目标节点则结束
	if STATE[node] is not in closed then   #如果该节点没有被遍历过
		add STATE[node] to closed     #将该节点加入已经遍历过的集合中
		for child-node in EXPAND(STATE[NODE],problem) do #遍历该节点周围的所有能到达的节点		
			if child-node not in closed       #如果子节点没有被遍历过
				
				PriorityQueue<- INSERT(child-node,g(n)+h(n),PriorityQueue)  #将该子节点以及对应的优先值加入优先队列
		end
	end

通过以上算法,可以实现A*算法,其中具体的针对该题的算法实现将在后面详细分析。

解题思路

问题一

该题目所涉及到的难点在于如何利用深度优先算法返回一串吃豆人的运行路径,并且考察我们对所提供函数的理解与使用。

根据深度优先算法,我们需要一个空列表closed = []存储遍历过的点,此外,我们还需要一个列表solution = []存储吃豆人每一步的前进方向。

则建立一个二元组,将某一个节点的坐标与路径列表同时存储进来,然后压入栈中。如起始时刻:stack.push((problem.getStartState(),solution)) ,此时solution是空列表。

然后进入循环,首先进行弹栈,用state,solution = stack.pop() 存储弹出的节点信息。如果problem.isGoalState(state)为真,则直接返回solution ,否则继续运行。

如果state在closed列表里面,就继续循环,继续弹栈,继续判断

否则将state加入closed,这里用到python的列表操作函数append,将元素加入列表的尾部

然后遍历for state,action,stepcost in problem.expand(state):子节点能够到达的点以及所需采取的方向。

如果子节点不在closed内,就stack.push((state,solution+[action]))

其中state为该子节点的坐标,solution + [action] 是将两个列表合二为一的操作,相当于将该方向如North,加入到原路径solution列表中。

从而经过不断循环,可以最终得到solution列表,从而完成实验任务。

以下是其完整代码

    stack = Stack()
    closed = []  #存储遍历过的点
    solution = []  #存储吃豆人每一步的前进方向
    # 将一个元组压入栈
    stack.push((problem.getStartState(),solution))
    while(not stack.isEmpty()):#当该fringe为非空
        state,solution = stack.pop()#弹出元素
        if problem.isGoalState(state): #如果是目标节点
            return solution #返回solution
        if state in closed:#如果该点已经遍历过,则跳过该循环
            continue
        closed.append(state)#将该点加入已经访问过的点
        #遍历该点的子节点
        for state,action,stepcost in problem.expand(state):
            if state in closed:#得出子节点的路径
                continue
            stack.push((state,solution+[action]))#压入栈中
问题二

问题二是广度优先算法,该算法的核心是使用队列作为容器而不是栈,其他操作和问题和一相同。

以下是其完整代码

    queue = Queue()
    closed = []  #存储遍历过的点
    solution = []  #存储吃豆人每一步的前进方向
    # 将一个元组压入队列
    queue.push((problem.getStartState(),solution))
    while(not queue.isEmpty()):#当该fringe为非空
        queue,solution = queue.pop()#弹出元素
        if problem.isGoalState(state): #如果是目标节点
            return solution #返回solution
        if state in closed:#如果该点已经遍历过,则跳过该循环
            continue
        closed.append(state)#将该点加入已经访问过的点
        #遍历该点的子节点
        for state,action,stepcost in problem.expand(state):
            if state in closed:#得出子节点的路径
                continue
            queue.push((state,solution+[action]))#压入队列中
问题三

问题三使用的是A*算法,前面已经分析过,A *算法使用的数据结构是优先队列,每次压入队列的除了一个二元组(包含位置信息和路径信息)还有一个A *算法的估算函数f(n)= g(n)+h(n),其中g(n)可以利用题中给出的函数problem.getCostOfActionSequence(state_solution[1])求得,state_solution[1]为路径信息,h(n)可以使用 heuristic(state,problem)求得。这样就可以实现每次先弹出队列的是总代价最小的点,可以省去A *算法遍历最小f(n)的过程。具体的代码如下所示:

    closed = []       #存储遍历过的点
    solution = []      #存储吃豆人每一步的前进方向
    queue = PriorityQueue()
    queue.push((problem.getStartState(),solution),0)   #压入队列中的是一个二元组和一个f(n)值
    while(not queue.isEmpty()):               # 当该fringe为非空
        state_solution = queue.pop()      
        if problem.isGoalState(state_solution[0]):
            return state_solution[1]        #state_solution[1]代表的是solution
        if state_solution[0] in closed:     #如果当前点不是目标点
            continue
        closed.append(state_solution[0])   
        for state,action,stepcost in problem.expand(state_solution[0]):
            if state not in closed:
                newactions = state_solution[1]+[action] 	 
                #将二元组和g(n)+h(n)压入优先队列中
                queue.push((state,newactions),problem.getCostOfActionSequence(newactions)+ heuristic(state,problem))
    

问题一到问题三的通用搜索算法将在下一章中详细介绍

问题四(searchAgents.py)

问题四需要我们找到所有的角落,需要我们将searchAgents.py中涉及到的函数进行完善,同时使用的搜索算法是广度bfs,相当于是我们需要编写实验二所调用的函数的具体实现,同时需要让吃豆人吃完所有的豆才能停下来。那么如何能够让吃豆人知道自己都吃过哪里的豆子呢?

根据题中所给的提示:新的状态只包含吃豆人的位置和角落的状态。那么我们在定义state时,就可以将其同样定义为一个二元组,state = (self.startingPosition,corners)其中二元组的第一个元素是某节点的位置坐标,二元组的第二个元素是当吃豆人到达该节点时的角落的状态。当初始时刻,吃豆人没有访问过任何一个角落,此时corners = (False,False,False,False),如果吃豆人到达的下一个节点时角落,则将该角落对应的corners里的对应元素改为True。具体的代码以及代码的思路如下所示:

  • getStartState(self)函数
        # 该函数需要返回初始位置的坐标值以及角落的状态
        corners = (False,False,False,False)
        # 根据提示,state应该是一个二元组的形式
        state = (self.startingPosition,corners)
        return state
  • isGoalState(self, state)函数
        # return (5,5)
        # 当所有的角落都访问过时结束访问,即四个角落的布尔值都为ture
        isGoal = state[1]
        boolean = isGoal[0] and isGoal[1] and isGoal[2] and isGoal[3]
        return boolean
        #返回的值即表示是不是已经完成搜索
  • expand(self, state)函数
# 根据提示,该部分需要返回一个由三元组构成的列表,我们需要实现在search中调用的expand()函数的功能
    # 元组的形式为(child,action, stepCost),其中child我们可以用getNextState()函数获取,action则是
        # 根据getActions函数,而stepCost为固定值1,所以我们需要首先完成另外的两个函数
        children = []
        for action in self.getActions(state):
            # Add a child state to the child list if the action is legal
            # You should call getActions, getActionCost, and getNextState.
            "*** YOUR CODE HERE ***"
            newstate = self.getNextState(state, action)
            children.append((newstate,action,1))       #将所有的子节点的3元组全部加入到这个列表中
        self._expanded += 1 # DO NOT CHANGE
        return children             #返回列表
  • getActions(self, state)函数
        #该函数的作用为根据迷宫信息判断出有哪些位置可以走
        possible_directions = [Directions.NORTH, Directions.SOUTH, Directions.EAST, Directions.WEST]
        valid_actions_from_state = []
        for action in possible_directions:
            x, y = state[0]                               #x,y代表的时迷宫中某点的坐标
            dx, dy = Actions.directionToVector(action)    #dx,dy代表的是分别朝四个方向走坐标的变化量
            nextx, nexty = int(x + dx), int(y + dy)       #下一步能到达的坐标的位置
            if not self.walls[nextx][nexty]:              #如果下一步到达的坐标不是墙
                valid_actions_from_state.append(action)   #就将该action加入到可可以走的方向列表中
        return valid_actions_from_state
  • getActionCost(self, state, action, next_state)函数
        assert next_state == self.getNextState(state, action), (
            "Invalid next state passed to getActionCost().")
        return 1       #cost永远为1
  • getNextState(self, state, action)函数
        #该部分需要返回下一个节点的坐标值以及角落的状态
    	assert action in self.getActions(state), (     #以及四个角落的状态
            "Invalid action passed to getActionCost().")
        x, y = state[0]
        dx, dy = Actions.directionToVector(action)
        nextx, nexty = int(x + dx), int(y + dy)
        "*** YOUR CODE HERE ***"
        corners = state[1]                             #首先获取角落的状态
        newcorners = ()
        nextstate = (nextx,nexty)                       #将下一步的坐标赋值
        if nextstate in self.corners:
            #如果下一步的位置是(1,1),则将第一个角落信息赋为true,其余的以此类推
            if nextstate ==(1,1):    
                newcorners = (True,corners[1],corners[2],corners[3])
            elif nextstate ==(1,self.top):
                newcorners = (corners[0],True,corners[2],corners[3])
            elif nextstate ==(self.right,1):
                newcorners = (corners[0],corners[1],True,corners[3])
            elif nextstate ==(self.right,self.top):
                newcorners = (corners[0],corners[1],corners[2],True)
        else:
            # 如果都不是,就不改变角落的状态
            newcorners = corners
        return(nextstate,newcorners)
  • getCostOfActionSequence(self, actions)函数

该函数最后的返回值是actions的长度,如果路径包括了墙的坐标,则返回999999

感悟

问题四到此为止完全结束,在写问题1-3时对题目给出的几个函数非常不解,知道需要自己来编写这几个函数才体会到题目设计的精妙之处,几个函数互相配合,能够完美的得到最终的结果。

问题五:角落问题(启发式)

题目的要求是

构建合适的启发函数,完成searchAgents.py文件中的cornersHeuristic角落搜索问题。用以下命令测试你的code:

Python pacman.py -l mediumCorners -p AStarCornersAgent -z 0.5

找到对应的代理使用的模块AStarCornersAgent ,如下所示

class AStarCornersAgent(SearchAgent):
    "A SearchAgent for FoodSearchProblem using A* and your foodHeuristic"
    def __init__(self):
        self.searchFunction = lambda prob: search.aStarSearch(prob, cornersHeuristic)
        self.searchType = CornersProblem

说明使用的搜索算法是A*,并且需要我们自己给出h(n),来解决问题四里面的角落搜索问题。

其中如果选用的h(n)不大于顶点n到目标顶点的实际距离,则一定可以求出最优解,且h(n)越小,需要计算的节点就越多,算法效率越低,因此我们要选择合适的h(n),如果直接使用默认返回值为0,可以看到花费为1966,显然效率较低。

Path found with total cost of 106 in 0.4 seconds
Search nodes expanded: 1966
Pacman emerges victorious! Score: 434
Win Rate:      1/1 (1.00)
Record:        Win

​ 表一 问题五官方标准

Number of nodes expandedGrade
more than 20000/3
at most 20001/3
at most 16002/3
at most 12003/3

上表为官网给出的评分表(如表1),可见需要设计出一个高效的h(n),最终选择评估函数为曼哈顿距离,其曼哈顿距离函数在util.py文件中,可以直接调用。

  • 实现思路

该问题涉及到访问全部四个节点的最短路径,则首先需要知道未被访问过角落有哪些,则可以根据二元组中状态元组得出。然后计算剩余每个点和当前位置的曼哈顿距离,为了能够得到最短路径,需要让吃豆人先访问离他最近的角落,即返回将所计算得出的曼哈顿距离的最小值。所以问题四只有两个难点:

  1. 找出未访问的角落
  2. 利用给出的曼哈顿函数计算出state到剩余所有位置的曼哈顿距离并返回最小值

以下是需要填写的代码:

    "*** YOUR CODE HERE ***"
    # 寻找还没有访问过的角落
    left_corners = []
    index = 0
    for isclosed in state[1]:
        if not isclosed:
            left_corners.append(corners[index])
        index+=1
    # 计算出到达剩余点的最长的曼哈顿距离
    max_dis = 0
    for point in left_corners:
        # 利用util模块中给出的manhattanDistance函数计算曼哈顿距离
        dis = util.manhattanDistance(state[0],point)
        # dis = mazeDistance(state[0],point,problem.startingGameState)
        if max_dis < dis:
            max_dis = dis
    return max_dis

通过计算,可得结果如下:

Path found with total cost of 106 in 0.2 seconds
Search nodes expanded: 1136
Pacman emerges victorious! Score: 434

可以看到,Search nodes expanded的值未1136<1200,说明算法的效率较高

问题六-吃掉所有的豆子

题目的要求是:

用尽可能少的步数吃掉所有的豆子。完成searchAgents.py文件中的FoodSearchProblem豆子搜索问题。构建合适的启发函数,完成searchAgents.py文件中的foodHeuristic豆子搜索(启发式)问题。用以下命令测试你的code:Python pacman.py -l trickySearch -p AStarFoodSearchAgent

可以看到,现在需要找到吃豆人吃掉图中的所有豆子的最短路径,首先题目初始的返回值为0,则说明此时选用的A *算法没有选用估价函数,即为Dijkstra算法,则最终效果较差,结果如下:

Path found with total cost of 60 in 37.0 seconds
Search nodes expanded: 16688

其中评分标准如表二所示:

​ 表二 问题六官方标准

Number of nodes expandedGrade
more than 150001/4
at most 150002/4
at most 120003/4
at most 90004/4 (full credit; medium)
at most 70005/4 (optional extra credit; hard)

可以看到需要选择更高效的h(n),若仍选择曼哈顿函数作为h(n),可以得到结果如下:

Path found with total cost of 60 in 10.6 seconds
Search nodes expanded: 9551

结果仍然不够理想,所以需要选择新的估价函数。这里考虑使用mazeDistance(point1, point2, gameState)函数,其功能为使用bfs来计算从当前点到其他点的距离,而不是采用估算的方法,使用该方法所计算出的h(n)会更大,重复次数更少,效果更好。则所用代码如下:

    "*** YOUR CODE HERE ***"
    # 得到剩余食物坐标的列表
    foodlist = foodGrid.asList()
    max_dis = 0
    for point in foodlist:
        # dis = util.manhattanDistance(state[0],point)
        # 计算h(n)估价函数
        dis = mazeDistance(position,point,problem.startingGameState)
        if max_dis < dis:
            max_dis = dis
            # 找出到所有点中估价函数最小的点并返回
    return max_dis

可以得到测试结果如下:

Path found with total cost of 60 in 66.9 seconds
Search nodes expanded: 4137
Pacman emerges victorious! Score: 570

可以看到最终的节点扩展值为4137,但是计算时间非常长

问题七-次最优搜索
  • 题目要求:

定义一个优先吃最近的豆子函数是提高搜索速度的一个好的办法。补充完成searchAgents.py文件中的AnyFoodSearchProblem目标测试函数,并完成searchAgents.py文件中的ClosestDotSearchAgent部分,在此Agent当中缺少一个关键的函数:找到最近豆子的函数。用以下命令测试你得code:

Python pacman.py -l bigSearch -p ClosestDotSearchAgent -z .5

首先是完善ClosestDotSearchAgent部分,只需要填写 return search.aStarSearch(problem)即使用A*算法寻找最短路径即可。在最后的 isGoalState(self, state)中,只需加入return self.food[x][y]即可,即返回当前点是不是终点。所谓终点就是最后一个食物的位置,涉及到的完善ClosestDotSearchAgent代码如下:

    def findPathToClosestDot(self, gameState):
        """
        Returns a path (a list of actions) to the closest dot, starting from
        gameState.
        """
        # Here are some useful elements of the startState
        startPosition = gameState.getPacmanPosition()
        food = gameState.getFood()
        walls = gameState.getWalls()
        problem = AnyFoodSearchProblem(gameState)
        "*** YOUR CODE HERE ***"
        return search.aStarSearch(problem)
        util.raiseNotDefined()

最终可以实现目标,但是吃豆人总是会露吃掉一个一个食物,导致其在最后时刻要花很长的路程去吃它,浪费了时间,将在后面对该部分进行讨论

至此每个问题的思路已经解释清晰。

算法实现

实验结果

问题一~三
  • 通用搜索算法

实验要求尽量定义一个通用的搜索算法 解决问腿1-3,且1-3的不同指出只在于不同的数据结构对open标进行排序。经过前面思路分析可以发现:深度优先和广度优先只有数据结构的不同,其他完全一样,因此在调用搜索算法时只需返回不同的数据结构即可。下面是通用搜索算法:

def generalSearch(problem,fringe):
    closed = []           #存储遍历过的点
    solution = []         #存储吃豆人每一步的前进方向
    #压入元组
    fringe.push((problem.getStartState(),solution))  
    while(not fringe.isEmpty()):       #当该fringe为非空
        state_solution = fringe.pop()   #弹出元素
        if problem.isGoalState(state_solution[0]):    #如果是目标节点
            return state_solution[1]                 #返回solution
        if state_solution[0] in closed:   #如果该点已经遍历过,则进入下一步循环
            continue
        closed.append(state_solution[0])      #将该点加入已经访问过的点
        #遍历该点的子节点
        for state,action,stepcost in problem.expand(state_solution[0]):
            #得出子节点的路径
            newactions = state_solution[1]+[action]
            fringe.push((state,newactions))  #加入数据结构中
    return []

以下是两个搜索函数下面的代码:

def depthFirstSearch(problem):
    from util import Stack
    fringe = Stack()
    return generalSearch(problem,fringe)
def breadthFirstSearch(problem):
    from util import Queue
    fringe = Queue()
    return generalSearch(problem,fringe)

可以看到,只需返回不同的open表即可

而A*算法由于涉及到计算f(n),除了压入位置和路径的元组fringe.push((state,newactions)),还需附上f(n),因此必须统一接口。通过观察可以看到util模块里面有这样一个优先队列的子类:

class PriorityQueueWithFunction(PriorityQueue):
    def  __init__(self, priorityFunction):
        "priorityFunction (item) -> priority"
        self.priorityFunction = priorityFunction      # 
        PriorityQueue.__init__(self)        # super-class initializer
    def push(self, item):
        PriorityQueue.push(self, item, self.priorityFunction(item))

它的功能是:根据初始类时定义的priorityFunction函数,使得入队列时用户只需压入item,即我们定义的元组(state,newactions),他能向优先队列中压入item 和使用priorityFunction函数对元组进行操作后的值。

因此我们只需要让priorityFunction(item)具有计算f(n)的功能,因此在A*算法下使用该数据结构:

def aStarSearch(problem, heuristic=nullHeuristic):
    from util import PriorityQueueWithFunction
    priorityFunction = lambda item: problem.getCostOfActionSequence(item[1]) + heuristic(item[0],problem)
    fringe = PriorityQueueWithFunction(priorityFunction)
    return generalSearch(problem, fringe)

我们需要一个匿名函数,它能够根据元组信息,将item换为problem.getCostOfActionSequence(item[1]) + heuristic(item[0],problem),即f(n)的值,然后返回该初始化好的数据结构,从而实现接口的统一。

  • 结果展示

问题一结果如图一所示

在这里插入图片描述
​ 图一 深度优先结果

问题二结果展示:

在这里插入图片描述

​ 图二 广度优先结果

可以看到,先深搜索选择的路径长于先广搜索。

问题三结果展示:

在这里插入图片描述

​ 图三 A*算法搜索结果

问题四~五

在这里插入图片描述

​ 图四 问题四和问题五的搜索界面

终端数据结果:

PS D:\桌面\search> Python pacman.py -l mediumCorners -p SearchAgent -a fn=bfs,prob=CornersProblem
[SearchAgent] using function bfs
[SearchAgent] using problem type CornersProblem
Path found with total cost of 106 in 0.2 seconds
Search nodes expanded: 1966
Pacman emerges victorious! Score: 434
Average Score: 434.0
Scores:        434.0
Win Rate:      1/1 (1.00)
Record:        Win
PS D:\桌面\search> Python pacman.py -l mediumCorners -p AStarCornersAgent -z 0.5
Path found with total cost of 106 in 0.2 seconds
Search nodes expanded: 1136
Pacman emerges victorious! Score: 434
Average Score: 434.0
Scores:        434.0
Win Rate:      1/1 (1.00)
Record:        Win
问题六
  • 结果展示
    在这里插入图片描述

​ 图5 问题六的搜索界面

终端结果:

PS D:\桌面\search> Python pacman.py -l trickySearch -p AStarFoodSearchAgent
Path found with total cost of 60 in 67.9 seconds
Search nodes expanded: 4137
Pacman emerges victorious! Score: 570
Average Score: 570.0
Scores:        570.0
Win Rate:      1/1 (1.00)
Record:        Win
问题七

结果展示

在这里插入图片描述

​ 图六 问题七的结果展示

终端结果:

PS D:\桌面\search> Python pacman.py -l bigSearch -p ClosestDotSearchAgent -z .5
[SearchAgent] using function depthFirstSearch
[SearchAgent] using problem type PositionSearchProblem
Path found with cost 350.
Pacman emerges victorious! Score: 2360
Average Score: 2360.0
Scores:        2360.0
Win Rate:      1/1 (1.00)
Record:        Win
自动评分

终端结果:

Finished at 9:03:08

Provisional grades
==================
Question q1: 4/4
Question q2: 4/4
Question q4: 3/3
Question q5: 3/3
Question q6: 5/4
Question q7: 3/3
------------------
Total: 26/25

可以看到,很好的完成了实验任务。由于问题6采用了bfs算法来计算h(n)的值,所以大大减少了计算次数,得到了更高的分数。

讨论及结论

讨论

实验过程中遇到的难点
  • 题目理解

最开始读到题目,一脸茫然,根本不知道从何入手,也不知道究竟要在对应区域写什么代码。在做问题一时,不知道先深算法如何结合题目要求书写,甚至不知道返回值应该是什么,在进行了求助后,开始仔细看提示的信息,并且开始尝试print()出提供的一个个函数的结果,并思考如何根据这几个函数整合出一个方向的列表,最终发现,自己写出的代码吃豆人总是会尝试所有的错误的路口,造成很大的时间浪费。经过上网搜索,才明白可以用一个二元组将位置和路径th信息一同入栈,从而重新编写程序,最终解决了这一问题。

问题四同样耗费了很长的时间理解,原因在于没有自习读懂题意,没有理解题中给出的state已经是一个二元组,state除了需要记录当前坐标还需记录四个角落的状态。此外,由于对python掌握不够熟练,造成了一定的阻力。

  • 通用搜索算法

通用搜索算法涉及到接口统一的问题,尤其是A*算法,没有想到居然可以通过给出的另外一个数据结构完美地实现接口统一,实在是十分惊讶。

收获

本次实验收获很多。首先是认识到自己的程序编写的熟练程度还有待提高,对一些操作应该游刃有余,但在实验的过程中也得到了很好的锻炼;第二,通过对具体算法的编写和分析,从而真正透彻地理解了A*等算法的原理,也第一次知道也可以使用python完成数据结构的建立,对用一个项目开发,可以将一些数据结构、类等几种到特定的模块中,使项目更模块化;第三,在编写程序时也应有意地注意到统一接口的问题,这样可以使程序更加简洁,也有利于使程序框架更为清晰;第四,看到了同寝室大佬在实验上课前就已经完成了整个实验,并且用各种方法对结果进行优化,并很早完成了实验报告,而自己还在摸索阶段,感到了自己是如此的懒惰和磨叽,必须时刻鞭策自己有任务第一时间完成。

结论

实验最终通过了自动评分,得到了较好的结果。本次实验收获很多,得到了一定的锻炼,但也暴露出了很多问题,也为自己敲响警钟。此外还有一些没有解决的问题,例如如何解决问题七剩下一个豆子等,此外本实验项目的其他部分还有许多值得学习的内容有待探索。

参考文献

[1] www.baidu.com

[2] www.csdn.com

[3] www.github.com

[4] 算法导论
没有理解题中给出的state已经是一个二元组,state除了需要记录当前坐标还需记录四个角落的状态。此外,由于对python掌握不够熟练,造成了一定的阻力。

  • 通用搜索算法

通用搜索算法涉及到接口统一的问题,尤其是A*算法,没有想到居然可以通过给出的另外一个数据结构完美地实现接口统一,实在是十分惊讶。

收获

本次实验收获很多。首先是认识到自己的程序编写的熟练程度还有待提高,对一些操作应该游刃有余,但在实验的过程中也得到了很好的锻炼;第二,通过对具体算法的编写和分析,从而真正透彻地理解了A*等算法的原理,也第一次知道也可以使用python完成数据结构的建立,对用一个项目开发,可以将一些数据结构、类等几种到特定的模块中,使项目更模块化;第三,在编写程序时也应有意地注意到统一接口的问题,这样可以使程序更加简洁,也有利于使程序框架更为清晰;第四,看到了同寝室大佬在实验上课前就已经完成了整个实验,并且用各种方法对结果进行优化,并很早完成了实验报告,而自己还在摸索阶段,感到了自己是如此的懒惰和磨叽,必须时刻鞭策自己有任务第一时间完成。

结论

实验最终通过了自动评分,得到了较好的结果。本次实验收获很多,得到了一定的锻炼,但也暴露出了很多问题,也为自己敲响警钟。此外还有一些没有解决的问题,例如如何解决问题七剩下一个豆子等,此外本实验项目的其他部分还有许多值得学习的内容有待探索。

参考文献

[1] www.baidu.com

[2] www.csdn.com

[3] www.github.com

[4] 算法导论

  • 7
    点赞
  • 75
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值