农夫过河游戏规则:在左岸有农夫、狼、羊、菜,农夫需要想办法将狼、羊、菜最终都带到右岸,条件就是农夫不在的时候,狼会吃羊,羊会吃菜,而且每次只能带一样,或者不带。
这里会用到堆栈、队列、列表这样的数据结构,以及广度优先搜索和深度优先搜索两种算法。
代码来自 aaybvh 一个努力学习的在校生,由于代码有点长,需要一些解释,于是抽个时间,将代码做了很多详细的注释,希望可以帮助到他,有兴趣的可以看下:
DFS与BFS
class Queue(list): # 队列的性质是先进先出,操作:队尾插入,队头删除
def __init__(self):
list.__init__(self)
def Enqueue(self, s): # 入队
self.append(s)
def Dequeue(self): # 出队
return self.pop(0)
def Print(self): # 打印队列
print('count:%d' % (len(self)))
for item in self:
item.Print()
def Empty(self): # 是否为空队列
if len(self) == 0:
return True
return False
class Stack(list): # 堆栈的性质是先进后出,操作:栈顶进行插入和删除
def __init__(self):
list.__init__(self)
def Push(self, s): # 入栈
self.append(s)
def Pop(self): # 出栈
return self.pop()
def Print(self): # 打印堆栈,索引从大开始
idx = len(self) - 1
while idx >= 0:
self[idx].Print()
idx = idx - 1
def PrintReverse(self): # 翻转打印
print('count:%d' % (len(self)))
for item in self:
item.Print()
def Top(self): # 栈顶元素
return self[len(self) - 1]
def Empty(self): # 是否为空栈
if len(self) == 0:
return True
return False
class State:
'''
农夫、狼、羊、菜的状态
初始状态为(0,0,0,0)都在左岸,当全部运送到对岸(右岸)时状态为(1,1,1,1)
'''
def __init__(self, f, w, s, v):
self.f = f
self.w = w
self.s = s
self.v = v
# 用于广度优先搜索
self.father_list = MyList()
self.direction = ''
def SetDirection(self, d): # 用于广度优先搜索
self.direction = d
def SetFather(self, f):
self.father = f
def __eq__(self, s): # 重写__eq__方法,这样就可以直接用"=="来判断对象否相等(当对象属性都相等时)
return self.w == s.w and self.f == s.f and self.s == s.s and self.v == s.v
def Print(self):
print('过河状态:(%d,%d,%d,%d)' % (self.f, self.w, self.s, self.v))
def Copy(self, s):
self.f = s.f
self.w = s.w
self.s = s.s
self.v = s.v
def IsValid(self): # 状态是否可行
if self.f == 0: # 农夫在左岸
if self.w == self.s and self.w == 1: # 如果狼和羊在同岸且都在右岸,就不行
return False
elif self.s == self.v and self.s == 1: # 如果羊和菜在同岸且都在右岸,也不行
return False
else:
return True
elif self.f == 1: # 农夫在右岸
if self.w == self.s and self.w == 0: # 同理,狼和羊不能同时在左岸
return False
elif self.s == self.v and self.s == 0: # 同理,羊和菜不能同时在左岸
return False
else:
return True
class MyList(list):
'''
保存的是状态的列表
状态是指农夫、狼、羊、菜在左岸还是右岸
Has:是否存在这个状态
Remove:删除指定状态
'''
def __init__(self):
list.__init__(self)
def Has(self, s):
for item in self:
if item == s:
return True
return False
def Remove(self, s):
for i in range(0, len(self)):
if self[i] == s:
return self.pop(i)
# 第i样东西送到右岸
def L(news, s, i):
news.Copy(s)
if news.f == 0: # 农夫需在左岸
if 0 == i: # 农夫过河不带东西
pass
elif 1 == i and news.w == 0: # 带狼过去,且在左岸
news.w = 1
elif 2 == i and news.s == 0: # 带羊过去
news.s = 1
elif 3 == i and news.v == 0: # 带菜过去
news.v = 1
else:
return False
news.f = 1 # 农夫到了右岸
return True
else:
return False
# 第i样东西送到左岸
def R(news, s, i):
news.Copy(s)
if news.f == 1: # 农夫需在右岸
if 0 == i: # 农夫过河不带东西
pass
elif 1 == i and news.w == 1: # 将狼从右岸带到左岸
news.w = 0
elif 2 == i and news.s == 1: # 带羊过去
news.s = 0
elif 3 == i and news.v == 1: # 带菜过去
news.v = 0
else:
return False
news.f = 0 # 农夫到了左岸
return True
else:
return False
def IsValid(s):
if s.f == 0: # 农夫在左岸
if s.w == s.s and s.w == 1: # 狼和羊不能同时在右岸
return False
elif s.s == s.v and s.s == 1: # 羊和菜不能同时在右岸
return False
else:
return True
elif s.f == 1: # 农夫在右岸
if s.w == s.s and s.w == 0: # 狼和羊不能同时在左岸
return False
elif s.s == s.v and s.s == 0: # 羊和菜不能同时在左岸
return False
else:
return True
def DFS(s, e, stack, close_list, direction):
''''
深度优先搜索
s:起始状态,e:结束状态。由于重写了__eq__方法,所以两个对象是可以直接做==比较
'''
if s == e: # 起始状态更新到与结束状态一样就打印出来,意思就是东西已全部运送到了对岸
global road_num
road_num += 1
print('第%d条路径:' % road_num)
stack.Push(s)
stack.PrintReverse()
stack.Pop()
return
# 加入close_list列表,表示已经访问过
close_list.append(s)
stack.Push(s)
if direction == 'L': # 农夫在左岸
for i in range(0, 4):
news = State(0, 0, 0, 0)
# 将东西带到右岸,而且要保证是有效状态以及没有被访问过,然后递归调用DFS,方向变为右岸
if L(news, s, i) and news.IsValid() and close_list.Has(news) == False:
DFS(news, e, stack, close_list, 'R')
elif direction == 'R': # 右岸带东西去左岸,同理
for i in range(0, 4):
news = State(0, 0, 0, 0)
if R(news, s, i) and news.IsValid() and close_list.Has(news) == False:
DFS(news, e, stack, close_list, 'L')
close_list.Remove(s) # 清空,为下一次的搜索做准备
stack.Pop()
start_state = State(0, 0, 0, 0)
end_state = State(1, 1, 1, 1)
direction = 'L'
road_num = 0
# close_list表存放访问过的状态
close_list = MyList()
stack = Stack()
print('----------深度优先搜索-------------')
DFS(start_state, end_state, stack, close_list, direction)
def PrintPath(s, e, close_list): # 广度优先搜索打印
if s == e:
global road_num
road_num += 1
print('第%d条路径:' % road_num)
print('(%d,%d,%d,%d)' % (e.f, e.w, e.s, e.v))
for state in close_list:
print('(%d,%d,%d,%d)' % (state.f, state.w, state.s, state.v))
# 加入到访问表中
else:
close_list.insert(0, s)
for state in s.father_list:
PrintPath(state, e, close_list)
close_list.Remove(s)
def BFS(start_state, end_state):
'''
广度优先搜索
start_state:起始状态和end_state:结束状态
'''
close_list = MyList()
open_list = Queue()
open_list.Enqueue(start_state)
return_state = None
while open_list.Empty() == False:
state = open_list.Dequeue()
# 判断状态state是否在已访问的列表当中
if close_list.Has(state):
state_real = close_list.Remove(state)
state_real.father_list += state.father_list
close_list.append(state_real)
continue
else:
close_list.append(state)
# 是否是目标状态
if state == end_state:
return_state = state
continue
if state.direction == 'L': # 农夫在左岸
for i in range(0, 4):
news = State(0, 0, 0, 0)
# 将东西带到右岸,而且要保证是有效状态以及没有被访问过,就添加到列表中,然后修改方向
if L(news, state, i) and news.IsValid() and close_list.Has(news) == False:
news.father_list.append(state)
news.SetDirection('R')
open_list.Enqueue(news)
else:
for i in range(0, 4):
news = State(0, 0, 0, 0)
if R(news, state, i) and news.IsValid() and close_list.Has(news) == False:
news.father_list.append(state)
news.SetDirection('L')
open_list.Enqueue(news)
return return_state
start_state.SetDirection('L')
end_state = BFS(start_state, end_state)
close_list = MyList()
print('----------广度优先搜索-------------')
road_num = 0
PrintPath(end_state, start_state, close_list)
'''
----------深度优先搜索-------------
第1条路径:
count:8
过河状态:(0,0,0,0)
过河状态:(1,0,1,0)
过河状态:(0,0,1,0)
过河状态:(1,1,1,0)
过河状态:(0,1,0,0)
过河状态:(1,1,0,1)
过河状态:(0,1,0,1)
过河状态:(1,1,1,1)
第2条路径:
count:8
过河状态:(0,0,0,0)
过河状态:(1,0,1,0)
过河状态:(0,0,1,0)
过河状态:(1,0,1,1)
过河状态:(0,0,0,1)
过河状态:(1,1,0,1)
过河状态:(0,1,0,1)
过河状态:(1,1,1,1)
----------广度优先搜索-------------
第1条路径:
(0,0,0,0)
(1,0,1,0)
(0,0,1,0)
(1,1,1,0)
(0,1,0,0)
(1,1,0,1)
(0,1,0,1)
(1,1,1,1)
第2条路径:
(0,0,0,0)
(1,0,1,0)
(0,0,1,0)
(1,0,1,1)
(0,0,0,1)
(1,1,0,1)
(0,1,0,1)
(1,1,1,1)
'''
从中我们也了解到,要将东西从左岸搬到右岸,从右岸搬到左岸,这里的岸就可以设置成两种状态,0为左岸,1为右岸。每种状态都是农夫、狼、羊、菜四个元素,每个元素两种状态,然后通过它们一起呈现的状态来决定是否是可行状态,比如(0,1,1,0)农夫和菜在左岸,狼和羊在右岸,这样就不行,因为农夫不在身边,狼会吃掉羊。
我们通过农夫的来回过河,带走或不带走某个东西从而改变其状态,最终是(1,1,1,1)这样的状态,就表示农夫将狼、羊、菜都运送到了右岸。
其中的两种优先搜索方法,解释如下:
深度优先搜索算法(Depth First Search):简称DFS,是一种用于遍历或搜索树或图的算法。该算法沿着树的深度遍历树的节点,会尽可能深的搜索树的分支。当节点v的所在边都己被探寻过,搜索将回溯到发现节点v的那条边的起始节点。这一过程一直进行到已发现从源节点可达的所有节点为止。如果还存在未被发现的节点,则选择其中一个作为源节点并重复以上过程,整个进程反复进行直到所有节点都被访问为止。
深度优先搜索使用的是回溯思想,这种思想很适合使用「递归」来实现。而递归对问题的处理顺序,遵循了「后进先出」的规律。所以递归问题的处理,需要借助「堆栈」来实现。
广度优先搜索算法(Breadth First Search),又称宽度优先搜索,简称BFS,与深度优先搜索不同的是,广度优先搜索会先将与起始点距离较近的点搜索完毕,再搜索更远的点
BFS从起点开始,优先搜索离起点最近的点,然后由这个点最近的点扩展到其他稍近的点,这样一层一层扩展,就像水波扩散一样。
__eq__重写
其中对于__eq__重写的用法,再写个例子让大家更加清晰的了解,重写这个方法之后,就可以直接用“==”来判断两个对象是不是相等了。就是当对象的属性都相等时,这个对象是相等的。
#__eq__重写
class A(object):
def __eq__(self, s):
print("A __eq__ called")
return self.v1 == s and self.v2==s
class B(object):
def __eq__(self, s):
print("B __eq__ called")
return self.v1 == s and self.v2==s
a=A()
a.v1="hello"
a.v2="tony"
b=B()
b.v1='hello'
b.v2="chyichin"
print(a==b)
print(a.v1==b.v1)
print(a.v2==b.v2)
'''
A __eq__ called
B __eq__ called
False
True
False
'''
随机交换
如果单纯的只是让农夫将狼、羊、菜带到对岸的话,还可以一种比较讨巧的方法,就是让农夫随机的交换,因为农夫不在的时候,只需要狼和羊不在一起,或者羊和菜不在一起就可以,当然这样也会产生一些无效的步骤,然后我们将它们删除掉,这样也可以得到农夫过河的步骤。
import random
left = ['白菜', '羊', '狼'] # 左岸的东西带到右岸
right = ['空'] * 3 # 右岸为空
things = [] # 储存每一步所带的东西
cnt = 0 # 奇数:农夫在左岸,偶数农夫在右岸
while True:
# 首先农夫在左岸(奇数)
# 或者农夫在右岸,同样右岸可以是狼羊或羊白菜
if cnt % 2 == 1 and ('狼' in left and '羊' in left or '羊' in left and '白菜' in left) \
or cnt % 2 == 0 and ('狼' in right and '羊' in right or '羊' in right and '白菜' in right):
left = ['白菜', '羊', '狼']
right = ['空'] * 3
things = []
cnt = 0
continue
i = random.randint(-1, 2) # -1,0,1,2表示带什么东西过河的索引,-1就是不带东西
things.append(i)
# 需带东西过河时,如果农夫在右岸,左岸有东西就交换,属于随机交换
if i != -1 and cnt % 2 == 0 and left[i] != '空':
left[i], right[i] = right[i], left[i]
# 如果农夫在左岸,右岸有东西,进行交换
elif i != -1 and cnt % 2 == 1 and right[i] != '空':
left[i], right[i] = right[i], left[i]
else:
# 不带东西
things[-1] = -1
# 如果全部带到右岸就退出
if right == ['白菜', '羊', '狼']:
break
cnt += 1
lit = ['不带', '白菜', '羊', '狼']
#-------无效的步骤设置为空数组,并移除掉-----
for i in range(len(things)-1):
if things[i] == things[i+1]:
things[i] = things[i+1] = []
while [] in things:
things.remove([])
#-----------------------------------------
for i in things:
print(lit[i+1], end='\t')
#羊 不带 白菜 羊 狼 不带 羊