本博客为讲解过河问题,具体问题如下:
你想运送五个动植物过河,分别是 (1) 花 (2)蚂蚱(3)青蛙 (4) 蛇 (5) 鹰. 如果没有人看着,老鹰会吃蛇, 蛇会吃青蛙, 青蛙吃蚂蚱, 蚂蚱破坏花。你的船一次最多能载除你之外的两样。
主要讲解python代码问题。

util.py

首先是对数据结构的创建,此为一个优先队列,具体用到了heapq库中的函数,具体我在Python标准库之heapq有所介绍

import heaqp

相当于#include,可以调用这个库中的函数

# Data structure for supporting uniform cost search.
class PriorityQueue:
    def  __init__(self):
        self.DONE = -100000
        self.heap = []
        self.priorities = {}  # Map from state to priority

    # Insert |state| into the heap with priority |newPriority| if
    # |state| isn't in the heap or |newPriority| is smaller than the existing
    # priority.
    # Return whether the priority queue was updated.
    def update(self, state, newPriority):
        oldPriority = self.priorities.get(state)
        if oldPriority == None or newPriority < oldPriority:
            self.priorities[state] = newPriority
            heapq.heappush(self.heap, (newPriority, state))
            return True
        return False

    # Returns (state with minimum priority, priority)
    # or (None, None) if the priority queue is empty.
    def removeMin(self):
        while len(self.heap) > 0:
            priority, state = heapq.heappop(self.heap)
            if self.priorities[state] == self.DONE: continue  # Outdated priority, skip
            self.priorities[state] = self.DONE
            return (state, priority)
        return (None, None) # Nothing left...

在这段代码中,相当于建立了一个PriorityQueue类,小姐姐将其封装到util.py中,我们只需要import util就可以调用其中的函数。
def init(self)相当于对这个类进行了初始化。在初始化的过程中设置了Done、heap以及priorities,其中Done是用来做比较的,这个节点被遍历过后,就会将其的优先级设置为Done。heap为一个列表,存储的为此图的优先队列。priorities为一个字典,属于python的一种数据结构,类似于map。
update函数,将一个状态的优先级进行改变,如果这个状态未在优先级队列中,将其加入,如果在,则对比优先级,如果新的优先级小于久的优先级,则更新队列中此状态的优先级
removeMin是将优先队列中的优先级最小的(不为Done)的节点以及优先级返回,并将其节点的优先级设为Done。

CrossRiverProblem.py

import util

首先调用刚刚写好util这个文件,其中有我们想用的方法。

def allSubsets(s, limit):
    if len(s) == 0 or limit == 0:
        return [[]]
    return allSubsets(s[1:], limit) \
           + [[s[0]] + r for r in allSubsets(s[1:], limit-1)]

allSubsets函数简单的来说就是将一个集合返回其所有的子集(子集的长度小于limit)。其中这里的limit相当于船一次可以带走的动物数量。
eg:s=(1,4), return [(1,4),(1),(4),()]
这个函数的写法运用了递归,首先进行了判断,如果s这个集合的长度等于零或者limit为0的话,那么返回一个空的列表。如果不为0的话则继续调用这个函数,这里的''是代表着连接下一行的意思。[1:]是切片,选取[start:end]end默认为最后一个,start默认为第0个。也就是说,选出了除第一个外的所有元素,相应的limit进行减1,也就是在[1:]选取了长度limit-1或者长度小于limit-1的子集。然后再去除一个元素进行limit-1的筛选。也就是用s[0]对其筛选的元素进行一个合并。在python中,列表相加,相当于合并,类似于c++中的string相加。举个例子

nums1 = [1,2]
nums2 = [2,3]
nums = nums1+nums2
print(nums)

out:[1,2,2,3]
这个函数返回了农夫可以带走动植物的集合。

class CrossRiverProblem:
    # what is a state e.g. ((1,1,1,1,1), 0),((0,1,1,0,1), 0)
    # state 状态
    def __init__(self, N, S):
        self.N = N #N=5
        self.S = S #S=2

    # Return start state ((0,0,0,.....,0),0)
    def startState(self):
        return (tuple(0 for _ in range(self.N)), 0)

    # Return TRUE if state is a goal state ((1,1,.....,1),1)
    def isEnd(self, state):
        return state == ((tuple(1 for _ in range(self.N))), 1)
    # Return a list of successor states and costs
    # (后继节点和代价 of state)
    def succAndCost(self, state):
        print("expand: " + str(state))
        animals = state[0]
        ship = state[1]
        result = []
        for s in allSubsets([i for i in range(self.N) if animals[i] == ship], self.S):
            temp = list(animals)
            for i in s:
                temp[i] = 1-ship

            newState = (tuple(temp), 1-ship)
            if self.isValidState(newState):
                result.append((newState, 1))
        return result

    def isValidState(self, state):
        animals = state[0]
        ship = state[1]
        for i in range(self.N - 1):
            if animals[i] != ship and animals[i+1] != ship:
                return False
        return True

首先也是进行类的一个初始化,N为除农夫以外所有的动植物,S则为农夫一次可以带过河的最大数量。(这道题中N为5S为2)
startState,返回一个初始状态的元组,我们知道一开始农夫和所有的动植物并没有过河,所以状态全部为0,这里将农夫单独设置为一个元素。所以返回的为((0,0,0,0,0),0)
isEnd是判断是否结束,如果未结束的话,就是元组中含有0,于是这里就返回了与元组全为1比较的结果。
我们先说isValidState这个函数,判断当前状态是否符合安全状态,状态相邻的动植物在一个河岸且农夫不在,则为不安全状态。state[0]为动物的位置,假设初始状态state[0]为(0,0,0,0,0),state[1]=0。
succAndCost函数挑选和农夫在一个对岸的动植物进行子集合选取,也就是一开始的allSubsets函数,将农夫的位置进行调换,(从一变零,从零变一,这里采取的是用一减),判断是否符合安全状态,如果符合就添加到result中,result.append((newState, 1))是将(newState, 1)元组添加到result中。

# 等代价搜索
# (TotalCost of optimal solution, history of solution)
def uniformCostSearch(problem):
    state = problem.startState()
    open = util.PriorityQueue()
    open.update(state, 0)
    while True:
        state, pastCost = open.removeMin()
        if problem.isEnd(state):
            print("Total cost: " + str(pastCost))
            return pastCost, []
        for newState, cost in problem.succAndCost(state):
            open.update(newState, pastCost + cost)

等代价搜索,open为在util中写的优先队列类,其为算法对应的open表。然后就是进行等代价搜索。

posted on 2020-03-26 23:01  吕店老板  阅读(1583)  评论(0编辑  收藏  举报