农夫过河游戏规则:在左岸有农夫、狼、羊、菜,农夫需要想办法将狼、羊、菜最终都带到右岸,条件就是农夫不在的时候,狼会吃羊,羊会吃菜,而且每次只能带一样,或者不带。
这里会用到堆栈、队列、列表这样的数据结构,以及广度优先搜索和深度优先搜索两种算法。
代码来自 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')

#羊      不带    白菜    羊      狼      不带    羊