人工智能(6)Dynamic Programming

上次提到了,回溯(Backtrack),深度和广度搜索,一些演化的算法,比如规定了最大深度的深度搜索,或者叫做DFS with iterative deepening (DFS-ID),每一个的action都有一个固定的cost,假设最优方案的深度为d的话,空间和时间的复杂度:O(d)和O(b^d)。

总结一下之前讲过的这几种算法, 不难看出时间复杂度都是指数级别的

那么有没有一些方式能把时间复杂度降低一些呢?我们先来看一下动态规划。

上次提到了,对于一个搜索的问题,包含几个要素:

当前的状态S, 到达下一个状态的动作a,这一动作的代价c, 下一个状态(Succ (s,a), successor of S),最终的状态。

动态规划实际上就是这样的一个过程:

在当前状态S下,判断出来达到下一个状态S‘的动作的代价,然后通过某种方式计算一下这个状态到达最终状态的代价。要做的就是找出来代价和最小的下一个状态。

用这样的一个例子一起来看一下动态规划的一个过程:

假设现在有1,2,5,10块四种面值的纸币,现在问一下凑齐68块钱最少需要多少张纸币?

直觉地:先用10块的6张,凑不够10块的,用5块的一张,再用2块的和1块的各一张,刚好68块,毫无疑问这样的方法是正确的,这其实用到了贪心的思想(Greedy Method)。

试想一下,如果现在的钱币面值设置不是这样的,而是1,5,8,10 块的面值,要凑64块钱,贪心的方法会选6张10块,再选4张一块,但这不是用的最少的,8张8块的是最少的。遇到这样的一类问题,方案就是动态规划的方法。

假设现在要兑换N块的纸币,那么最优的方案需要的张数n是关于N的一个这样的递归函数:

n(N) = min {1+n(N-1);1+n(N-5); 1+n(N-8); 1+n(N-10)}

很明显有几个隐含的条件:如果N = 1,5,8,10, n(N) = 1, 一张就够了。选择的纸币的面值肯定不能超过要凑的纸币总数,比如要凑6块钱,8和10块肯定用不到了。代码实现也很容易:


# i_STEM,Dynamic programming

# 用1,5,8,10 块的纸币凑N块的问题

# 递归

import numpy as np

value = [1,5,8,10]

N = 40

def exchange(value,N, count = [0]):

    count[0]+= 1

    mn = N

    if N in value:

        print ("Recursion times:",count[0])

        return 1

    for i in [int(v) for v in value if int (v) <=N]:

        n = 1 + exchange(value,N-i,mem)

        if n < mn:

            mn = n

    return mn

exchange(value,N) #

但上面这段短短的代码,却要花很多时间去递归,举个例子,要算一下凑40块,要调用20多万次递归,,普通电脑可能花一分钟左右才跑完。。那么问题出现在哪儿呢?不难想象,很多重复的数据出现,重新进行计算,浪费了大量的时间。比如,凑68块,可能会用到前67块各自的需要的最少的数量的纸币,才能得出最后的最优的结果。如果把这些结果保存下来,就省去了重复计算的时间,这其实也是“学习”的一个过程,或者说“记忆”。很简单的,加几行代码就可以了。首先,声明存储的变量,函数调用的时候作为参数。接着,递归出口多了一个,除了面值等于1,5,8,10的之外,其他存储过的最少的纸币的张数也保存起来,到时候直接调用。这样显示,递归了80多次,调用了70多次,不到1秒钟就跑完程序。


# i_STEM,递归动态规划

import numpy as np

value = [1,5,8,10]

N = 40

mem = np.zeros([N+1]) # 存储所有N-1块的需要的纸币的最少的张数

def exchange(value,N, mem,count = [0,0]):

    count[0]+= 1

    mn = N

    if N in value:

        print ("Recursion times:",count[0])

        return 1

    elif mem[N] > 0:

        count[1]+= 1

        print ("Retrieval times:",count[1])

        return mem[N]

    for i in [int(v) for v in value if int (v) <=N]:

        n = 1 + exchange(value,N-i,mem)

        if n < mn:

            mn = n

            mem[N] = mn

    return mn

exchange(value,N, mem) #

print (mem)

那么有一个问题,程序跑完之后,所有1-39块的最少的张数都会保存起来吗?

答案是不一定,因为在求40块的张数的时候,是一个自顶向下的过程,并且,40块不一定需要的张数比20多,30多块的需要的多,自顶向下分解的时候,不一定会分解到每一个值。那么问题来了,能不能找到一种方法,把1-39块的每一种方案都找出来?

答案是肯定的,自底向上的过程可以确保顶层的问题肯定能从下面找到解答。同样的公式:

