把常用的算法在好好总结学习一下,翻了翻去年的博文还是用c写的,拿出来扩充一下。
目录
动态规划
原理
将原问题分成规模更小的子问题,并且原问题的最优解包含子问题的最优解
三要素
最优子结构、边界、状态转移公式
一般有四个步骤:定义最优子问题->定义状态->定义决策和状态转换方程->确定边界条件
是解决多阶段决策问题常用的最优化理论,由美国数学家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
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | |
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | 0 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 6 |
2 | 0 | 0 | 6 | 6 | 9 | 9 | 9 | 9 | 9 | 9 | 9 |
3 | 0 | 0 | 6 | 6 | 9 | 9 | 9 | 9 | 11 | 11 | 14 |
4 | 0 | 0 | 6 | 6 | 9 | 9 | 9 | 10 | 11 | 13 | 14 |
5 | 0 | 0 | 6 | 6 | 9 | 9 | 12 | 12 | 15 | 15 | 15 |
当装入第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、拼凑面额
给你六种面额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]