【常用算法】动态规划

把常用的算法在好好总结学习一下,翻了翻去年的博文还是用c写的,拿出来扩充一下。

 

目录

动态规划

原理

三要素

两个特性

1、0-1背包问题

分析

代码(python)

2、拼凑面额

分析

代码(python)

3、字符串的编辑距离

朴素的递归算法(C)

动态规划(C)

动态规划(python)


 

 

动态规划

原理

将原问题分成规模更小的子问题,并且原问题的最优解包含子问题的最优解

三要素

最优子结构、边界、状态转移公式

一般有四个步骤:定义最优子问题->定义状态->定义决策和状态转换方程->确定边界条件

是解决多阶段决策问题常用的最优化理论,由美国数学家Bellman等人在1957年提出,用于研究多阶段决策过程的优化问题。

适合求解多阶段(状态转移)决策问题的最优解,也可用于含线性问题或非线性递推关系的最优解问题。需满足以下两个特性。

两个特性

最优化原理:不管之前的决策是否是最优决策,都必须保证从现在开始的决策是在之前决策基础上的最优决策。

无后向性(无后效性):每个阶段的决策仅受之前决策的影响,但是不影响之后各阶段的决策。

有重叠子问题:即子问题之间是不独立的,一个子问题在下一阶段决策中可能被多次使用到。(该性质并不是动态规划适用的必要条件,但是如果没有这条性质,动态规划算法同其他算法相比就不具备优势)

 

1、0-1背包问题

参考博文:Java数据结构与算法:动态规划

有编号为 a,b,c,d,e 的5件宝物在山洞,它们的重量分别是2、2、6、5、4,它们的价值分别是6、3、5、4、6,现在给你一个承重为 10 的背包。请问怎么装背包,才能带走最多的财富?

分析

首先定义背包容量 c=10 ,重量数组 w = [2, 2, 6, 5, 4] ,价值数组 p = [6, 3, 5, 4, 6] ,bag[i][c] 表示承重为c的背包背走标号为 i,i+1,……的宝物的最优解

从宝物a开始,如果装此宝物,那么背包容量 c-w[a] ,得到财富 p[a] ;如果放弃此宝物,背板容量和价值不变。

所以背包问题 bag[i][c] 的最优价就是 bag[i-1][c] 不装入此件宝物和 bag[i-1][c-w[i-1]]+p[i-1] 装入此件宝物的最大值

由此得到递推公式/状态转移公式

# 背包放得下
if j >= w[i-1]:
    bag[i][j] = max(bag[i-1][j], bag[i-1][j-w[i-1]]+p[i-1])
# 背包放不下该物体
elif j < w[i-1]:
bag[i][j] = bag[i-1][j]

首先建立一个(n+1)*(c+1)的列表,横坐标表示包的容量大小,纵坐标表示物品。初始时每行每列的所有元素都为0

 012345678910
000000000000
100666666666
200669999999
300669999111114
4006699910111314
50066991212151515

当装入第1件物品时,重量为2,价值为6。所以只有当包的容量>2时才能放进这个物品,此时包里物品的价值就是6。

当装入第2件物品时,重量为2,价值为3。所以只有当包的容量>2时才能放进这个物品,但与物品1相比只能放进去一样,选择价值更高的放;包的容量>4时两个物品都能放进去,价值为9。

……

以此类推

 

代码(python)

import numpy as np
def bags(w, p, c, n):
    bag = [[0 for j in range(c+1)] for i in range(n+1)]    # (n+1)*(c+1)
    # bag = [[0] * (c + 1) for i in range(n + 1)]
    # print(np.array(bag))
    for i in range(1, n+1):
        for j in range(1, c+1):
            # print(i, j)
            if j >= w[i-1]:  # 背包放得下
                bag[i][j] = max(bag[i-1][j], bag[i-1][j-w[i-1]]+p[i-1])
            else:
                bag[i][j] = bag[i-1][j]
    return bag
if __name__ == "__main__":
    n = 5
    w = [2, 2, 6, 5, 4]
    p = [6, 3, 5, 4, 6]
    c = 10
    bag = bags(w, p, c, n)
    print(np.array(bag))
    x = []
    for i in range(n, 1, -1):
        if bag[i][c] != bag[i-1][c]:
            x.append(w[i-1])
            c = c - w[i-1]
    if bag[1][c] > 0:
        x.append(w[0])
    print(x)

 

2、拼凑面额

牛客网原题

参考:算法题 - 拼凑面额 - Python

给你六种面额1、5、10、20、50、100元的纸币,假设每种币值的数量都足够多,编写程序求组成N元(N为0-10000的非负整数)的不同组合的个数。

分析

类似于背包问题,首先定义nums = [1, 5, 10, 20, 50, 100],n元钱用最大面值不超过m元的基本面值组合起来的个数,记为A(n,m)。

举个例子,10元用这些面值来组合A(10,100),最大面值是100元,远大于需要组合的面值,那面用更小面值的纸币。