n(N) = min {1+n(N-1);1+n(N-5); 1+n(N-8); 1+n(N-10)}

在第一种方案中,n(N)代表递归的解决方案,这里n(N)表示的是存储的最优方案的值,也即不是去递归调用,而是直接从存储里面去取值,也就是说我们不需要使用递归就可以计算出来,无非是在原有的基础上试探所有四种纸币的可行性和解决方案是否最优。在程序实现的时候,每个n(N)的初始值都是N,也即,最坏的方案是都用1块去凑,然后每求一个n(N),依据N的大小,考虑这四种纸币拼凑的情况,在n(N-1), n(N-5), n(N-8), n(N-10)的基础上加1取值即可。这样的话时间复杂度是O(4*N),空间复杂度O(N)。来看一下代码的实现:


# i_STEM,自底向上动态规划

import numpy as np

value = [1,5,8,10]

N = 40

# 存储最优的方案,N+1个0的list,这样保证下标是0-40,41个数,虽然0下标的位置用不到,

# cost = [0]*(N + 1)

cost = np.zeros([N+1])

def exchange(value, N, cost):

    # N重循环

    for i in range(1,N+1):

        # 初始化为最差的方案,全部用1块

        cost[i] = i

        # 最多四重循环

        for j in [k for k in value if k <= i]:

            # 读取i-j块需要的张数,再加一张即可,j一定是不大于i,需要注意的是cost[0]=0保持不变

            cost_temp = cost[i-j] + 1

            if cost_temp <= cost[i]:

                cost[i] = cost_temp

    return cost

exchange(value, N, cost)

如果要想存储解决的方案,也即哪些纸币被用来去凑N块,声明另外一个变量,cost,求N块的方案,存储最后一个纸币的数额m,也即coin[N]=m,再找出N-m的最后一张纸币的数额,这样就可以把整个方案的找出来。来看一下代码实现:


# i_STEM,自底向上动态规划,存储方案

import numpy as np

value = [1,5,8,10]

N = 40

# 存储最优的方案,N+1个0的list,这样保证下标是0-40,41个数,虽然0下标的位置用不到,

# cost = [0]*(N + 1)

cost = np.zeros([N+1])

# 求N块的方案,存储最后一个纸币的数额m,也即coin[N]=m,再找出N-m的最后一张纸币的数额,这样就可以把整个方案的找出来,

coin = np.zeros([N+1])

def exchange(value, N, cost):

    # N重循环

    for i in range(1,N+1):

        # 初始化为最差的方案,全部用1块

        cost[i] = i

        # 最多四重循环

        for j in [k for k in value if k <= i]:

            # 读取i-j块需要的张数,再加一张即可,j一定是不大于i,需要注意的是cost[0]=0保持不变

            cost_temp = cost[i-j] + 1

            if cost_temp <= cost[i]:

                cost[i] = cost_temp

                coin[i] = j

    return cost,coin

exchange(value, N, cost)

简单的总结,上面的前三种方案中,第一种单纯使用递归,而不使用存储的话,花费大量的时间;第二种方案,使用了一些存储,同时也有递归调用,节约了大量的时间;第三种方案,最大限度的使用存储,没有使用递归,自底向上的解决了问题,也节省了很多的时间。适当的使用存储,来节约大量的时间成本,还是很合算的。

类似的拿少量存储去节省大量时间的例子还有很多,在python中,递归调用的层数不能超过1000层,再考虑到时间成本,所以大量的运算的时候,合适的使用存储很有必要。除此之外,动态规划在很多领域中都可以见到,比如在生物信息学中,做DNA序列的alignment的时候,动态规划是非常常见的例子,比如Needleman–Wunsch 算法

再回到搜索中来,搜索中的动态规划的几个要素上面提到了,当前状态S,动作a,动作的代价Cost(S,a)和下一个状态Succ(s,a),以及终止状态的判断。与上面所说的不太一样的地方是,上面的问题每一个动作的代价是一样的,增加一张纸币,只是这张纸币选择的上一个状态不尽相同,导致整个方案的代价也不同,取其中最优的。

在搜索中,

FutureCost (S) = 0, 如果是终止状态;

FutureCost (S) = min [Cost(S,a) + FutureCost (Succ(s,a))], 不是终止状态。

好,动态规划是非常重要的一种思想,我们从时间和存储成本的角度简单的分析了一下这样的应用,在不同的行业领域,都会不同的实际应用的方案,但抽象化的中心思想是一致的。后面遇到更经典的动态规划案例,再补充分享。下次来看一下搜索中 uniform cost search的实现。

Reference:

Problem Solving with Algorithms and Data Structures

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

推荐阅读更多精彩内容