本博客为讲解过河问题,具体问题如下:
你想运送五个动植物过河,分别是 (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表。然后就是进行等代价搜索。