170 likes | 385 Views
算法设计与分析 第三章 动态规划. 杨圣洪. 3.3 最长公共子序列. 概述: 若给定序列 X={x 1 ,x 2 ,…,x m } ,取其 连续 或 不连续 部分字符串,称为 子串 或 子序列 。 ( 板 ) 如序列 X={A , B , C , B , D , A , B} ,则 Z={B , C , D , B} 是 X 的子序列,其下标序列为 {2 , 3 , 5 , 7} ,显然不是 X 中连续字符串。 若 Z 既是 X 的子序列,又是 Y 的子序列,则称 Z 是序列 X 和 Y 的 公共子序列 。 ( 板 )
E N D
算法设计与分析第三章 动态规划 杨圣洪
3.3 最长公共子序列 • 概述: • 若给定序列X={x1,x2,…,xm},取其连续或不连续部分字符串,称为子串或子序列。(板) • 如序列X={A,B,C,B,D,A,B},则Z={B,C,D,B}是X的子序列,其下标序列为 {2,3,5,7},显然不是X中连续字符串。 • 若Z既是X的子序列,又是Y的子序列,则称Z是序列X和Y的公共子序列。(板) • 问题:给定序列X={x1,x2,…,xm}和Y={y1, y2, …,yn},如何找出X和Y的最长公共子序列。
3.3 最长公共子序列 请找出下列两个序列的最长公共子序列 X={A,B,C,B,D,A,B} (板) Y={B,D,C,A,B,A} (板) 提示: 回忆离散中子集的生成方法? 生成各自所有子串,则2m2n,一一比对,指数级! 是否可你一个我一个的找呢? 请把你的思路详细描述之。
分治法的基本思想 • 分治法的基本思想 • 将一个规模为n的问题分解为k个规模较小的子问题,这些子问题互相独立且与原问题相同。 • 对这k个子问题分别求解。如果子问题的规模仍然不够小,则再划分为k个子问题,如此递归的进行下去,直到问题规模足够小,很容易求出其解为止。 • 将求出的小规模的问题的解合并为一个更大规模的问题的解,自底向上逐步求出原来问题的解。 • 将一个难以直接解决的大问题,分割成一些规模较小的相同问题,以便各个击破,分而治之。
T(n) T(n) T(n) n = n = n = T(n/2) T(n/2) T(n/2) T(n/2) n/2 n/2 n/2 n/2 n/2 n/2 n/2 n/2 T(n/4) T(n/4) T(n/4) T(n/4) T(n/4) T(n/4) T(n/4) T(n/4) T(n/4) T(n/4) T(n/4) T(n/4) T(n/4) T(n/4) T(n/4) T(n/4) T(n/4) T(n/4) T(n/4) T(n/4) T(n/4) T(n/4) T(n/4) T(n/4) T(n/4) T(n/4) T(n/4) 动态规划算法 • 动态规划算法也是将待求解问题分解成若干个子问题。 • 分治法要求子问题互相独立。子问题数目太多,最后T(n)可能指数级。有些子问题被重复计算。 • 动态规划保存已解决的子问题的答案,需要时引用,避免重复计算,从而T(n)为多项式时间。
动态规划基本步骤 • 找出最优解的性质,并刻划其结构特征。判断整体最优是否有局部最优 • 递归定义最优值。有难度。 • 以自底向上的方式计算出最优值。非递归 • 根据计算最优值时得到的信息,构造最优解。!
全局最优局部最优? • 设X={x1,x2,…,xm}和Y={y1,y2,…,yn} • 最长公共子序列Z={z1,z2,…,zk} ,则(板) • 若xm=yn(最后者同则必属公串),则zk=xm=yn,且Zk-1是Xm-1和Yn-1的最长公共子序列。全优局优 • 若xm≠yn且zk≠xm,则Z是Xm-1和Y的最长公子串 • 若xm≠yn且zk≠yn,则Z是X和Yn-1的最长公子串 • 因此Z是整体最长公共子序列时,其部分也是是X与Y的前面部分中的最优子串。 • 满足“全局最优局部最优”特点,具有最优子结构性质,可动态规划。
全局最优局部最优? • X={x1,x2,…,xm}和Y={y1,y2,…,yn},最长公共子序列Z={z1, z2, …, zk} ,则 若xm=yn,则zk=xm=yn且Zk-1是Xm-1和Yn-1的最长公共子序列 • 反证法求证zk=xm=yn。假设zk xm。 • 由于X、Y的最后字符xm=yn,而Z此时已经是X、Y公共子串,故Z+'xm'=Z+'yn'也是X,Y公共子串,其长度=Z长+1>Z长,与Z最长矛盾。 • 反证法求证“Zk-1是Xm-1和Yn-1的最长公共子序列” • 假设Zk-1不是Xm-1,Yn-1的最长公共子串, W 是Xm-1,Yn-1的最长公共子串, • 则|W|>|Zk-1|即|W|k,而X,Y的最后字符相同, • 故W+'xm'为X,Y的公共子串,其长=|W|+1k+1, • 而前面已知X,Y的最长公共子串长为k.故矛盾
全局最优局部最优? • 设X={x1,x2,…,xm}和Y={y1,y2,…,yn},其最长公共子序列Z={z1,z2,…,zk} ,则: • (1)若xm=yn(最后者同则必属公串),则zk=xm=yn,且Zk-1是Xm-1和Yn-1的最长公共子序列。 • (2)若xm≠yn且xm≠zk,则Z是Xm-1和Y最长公子串 • (3)若xm≠yn且zk≠yn,则Z是X和Yn-1最长公子串 • 证(2):知Z是{x1,x2,…,xm-1,xm},Y最长子串且长为k • 又zkxm即Z与{x1,x2,…,xm-1,xm} 的最后字符xm不同 • 故Z是{x1,x2,…,xm-1},Y公共子串 • 假设“Xm-1与Y有长>k”的公共子串W,则|W|>k • 因X,Y最后字符不同即xmyn,则“Xm-1,Y的公共子串W”也是X,Y的公共子串。即X,Y公子串长为k+1 • 而X,Y的最长公共子串长为k,故矛盾。
算法描述 • 若xm=yn(最后者同则必属公串),则zk=xm=yn,且Zk-1是Xm-1和Yn-1的最长公共子序列。 • 若xm≠yn且zk≠xm,则Z是Xm-1和Y的最长公子串 • 若xm≠yn且zk≠yn,则Z是X和Yn-1的最长公子串 • 算法基本思想:其实递归 分治与动规都要这步 • 当xm=yn(最后者同)时, maxStr(Xm-1,Yn-1)+xm(=yn)得maxStr(X,Y)。 (板) • 当xm yn(最后者异)时, maxStr(Xm-1,Yn), maxStr( Xm,Yn-1) 中较长者为maxStr(X,Y)。 (板) • 总之,计算maxStr(X,Y) ,要算maxStr(Xm-1,Yn-1), maxStr(Xm-1,Yn), maxStr(Xm,Yn-1) (板) • maxStr(Xm-1,Yn-1)公共子问题, • 重叠可备忘,每个问题都算到可动态规划
子问题的递归结构 • 递归关系: 序列X的前缀Xi ={x1,x2,…,xi}, 序列Y的前缀Yj ={y1,y2,…,yj}。 • c[i][j]保存Xi与Yj的最长公共子序列的长度。 • 当i=0或j=0时即空序列,故C[0][j]=0或C[i][0]=0 。此处c[i][j],两变元(i,j),m[i][j] • 递归: x[i]=y[j]各退1否单退1最大者(板C示算)
计算最优值 • c[i][j]的下标取值范围可知,共有θ(mn)个不同的子问题,每个子问题都会算到,故动态规划算法自底向上,比备忘递归法效率更高。 void LCSLength(int m,int n,char *x,char *y,int **c,int **b){ int i,j; for (i = 1; i <= m; i++) c[i][0] = 0;//初值 for (i = 1; i <= n; i++) c[0][i] = 0;//初值 for (i = 1; i <= m; i++) //X的第1个字符~最后一个 for (j = 1; j <= n; j++) { //针对X[i],Y从第1个~最后一个 if (x[i]==y[j]) { //(Xn,Yn)=(Xn-1,Yn-1)的公串+xn. c[i][j]=c[i-1][j-1]+1; b[i][j]=1; } else if (c[i-1][j]>=c[i][j-1]) { //(Xm,Yn)=(Xm-1,Yn) c[i][j]=c[i-1][j]; b[i][j]=2;} else { //(Xm,Yn)=(Xm,Yn-1) c[i][j]=c[i][j-1]; b[i][j]=3; } } } //在js中b,c为全局变量,未出现在形参中
1对角前,2上行同列, 3同行左列 4,构造最长公共子序列 • 根据b[i][j]构造最长公子串。 • 算法描述: void LCS(int i,int j,char *x,int **b) { if (i ==0 || j==0) return; if (b[i][j]== 1){ //(Xn,Yn)=(Xn-1,Yn-1)的公串+xn. LCS(i-1,j-1,x,b); cout<<x[i]; } else if (b[i][j]== 2){ //(Xm,Yn)=(Xm-1,Yn) LCS(i-1,j,x,b);} else { //(Xm,Yn)=(Xm,Yn-1) LCS(i,j-1,x,b);} }
算法的改进 • 在算法lcsLength和lcs中,可进一步将数组b省去。 • c[i][j]的值仅由c[i-1][j-1],c[i-1][j]和c[i][j-1] 确定。 • 对于给定的数组元素c[i][j],可以不借助于数组b而仅借助于c本身在时间内确定c[i][j]的值是由c[i-1][j-1],c[i-1][j]和c[i][j-1]中哪一个值所确定的。 • 如果只需要计算最长公共子序列的长度,则算法的空间需求可大大减少。事实上,在计算c[i][j]时,只用到数组c的第i行和第i-1行。因此,用2行的数组空间就可以计算出最长公共子序列的长度。进一步的分析还可将空间需求减至O(min(m,n))。
计算最优值 • 不要数组B void LCSLengthB(int m,int n,char *x,char *y,int **c,int **b){ int i,j; for (i = 1; i <= m; i++) c[i][0] = 0;//初值 for (i = 1; i <= n; i++) c[0][i] = 0;//初值 for (i = 1; i <= m; i++) //X的第1个字符~最后一个 for (j = 1; j <= n; j++) { //针对X[i],Y从第1个~最后一个 if (x[i]==y[j]) { //(Xn,Yn)=(Xn-1,Yn-1)的公串+xn. c[i][j]=c[i-1][j-1]+1;} //b[i][j]=1; else if (c[i-1][j]>=c[i][j-1]) { //(Xm,Yn)=(Xm-1,Yn) c[i][j]=c[i-1][j];} //b[i][j]=2; else { //(Xm,Yn)=(Xm,Yn-1) c[i][j]=c[i][j-1];} // b[i][j]=3; } }
算法的改进 • 在算法lcsLength和lcs中,将数组b省去。 void LCSB(int i,int j,char *x,int **b) { if (i ==0 || j==0) return; if (c[i][j]==c[i-1][j-1]+1){ //(Xn,Yn)=(Xn-1,Yn-1) +xn. LCSB(i-1,j-1,x,b); cout<<x[i]; } else if (c[i-1][j]>c[i][j-1]){ //(Xm,Yn)=(Xm-1,Yn) LCSB(i-1,j,x,b);} else { //(Xm,Yn)=(Xm,Yn-1) LCSB(i,j-1,x,b);} }
算法的改进 • 改lcs返回最长公子串 String LCSC(int i,int j,char *x,int **b) { if (i ==0 || j==0) return ""; if (c[i][j]==c[i-1][j-1]+1){ //(Xn,Yn)=(Xn-1,Yn-1) +xn. return LCSC(i-1,j-1,x,b)+x[i]; } else if (c[i-1][j]>c[i][j-1]){ //(Xm,Yn)=(Xm-1,Yn) return LCSC(i-1,j,x,b);} else { //(Xm,Yn)=(Xm,Yn-1) return LCSC(i,j-1,x,b);} }