算法分析-动态规划(矩阵链相乘,最长公共子序列,最长递增子序列)
1 #include <iostream> 2 using namespace std; 3 #define A_ROWS 3 4 #define A_COLUMNS 2 5 #define B_ROWS 2 6 #define B_COLUMNS 3 7 void matrix_multiply(int A[A_ROWS][A_COLUMNS],int B[B_ROWS][B_COLUMNS],int C[A_ROWS][B_COLUMNS]); 8 int main() 9 { 10 int A[A_ROWS][A_COLUMNS] = {1,0, 11 1,2, 12 1,1}; 13 int B[B_ROWS][B_COLUMNS] = {1,1,2, 14 2,1,2}; 15 int C[A_ROWS][B_COLUMNS] = {0}; 16 matrix_multiply(A,B,C); 17 for(int i=0;i<A_ROWS;i++) 18 { 19 for(int j=0;j<B_COLUMNS;j++) 20 cout<<C[i][j]<<" "; 21 cout<<endl; 22 } 23 return 0; 24 } 25 void matrix_multiply(int A[A_ROWS][A_COLUMNS],int B[B_ROWS][B_COLUMNS],int C[A_ROWS][B_COLUMNS]) 26 { 27 if(A_COLUMNS != B_ROWS) 28 cout<<"error: incompatible dimensions."<<endl; 29 else 30 { 31 int i,j,k; 32 for(i=0;i<A_ROWS;i++) 33 for(j=0;j<B_COLUMNS;j++) 34 { 35 C[i][j] = 0; 36 for(k=0;k<A_COLUMNS;k++) 37 C[i][j] += A[i][k] * B[k][j]; //将A的每一行的每一列与B的每一列的每一行的乘积求和 38 } 39 } 40 }
程序测试结果如下所示:
2、矩阵链乘问题描述
给定n个矩阵构成的一个链<A1,A2,A3,.......An>,其中i=1,2,...n,矩阵A的维数为pi-1pi,对乘积 A1A2...An 以一种最小化标量乘法次数的方式进行加全部括号。
注意:在矩阵链乘问题中,实际上并没有把矩阵相乘,目的是确定一个具有最小代价的矩阵相乘顺序。找出这样一个结合顺序使得相乘的代价最低。
3、动态规划分析过程
1)最优加全部括号的结构
动态规划第一步是寻找一个最优的子结构。假设现在要计算AiAi+1....Aj的值,计算Ai...j过程当中肯定会存在某个k值(i<=k<j)将Ai...j分成两部分,使得Ai...j的计算量最小。分成两个子问题Ai...k和Ak+1...j,需要继续递归寻找这两个子问题的最优解。
有分析可以到最优子结构为:假设AiAi+1....Aj的一个最优加全括号把乘积在Ak和Ak+1之间分开,则Ai..k和Ak+1..j也都是最优加全括号的。
2)一个递归解
设m[i,j]为计算机矩阵Ai...j所需的标量乘法运算次数的最小值,对此计算A1..n的最小代价就是m[1,n]。现在需要来递归定义m[i,j],分两种情况进行讨论如下:
当i==j时:m[i,j] = 0,(此时只包含一个矩阵)
当i<j 时:从步骤1中需要寻找一个k(i≤k<j)值,使得m[i,j] =min{m[i,k]+m[k+1,j]+pi-1pkpj} (i≤k<j)。
3)计算最优代价
虽然给出了递归解的过程,但是在实现的时候不采用递归实现,而是借助辅助空间,使用自底向上的表格进行实现。设矩阵Ai的维数为pi-1pi,i=1,2.....n。输入序列为:p=<p0,p1,...pn>,length[p] = n+1。使用m[n][n]保存m[i,j]的代价,s[n][n]保存计算m[i,j]时取得最优代价处k的值,最后可以用s中的记录构造一个最优解。书中给出了计算过程的伪代码,摘录如下:
1 // p是数组,表示维度 2 Matrix_Chain_Order = function (p) { 3 4 var n = p.length - 1; //表示几个矩阵 5 var m = []; //存最大值 6 var s = []; //存路径 7 8 9 //m[0][1] 表示一个矩阵 10 for (var i = 0; i <= n; i++) { 11 m[i] = []; 12 s[i] = []; 13 for (var j = 0; j <= n; j++) { 14 15 m[i][j] = 0; 16 s[i][j] = 0; 17 } 18 19 } 20 21 22 //表示几个矩阵相乘 23 for (var l = 2; l <= n; l++) { 24 25 for (var i = 1; i + l - 1 <= n; i++) { 26 27 var j = i + l - 1; 28 29 m[i][j] = Number.MAX_VALUE; 30 31 for (var k = i; k < j; k++) { 32 33 var temp = m[i][k] + m[k + 1][j] + p[i - 1] * p[k] * p[j]; 34 35 if (m[i][j] > temp) { 36 37 m[i][j] = temp; 38 s[i][j] = k; 39 } 40 41 } 42 } 43 } 44 45 return { 46 m: m, 47 s: s 48 }; 49 }; 50 51 52 var p = [30, 35, 15, 5, 10, 20, 25]; 53 54 var str = ""; 55 56 //构造最优势解 57 function Print_Optimal_Parens(s, i, j) { 58 59 if (i === j) { 60 61 str += "A" + i; 62 63 } else { 64 str += "("; 65 Print_Optimal_Parens(s, i, s[i][j]); 66 Print_Optimal_Parens(s, s[i][j] + 1, j); 67 68 str += ")"; 69 70 71 } 72 73 74 } 75 76 77 var obj = Matrix_Chain_Order(p); 78 var s = obj.s; 79 console.log(obj.m) 80 console.log(obj.s) 81 Print_Optimal_Parens(s, 1, s.length - 1); 82 console.log(str)
1 function LCS(A, B) { 2 3 var m = A.length; 4 var n = B.length; 5 var C = []; 6 var b = []; 7 8 for (var i = 0; i <= m; i++) { 9 C[i] = []; 10 b[i] = []; 11 for (var j = 0; j <= n; j++) { 12 13 C[i][j] = 0; 14 b[i][j] = 0; 15 } 16 } 17 18 19 for (var i = 1; i <= m; i++) { 20 for (var j = 1; j <= n; j++) { 21 if (A[i - 1] == B[j - 1]) { 22 C[i][j] = C[i - 1][j - 1] + 1; 23 b[i][j] = 'left_up'; 24 } else { 25 if (C[i - 1][j] >= C[i][j - 1]) { 26 C[i][j] = C[i - 1][j]; 27 b[i][j] = 'left'; 28 } else { 29 C[i][j] = C[i][j - 1]; 30 b[i][j] = 'up'; 31 } 32 } 33 } 34 } 35 return { 36 C: C, 37 b: b 38 }; 39 } 40 41 function zuiyou(A, b, i, j) { 42 if (i === 0 || j === 0) { 43 return; 44 } 45 if (b[i][j] == 'left_up') { 46 zuiyou(A, b, i - 1, j - 1); 47 console.log(A[i-1]); 48 } else if (b[i][j] == 'left') { 49 zuiyou(A, b, i - 1, j); 50 51 } else { 52 zuiyou(A, b, i, j - 1); 53 } 54 } 55 var A = ['A', 'B', 'C', 'B', 'D', 'A', 'B']; 56 var B = ['B', 'D', 'C', 'A', 'B', 'A']; 57 var len_a = A.length; 58 var len_b = B.length; 59 60 61 var obj = LCS(A, B); 62 console.log(obj.C); 63 console.log(obj.b); 64 65 66 zuiyou(A, obj.b, len_a, len_b);
最长递增子序列:
一, 最长递增子序列问题的描述
设L=<a1,a2,…,an>是n个不同的实数的序列,L的递增子序列是这样一个子序列Lin=<aK1,ak2,…,akm>,其中k1<k2<…<km且aK1<ak2<…<akm。求最大的m值。
二, 第一种算法:转化为LCS问题求解
设序列X=<b1,b2,…,bn>是对序列L=<a1,a2,…,an>按递增排好序的序列。那么显然X与L的最长公共子序列即为L的最长递增子序列。这样就把求最长递增子序列的问题转化为求最长公共子序列问题LCS了。
最长公共子序列问题用动态规划的算法可解。设Li=< a1,a2,…,ai>,Xj=< b1,b2,…,bj>,它们分别为L和X的子序列。令C[i,j]为Li与Xj的最长公共子序列的长度。则有递推方程看上面。
这可以用时间复杂度为O(n2)的算法求解,由于这个算法上课时讲过,所以具体代码在此略去。求最长递增子序列的算法时间复杂度由排序所用的O(nlogn)的时间加上求LCS的O(n2)的时间,算法的最坏时间复杂度为O(nlogn)+O(n2)=O(n2)。
三, 第二种算法:动态规划法
设f(i)表示L中以ai为末元素的最长递增子序列的长度。则有如下的递推方程:
f[i] = max(f[j]) + 1
这个递推方程的意思是,在求以ai为末元素的最长递增子序列时,找到所有序号在L前面且小于ai的元素aj,即j<i且aj<ai。如果这样的元素存在,那么对所有aj,都有一个以aj为末元素的最长递增子序列的长度f(j),把其中最大的f(j)选出来,那么f(i)就等于最大的f(j)加上1,即以ai为末元素的最长递增子序列,等于以使f(j)最大的那个aj为末元素的递增子序列最末再加上ai;如果这样的元素不存在,那么ai自身构成一个长度为1的以ai为末元素的递增子序列。
1 var arr = [1, 5, 8, 2, 3, 4]; 2 3 function lis(arr) { 4 5 var n = arr.length; 6 var c = []; 7 8 for (var i = 0; i <= n; i++) { 9 10 c[0] = 0; 11 12 } 13 14 15 for (var j = 1; j <= n; j++) { 16 17 c[j] = 1; 18 19 for (var k = 1; k < j; k++) { 20 if (arr[j-1] > arr[k-1] && c[k] >= c[j] - 1) { 21 22 c[j] = c[k] + 1; 23 24 } 25 26 } 27 28 } 29 30 return c; 31 } 32 33 var c =lis(arr); 34 console.log(c);