晓强Deep Learning的读书分享会,先从这里开始,从大学开始。大家好,我是晓强,计算机科学与技术专业研究生在读。我会不定时的更新我的文章,内容可能包括深度学习入门知识,具体包括CV,NLP方向的基础知识和学习的论文;网络表征学习的相关论文解读。当然我每天的读书心得也会分享给大家,可能涉及我们生活各个方面的书籍。我也会不定时回答大家的问题与大家一同进步,共同交流,互相监督,结交更多的朋友。希望大家多留言,多交流,多多关照。我在这里等你一同学习,如果需要相关资料也可以私信我,进入我们的群大家庭。
【晓白】今天准备了开题的报告,又想想自己,发现弱的一批,我还是老老实实更新我的文章吧,咱们可以互相学习,互相进步。如果喜欢请点关注,请多点赞。事情较多,但是今天脑袋里始终浮现出一句话,论语:君子忧道不忧贫,君子谋道不谋食。仔细一想很是受教,很开心与精神合伙人们分享。加油,奥利给!今天我们继续分享算法设计与分析的第三章-动态规划。
本章提要:
动态规划基本原理
用动态规划方法求解一些问题,如
3.1 动态规划基本原理
Dynamic Programming 的基本思想:把原始问题划分成一系列子问题,自底向上计算 ,求解每个子问题仅一次,并将其结果保留在一 个表中,以后用到时直接存取,不用重复计算,节省时间。
适用范围: 一类优化问题:可分为多个相关子问题,子问题的解被重复使用。
动态规划方法:
与分治法类似,也是将要求解的问题一层一层地分解成一级一级规模逐步缩小的子问题,直到可以直接求解的子问题为止。自底向上求解,原问题的解依赖于子问题树中所有子问题的解。与分治法不同的是,子问题往往不是相互独立的,子问题树中的子问题呈现大量的重复 。
使用Dynamic Programming 的条件:
Optimal Substructure(最优子结构)
当一个问题的优化解包含了子问题的优化解时,我们说这个问题具有优化子结构,优化子结构使得我们能自底向上完成求解过程
Overlaping Subproblems (重叠子问题)
在问题的求解过程中,很多子问题的解将被多次使用
Dynamic Programming算法的设计步骤:
分析最优解的结构
递归地定义最优解的代价(最优值)
递归划分子问题,直至不可分
自底向上计算最优值并保存之;并记录构造最优解的信息,根据构造最优解的信息,构造最优解。
3.2 矩阵连乘最佳计算次序问题(Matrix-chain Multiplication)
问题定义:
输入:给定n个矩阵{A1,A2,…,An}。其中 Ai和Ai+1是可乘的,i =1,2,…,n-1。
输出:计算出这n个矩阵的连乘积A1A2…An的最小代价的方法。
矩阵连乘积的代价:乘法的次数。
例如:若A是p ╳ q矩阵, B是q ╳ r矩阵,则A ╳ B的代价是O(pqr)。
矩阵乘法满足结合律。计算一个矩阵链的乘法可有多种方法:
例如:A1A2A3A4=(A1((A2A3)A4)
=((A1A2)(A3A4))
=((A1(A2A3))A4),
=(((A1A2)A3)A4)。
矩阵链乘法的代价与计算顺序的关系:
设A1为10 ╳ 100矩阵, A2为100 ╳ 5矩阵, A3为5 ╳ 50矩阵
则 T((A1A2)A3)= 10 ╳ 100 ╳ 5+ 10 ╳ 5 ╳ 50=7500
T((A1(A2A3))= 10 0╳ 5 ╳ 50+ 10 ╳ 100 ╳ 50=75000
结论:不同计算顺序有不同代价。因而需要确定最佳乘法次序--- 优化问题。
1.分析最优解的结构
矩阵链乘积问题具有:最优子结构性质;子问题的重叠性。
2.建立递归关系式
m[i,j] (1≤i≤j≤n):记录计算矩阵链Ai¨Aj乘积所需的最少乘法次数。
s[i,j] (1≤i≤j≤n):记录计算矩阵链Ai¨Aj乘积的最少乘法次数对应的断开点k。
m[i,j]可以递归地定义为:
3.递归地划分子问题,直至不可分
4.计算最优值
例:确定计算矩阵连乘积A1A2A3A4A5A6的最优次序。其中各个矩阵的维数为:
用动态规划法计算m[i,j]和s[i,j]如下图所示:
下面所给出的计算m[i][j]的动态规划算法MatrixChain中, 输入:序列P={p0,p1,…,pn}, 输出:除了最优值m[i][j]外,还有使 m[i, j]= m[i, k]+ m[k+1, j]+ pi-1 pk 达到最优的断开位置(k=s[i, j], 1≤i≤j≤n)。
for (int r=2; r<=n; r++) // r是链长,或者说是斜线的号
for( int i=1; i<=n-r+1; i++) // i是行号
{ int j=i+r-1; // j是列号
m[i][j] = m[i+1][j]+p[i-1]*p[i]*p[j];//最小数乘次数初值
s[i][j] = i; //最佳断点初值
for (int k=i+1; k<j; k++) //确定哪个断点最好
{ int t =m[i][k]+m[k+1][j]+p[i-1]*p[k]*p[j];
if (t<m[i][j])
{ m[i][j] = t; S[i][j]= k; }
}
}
MatrixChain算法的主要计算量取决于程序中对r, i,和k的三重循环,三重循环的总次数为O(n3);循环体内的计算量为O(1);因此,该算法的计算时间上界为O(n3) 。算法所占用的空间显然为O(n2) 。 由此可见,动态规划算法比穷举搜索法要有效的多。
5. 构造最优解
下面的算法按MatrixChain算法计算出的断点矩阵s指示的加括号方式输出计算A[i:j]的最优计算次序。
void Traceback(int i, int j, int **s)
{ if (i==j)
print “Ai”;
else
print “(”;
Traceback(i, s[i][j], s);
Traceback(s[i][j]+1, j, s);
print “)”;
}
构造最优解的时间:O(n)
二 、最长公共子序列 (Longest Common Subsequence)
<1> 问题定义
子序列:一个给定序列的子序列是在该序列中删去若干元素后得到的序列。
例:X={A,B,C,B,D,B}
Z={B,C,D,B} X的子序列。
W={B,D,A} X的子序列。
公共子序列:
如果序列Z是序列X的子序列,也是序列Y的子序列,则Z是序列X与Y的公共子序列。
例:X={A,B,C,B,D,A,B}, Y={B, D,C,A,B,A}
Z={B,C,A} X与Y的公共子序列。
给定两个序列X={x1,x2,…xm}和Y={y1,y2,…yn},
要求找出X和Y的最长公共子序列。
例: X={A,B,C,B,D,A,B}
Y={B, D, C,A,B,A}
Z={B,C,A}是X与Y的一个公共子序列。
但L={B,C,B,A}是X与Y的一个最长公共子序列。 因为X与Y没有长度大于4的LCS。V={B,C,A,B}也是X与Y的一个最长公共子序列。( LCS不唯一!)
<2> 最长公共子序列(LCS)结构分析:
第i前缀:设X=<x1,x2,…xm>是一个序列,X的第i前缀Xi是一个序列,定义为Xi=<x1,x2,…xi>。
定理(LCS的最优子结构性质):
设X={x1,x2,…xm}和Y={y1,y2,…yn}是两个序列,
Z={z1,z2,…zk}是X与Y的LCS,则
(1)若xm=yn,则zk=xm=yn,且Zk-1是Xm-1和Yn-1的LCS。
(2)若xm≠yn,且zk≠xm,则Z是Xm-1和Y的LCS。
(3)若xm≠yn,且zk≠yn,则Z是X和Yn-1的LCS。
反证法:
定理(LCS的最优子结构性质):
设X={x1,x2,…xm}和Y={y1,y2,…yn}是两个序列,
Z={z1,z2,…zk}是X与Y的LCS,则若xm=yn,则zk=xm=yn,且Zk-1是Xm-1和Yn-1的 LCS。
(2)若xm≠yn,且zk≠xm,则Z是Xm-1和Y的LCS。
(3)若xm≠yn,且zk≠yn,则Z是X和Y n-1的LCS。
定理(LCS的最优子结构性质):
设X={x1,x2,…xm}和Y={y1,y2,…yn}是两个序列,
Z={z1,z2,…zk}是X与Y的LCS,则
若xm=yn,则zk=xm=yn,且Zk-1是Xm-1和Yn-1的LCS。
这个定理告诉我们,两个序列的最长公共子序列包含了这两个序列的前缀的最长公共子序列。
因此,最长公共子序列问题具有最优子结构性质。
<3> 建立求解LCS长度的递归方程
c[i][j]=Xi与Yj的LCS的长度。
LCS长度的递归方程如下:
<4> 递归划分子问题与自底向上计算LCS长度
计算LCS长度的算法
数据结构:
c[0:m,0:n]: c[i][j]是Xi和Yj的的LCS的长度。
b[1:m,1:n]: b[i][j]是指针,指向计算c[i][j]时所选择的子问题的优化解所对应的c表的表项。
算法:
void LCSLength(int m, int n, char *x, char *y, int **c, Type **b)
{ int i, j;
for(i=0; i<=m; i++) c[i][0]=0;
for(j=0; j<=n; j++) c[0][j]=0;
for(i=1; i<=m; i++)
for(j=1; j<=n; j++)
{ if (x[i]==y[j])
{ c[i][j]= c[i-1][ j-1]+1; b[i][j]=‘↖’;}
else if (c[i-1][j]>= c[i][j-1])
{c[i][j]= c[i-1][j]; b[i][j]=‘↑’;}
else {c[i][j]= c[i][j-1]; b[i][j]=‘←’;}
}
<5> 构造LCS (构造优化解)
基本思想:
从b[m][n]开始按指针搜索;
若b[i][j]=“↖”,则xi= yj是LCS的一个元素;
如此找到的LCS是X与Y的inverse
算法:
void LCS(int i, int j, char *x, Type **b)
{ if( (i==0) || (j==0) ) return ;
if (b[i][j]==‘↖’)
{ LCS(i-1, j-1, x, b); cout<< x[i];}
else if (b[i][j]==‘↑’) LCS(i-1, j, x, b);
else LCS(i, j-1, x, b);
<6>时间复杂性
计算代价的时间:
i, j两层循环,i循环m步,j循环n步,
由于每个数组单元的计算耗费O(1)时间,
算法LCSLength耗时O(mn)。 (P58)
构造最优解的时间:
算法LCS中,每一次递归调用使i或j减1,因此算法的计算时间为O(m+n)
∴T(n)= O(mn)。
空间复杂性:使用了二维数组b和c,∴S(n)= O(mn)。
三、求最大子段和
给定由n个整数(可能为负整数)组成的序列a1, a2,…, an, 求该序列形如 的子段和的最大值。当所有整数均为负整数时定义其最大子段和为0。依此定义,所求的最优值为:
例如, 当(a1,a2,a3,a4 ,a5,a6) = (-2,11,-4,13,-5,-2)时,最大子段和为:
最大子段和问题的简单算法
用数组a[ ]存储给定的n个整数a1, a2,…, an。
int MaxSum(int n, int a*, int & besti, int & bestj)
{ int sum=0;
for (int i=1; i<=n; i++)
for (int j=i; j<=n; j++)
{ int thissum=0;
for (int k=i; k<=j; k++) thissum+=a[k];
if (thissum>sum)
{ sum= thissum;
besti= i;
bestj=j;
}
}
return sum;
}
从这个算法的3个for循环可以看出它所需的计算时间是O(n3)事实上,如果我们注意到
则可将算法中的最后一个循环省去,避免重复计算,从而使算法得以改进。改进后的算法可描述为:
2.最大子段和问题的分治算法
从最大子段和问题的解的结构可以看出,它适合用分治法求解。
a[1:n]的最大子段和有三种情形:
(1) a[1:n]的最大子段和与a[1:n/2]的最大子段和相同。
(2) a[1:n]的最大子段和与a[n/2+1:n]的最大子段和相同。
(3) a[1:n]的最大子段和为 ,
且1≤i≤n/2, n/2+1≤j≤n。
其中(1)和(2)这两种情形可递归求得。
对于情形(3), a[n/2]与a[n/2+1]在最优子序列中,a[1:n]的最大子段和是a[1:n/2]的最大子段和与a[n/2+1:n]的最大,例如, 当(a1,a2,a3,a4 ,a5,a6) = (-2,11,-4,13,-5,-2)时,最大子段和为:Max{11,13,20}
求最大子段和的分治算法如下:
int MaxSubSum(int *a, int left, int right)
{ int sum =0;
if (left==right) sum= a[left] >0?a[left]:0;
else { int center = (left+right)/2;
int leftsum = MaxSubSum(a, left, center);
int rightsum = MaxSubSum(a, center+1, right );
int s1 =0; int lefts =0;
for (int i=center; i>=left; i--)
{ lefts += a[i]; if (lefts >s1) s1= lefts; }
int s2 =0; int rights =0;
for (int i=center+1; i<=right; i++)
{ rights += a[i]; if (rights >s2) s2= rights; }
sum = s1+s2;
if (sum < leftsum) sum = leftsum;
if (sum < rightsum) sum = rightsum;
}
return sum; }
分治算法的时间复杂性分析
3. 最大子段和问题的动态规划算法
(3)计算最优值
据此,可设计出求最大子段和的动态规划算法如下:
int MaxSum(int n, int *a)
{ int sum=0,b=0,i=0,besti=0,bestj=0;
for (int j=1; j<=n; j++)
{ if (b>0) b+= a[j];
else {b=a[j];i=j;}
if (b > sum) {sum=b; besti=i; bestj=j;}
}
return sum;
}
所有顶点对之间的最短路径 (All Pairs Shortest Paths)
.问 题 定 义 :对于给定的有向网络G=(V,E),要对G中任意两个顶点v,w(v≠w),找出v到w的最短路径。
1. 轮流以每一个顶点为源点,重复执行迪杰斯特拉算法n次,即可求得每一对顶点之间的最短路径,总的时间复杂度为
2.弗洛伊德(Flo yd)给出了一种更简洁的方法。【动态规划算法】
2.分析最优解的结构
最优子结构性质
子问题的重叠性
3.递归地定义最优解的代价
A0[i][j]=C[i][j]
Ak[i][j]= min{Ak-1[i][j],
Ak-1[i][k]+Ak-1[k][j]}
(1≤k≤n)
其中C矩阵是图的矩阵。
4.递归地划分问题,直至不分
An[i][j] (1≤i, j≤n)
An-1[i][j] (1≤i, j≤n)
… … … …
A1[i][j] (1≤i, j≤n)
A0[i][j] (1≤i, j≤n)
A0[i][j]=C[i][j]
Ak[i][j]= min{Ak-1[i][j],
Ak-1[i][k]+Ak-1[k][j]} (1≤k≤n)
5.自底向上求解各个子问题
子问题最优解递归方程:
A0[i][j]=C[i][j]
Ak[i][j]= min{Ak-1[i][j],
Ak-1[i][k]+Ak-1[k][j]}
(1≤k≤n)
弗洛伊德算法
int path[n][n]; float A[ ][n];
FLOYD(float C[ ][n])
{int max=∞;
for (i=0;i<n; i++)
for (j=0;j<n;j++)
{if(C[i][j]!= max)
path[i][j]=j+1;
else path[i][j]= 0;
A[i][j]=C[i][j];
}
for (k=0;k<n; k++)
for (i=0;i<n; i++)
for (j=0;j<n;j++)
if(A[i][j]>(A[i][k]+A[k][j]))
{A[i][j]= A[i][k]+A[k][j];
path[i][j]=path[i][k];
Analysis: T(n)=O(n3)
6.构造最优解
输出顶点间最短路径及长度的算法
int path[n][n]; float A[ ][n];
printPaths()
{ for (i=0;i<n; i++)
for (j=0;j<n;j++)
{printf(“%f”,A[i][j]);
next=path[i][j];
if(next== 0)
printf(“%d to %d
no path.n”,i+1,j+1);
else
{printf(“%d”,i+1);
while(next!=j+1) {printf(“%d”, next);
next=path[next-1][j];
}
printf(“%dn”,j+1);
}
}
}
3.5 0/1 Knapsack Problem
问题定义
问题求解
分析优化解结构
建立优化解代价的递归方程
递归地划分子问题
自底向上计算优化解的代价
记录优化解的构造信息
构造优化解
问题定义:给定n种物品和一个背包,物品i的重量是wi,价值vi, 背包承重为C, 问如何选择装入背包的物品,使装入背包中的物品的总价值最大? 对于每种物品只能选择完全装入或不装入,一个物品至多装入一次。
问题优化子结构:
定理 如果Si=(yi, yi+1, …, yn)是0-1背包子问题Pi=[{i, i+1, …, n}, Ci=C- 1ki-1wkyk]的优化解,则(yi+1, …, yn)是如下子问题Pi+1的优化解
证明: 如果Si+1=(yi+1, …, yn)不是子问题Pi+1的优化解, 则存在S’i+1=(zi+1, …, zn)是Pi+1的更优解。S’i=(yi, zi+1, …, zn)是问题Pi之比Si更的优解,与Si优化矛盾。
子问题重叠性:
算法:for ( j=0 ; j< wn; j++)
m[n][j] = 0;
for ( j=wn ;j<= C; j++)
m[n][j] = vn;
for ( i=n-1 ; i>=2 ; i--)
{for ( j=0 ; j<= wi -1; j++)
m[i][j] = m[i+1][j];
for (j=wi ; j<=C ; j++)
m[i][j]=max{m[i+1][j], m[i+1][j-wi]+vi};
}
if (C<w1 ) m[1][C]=m[2][C];
else m[1][C]=max{m[2][C], m[2][C-w1]+v1};
Idea:
1. m(1, C)是最优解代价值,相应解计算如下:
If m(1, C) = m(2, C)
Then x1 = 0;
Else x1 = 1;
2. 如果 x1=0, 由m(2, C)继续构造x2;
3. 如果 x1=1, 由m(2, C-w1)继续构造x2;
……
时间复杂性
计算代价的时间=O(Cn)
构造最优解时间=O(n)
总时间复杂性为
T(n)=O(Cn)
空间复杂性
使用数组m
需要空间S(n)=O(Cn)
自此,几个动态规划问题更新完毕,这部分知识很难,不好掌握。精神合伙人们加油。
【晓议】今天技术文章更新完毕,算法设计与分析计算机基础知识的第三篇。如果有需要继续补充的,我会在后面继续更新。每一部分的讲解,请关注我之后私信我,我会发给大家。您如果在计算机入门时或者想转行学习计算专业的知识,有什么问题也可以一起讨论解决。谢谢大家的关注和分享。如果对您有帮助,请帮我点赞,谢谢。附上之前的文章连接,方便大家学习。关注我,看更多更精彩的文章,我会按每天的进度安排,不定时更新对大家有益的内容。如果精神合伙人们对学习计算机方面的知识,可以私信与我交流。下面是我写的深度学习和其他文章,点击链接阅读。
晓强DL:第二章 递归与分治zhuanlan.zhihu.com