A(10,100) = A(10,50) = A(10,10)

这个时候最大面值是10,可以组合成要表示的数额。那么用掉一张10块,剩下0元;也可以不用这个10块,那么最高面值变成5.

组合数就可以这么表示:

A(10,10) = A(0,10) + A(10,5)

用一张图说明一下(图片来自这道题下面‘mindle’的回答)

 

得到递推公式/状态转移公式

# 如果所需组合的钱大于面值,即够付
if i >= nums[j]:
    dp[i][j] = dp[i-nums[j]][j]+dp[i][j-1]
else:
    dp[i][j] = dp[i][j-1]

 

代码(python)

nums = [1, 5, 10, 20, 50, 100]
n = int(input())
def solve(nums, n):
    dp = [[1 for j in range(len(nums))] for i in range(n+1)]
    for j in range(1, len(nums)):
        for i in range(1, n+1):
            if i >= nums[j]:
                dp[i][j] = dp[i-nums[j]][j]+dp[i][j-1]
            else:
                dp[i][j] = dp[i][j-1]
    return dp[-1][-1]
print(solve(nums, n))

 


2019/05


2018/03


 

3、字符串的编辑距离

参考书目:《算法的乐趣》作者王晓华

 

参考博文:两个字符串的编辑距离-动态规划方法

 

两个字符的相似度定义为:将一个字符串转换成另一个字符串时需要付出的代价。其中,字符串的操作包括以下三种:

  • 删除一个字符     a) Insert a character
  • 插入一个字符     b) Delete a character
  • 修改一个字符     c) Replace a character

 

朴素的递归算法(C)

/*
2018-3-22
朴素的递归算法实现:字符串的编辑距离
copyright @GCN
*/

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

int min(int a,int b)
{
    if(a>b)
        return b;
    else
        return a;
}

int EditDistance(char *src,char *dest)
{
    if(strlen(src)==0||strlen(dest)==0)
        return abs(strlen(src)-strlen(dest));
    if(src[0]==dest[0])
        return EditDistance(src+1,dest+1);
    int edIns=EditDistance(src,dest+1)+1;//source insert
    int edDel=EditDistance(src+1,dest)+1;//source delete
    int edRep=EditDistance(src+1,dest+1)+1;//source replace
    return min(min(edIns,edDel),edRep);
}

int main()
{
    char *src="SNOWY";
    char *dest="sunny";
    int cost;
    cost=EditDistance(src,dest);
    printf("the final answer is %d\n",cost);
    return 0;
}

 

(发现两次结果不一样,还以为写错了。呃,原来是我输入的字符串的大小写问题。)

动态规划(C)

然后用动态规划对朴素的递归算法进行改进

/*
2018-3-23
动态规划:字符串的编辑距离
copyright @GCN
*/

#define MAX_STRING_LEN 100
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

int min(int a,int b)
{
    if(a<b)
        return a;
    else
        return b;
}

int EditDistance(char *src,char *dest)
{
    int i,j;
    int d[MAX_STRING_LEN][MAX_STRING_LEN]={0xFFFF};
    int edIns,edDel,edRep;

    for(i=0;i<=strlen(src);i++)
        d[i][0]=i;
    for(j=0;j<=strlen(dest);j++)
        d[0][j]=j;
    for(i=1;i<=strlen(src);i++)
    {
        for(j=1;j<=strlen(dest);j++)
        {
            if((src[i-1]==dest[j-1]))
                d[i][j]=d[i-1][j-1];//字符匹配,不需要任何操作
            else
            {
                edIns=d[i][j-1]+1;//source 插入字符
                edDel=d[i-1][j]+1;//source 删除字符
                edRep=d[i-1][j-1]+1;//source 替换字符

                d[i][j]=min(min(edIns,edDel),edRep);
            }
        }
    }
    return d[strlen(src)][strlen(dest)];
}

int main()
{
    char *src="SNOWY";
    char *dest="SUNNY";

    int cost=EditDistance(src,dest);
    printf("the final result is %d\n",cost);
    return 0;
}

动态规划(python)

# 问题描述:给定两个字符串word1和word2,求字符串word1至少经过多少步字符操作变成字符串word2。
class Solution:
    def minDistance(self, word1: str, word2: str) -> int:
        n = len(word1)
        m = len(word2)
        
        if n * m == 0:
            return n + m
        
        # 目标串作为列,模板为行
        d = [ [0] * (m + 1) for _ in range(n + 1)]
        for i in range(n + 1):
            d[i][0] = i
        for j in range(m + 1):
            d[0][j] = j

        for i in range(1, n + 1):
            for j in range(1, m + 1): 
                if word1[i - 1] == word2[j - 1]:	#!
                    d[i][j] = d[i - 1][j - 1]
                else:
                    d[i][j] = min(d[i - 1][j], d[i][j - 1], d[i - 1][j - 1])+1
        # 往下是删除,往左是添加,往斜对角:要么不变要么改
        return d[-1][-1]

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

故沉

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值