三个水桶等分8升水问题python实现--穷举、简单状态转移与递归

最近在看算法的乐趣,其中有个很简单的三个水桶等分8升水的问题。

题目:有三个容积分别是3升、5升、8升的水桶,其中容积为8升的水桶中装满了水,容积为3升和5升的水桶中是空的。三个水桶都没有体积刻度,现在需要将水桶中的8升水等分成两份,每份都是4升水,附加条件是只能使用另外两个空桶,不能借用其他辅助容器。

  • 题目是个经典的简单问题,书中说人脑算很简单,通过倒水凑出1升水空间就行。但是用计算机求解的话就要利用经典的状态转移和穷举搜索算法。

简单题解:

  • 把三个水桶中的水量作为一个状态,经过一个合法动作后得到下一个状态(合法动作是把A桶中的水全部倒入B桶 或者 把A桶中的水部分倒入B桶使B桶充满水),递归搜索所有的可能状态,如果到达最终状态则递归停止。
状态图 引用自http://blog.csdn.net/orbit/article/details/6596521

题目的关键:

  1. 由当前状态得到所有可能的合法动作
  2. 由合法动作得到下一个状态
  3. 建立一个状态队列来实现状态记录以及检查新状态是否在队列中已经出现,避免形成死循环
  4. 递归搜索,在达到final状态时终止

  • 书里只给出了C++的实现,在这里用python实现试试

主要使用的python知识点:

  1. deque队列
    例如deque.append()#右端插入
    deque.pop()#右端删除
  2. 列表推导
    例如[x*x for x in range(10) if x % 3 == 0]得出10以内能被3整除的数的平方构成的列表
  3. yield生成器
    例如经典的斐波那契数列的生成
def fib(max):
    a, b = 1, 1  
    while a < max:  
        yield a #generators return an iterator that returns a stream of values.  
        a, b = b, a+b
for n in fib(15):  
     print n  

踩过的坑:

  1. python的引用机制,即浅复制机制,a=b并没有创建一个新的对象而是建立了对于b对象的一个引用,对于a的修改同样会改变b。犯过错误的地方在代码中有说明。可以使用new_list = list(old_list)new_deque = deque(old_deque)来生成新的对象,实现深复制。或者使用copy也行。
  2. 在同级的循环中,即所有下一个状态的同级遍历中,进入的时候对队列末尾添加了新状态,退出的时候一定不要忘记删除该新状态。虽然很简单的逻辑,但是第一次实现时确实忽略了,汗。
initial_bucket_state = [0,0,8]
#水桶的初始状态
bucket_volume = [3,5,8]
#每个水桶的对应的容积
from collections import deque
record = deque()
record.append(initial_bucket_state)
#利用python的deque队列记录状态转移情况,初始化时加入水桶初始状态。deque是可以从头尾插入和删除的队列,在不指定大小时,为一个无边界的队列


def nextStateLawful(current_state, bucket_volume):
    next_action = [
        (from_, to_) 
        for from_ in range(3) for to_ in range(3) 
        if from_ != to_ 
            and current_state[from_] > 0 
            and current_state[to_] < bucket_volume[to_] 
    ]
    #通过列表推导式获得下一动作的二元组构成的列表,由(倒出水的容器编号,倒入水的容器编号)组成。
    #二重循环得到下一步的所有可能动作,然后通过
    ##1.倒入倒出不能为同一个2.倒出的捅中必须有水3.倒入的桶中不能为满 的条件判断是否合法
    for from_, to_ in next_action:
        #next_state = current_state #浅复制造成错误
        next_state = list(current_state)
        if current_state[from_] + current_state[to_] > bucket_volume[to_]:
            next_state[from_] -= (bucket_volume[to_] - current_state[to_])
            next_state[to_] = bucket_volume[to_]
        else:
            next_state[from_] = 0
            next_state[to_] = current_state[to_] + current_state[from_]
        yield next_state
        #再由所有可能的合法动作得出所有的下一个状态,通过yield产生供其它函数调用。

num = 0
record_list = []
#记录调试的变量:num表示总共实现方法数,record_list记录所有实现路径

def searchResult(record, bucket_volume=[3,5,8], final_bucket_state=[0,4,4]):

    global num,record_list
    current_state = record[-1]
    #由record的末尾元素得到当前水桶状态
    next_state = nextStateLawful(current_state, bucket_volume)
    #得到关于当前状态的下一状态的可迭代生成器,供下一步循环使用
    for state in next_state:
    #遍历所有可能的下一状态
        if state not in record:
            #保证当前状态没在以前出现过。如果状态已经出现还进行搜索就会形成状态环路,陷入死循环。
            record.append(state)
            #添加新的状态到列表中
            if state == final_bucket_state:
                print(record)
                #打印出可行方案
                #record_list.append(record)这样使用错误,导致加入列表的是record的引用,应该使用下面的式子来进行深复制,得到一个新的队列再加入列表。
                record_list.append(deque(record))
                num += 1
            else:
                searchResult(record, bucket_volume, final_bucket_state)
                #不是最终状态则递归搜索
            record.pop()
            #去除当前循环中添加的状态,进入下一个循环,关键步,第一次实现的时候遗漏了

if __name__=='__main__':            
    searchResult(record)
    #开始
    print(num)
    #打印所有方案的数量
    print(min([len(i) for i in record_list]))
    #打印最短路径方案中的状态总数
题外话: 算法的乐趣是个好书,看个序就感觉大牛的气息扑面而来。推荐
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,636评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,890评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,680评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,766评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,665评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,045评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,515评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,182评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,334评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,274评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,319评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,002评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,599评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,675评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,917评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,309评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,885评论 2 341

推荐阅读更多精彩内容