算法笔记(问题分解,分治与动态规划)
2011-11-27 21:45 java线程例子 阅读(275) 评论(0) 编辑 收藏 举报问题分解就是通过对问题进行分解,从而将问题分解成有限个问题规模更小的子问题,从而达到问题求解的过程或者方法。问题分解是一种从问题规模出发来进行问题求解的方法,一般来讲,问题的规模与问题的复杂性密切相关,通过降低问题的规模可以达到减低问题复杂性,从而易于问题的求解。分治策略和动态规划都是这种思想。其实问题分解是问题求解思维中的一种基本的思维方式。当然问题能通过分解来获得答案,最关键的地方就是原问题的答案与分解后的子问题之间存在着关联关系,也就是原问题的解可以通过对分解后的子问题的解经过简单运算或选择就可得到(分治策略中是合并),这是问题能否通过分解进行求解的关键所在。,当然,如果原问题能够从子问题集中到处解,一个基本的要求是子问题之间必须是相互独立的。一般来讲,如果问题可以通过分解成子问题来求解,我们总希望能寻找到一种递归表达式,从而便于计算,特别是计算机的处理。怎么寻找这种表达式呢?
1)首先是观察,获取分解的原子规模的解,一般是观察规模为1..m的情况(m一般很小,很容易就能得出解),比如最快装配线问题中的只有1个装配站,2个装配站的情况,从而获得对于解的感性认识,可以帮助对递归表达式的猜测;
2)其次是分析规模n的解与子问题(规模k,k<n)解之间的关系,比较多的是倍数关系(n=αk,α>1)和线性关系T(n)=a1*T(n-b1)+a2*T(n-b2)+...;多项式关系(比如矩阵乘法计算顺序最优化求解问题);
3)通过对猜测或者推理获得递归表达式。
上述分析得到的递归表达式很容易得到递归运算程序,无论是动态规划还是分治策略都可以。
通过对分解的子问题进行观察,我们可以发现,这些得到的子问题在求解过程中有的只出现一次,有的会出现多次,2分查找的子问题就只出现一次,而Fibonacci的子问题就会出现多次。如果子问题只出现一次,其实就是算法导论中的分治策略,如果子问题会出现多次,就是算法导论中的子问题重叠,就可以通过保存子问题的运算结果来加快运算(递归方式中的备忘录方法也属于此,这样子问题就不用重复计算),其效率是非常可观的,可以将问题的时间复杂度从指数级降到多项式级别(比如矩阵乘法循序问题),这就是动态规划的核心思想(其实到现在都不明白为什么叫动态规划,难道就是因为中间结果表格的填写是动态的?)。
对于上面的倍数关系和线性关系中的a2及以上为0的情况,一般适用分治策略的处理,即用递归完成,而对于线性关系中有两项或以上的常数不为0的情况,则适合用动态规划来完成求解。
几个关系:
1)问题不一定能分解
2)分解后的子问题不一定能导出原问题的解;
3) 分解后的子问题可以导出原问题的解,但不一定能表达成递归表达式
4)分解后子问题不一定相互独立(比如有向图的最大无权路径问题,但如果是有向无环图则不一样,子问题是相互独立的,可求解的)
对于分治策略类的问题相对比较好处理,容易找到其递归表达式,而对于动态规划而言,就比较难了,很多时候还需要证明子问题间的独立性,其递归表达式的获取也并不十分容易,更多依靠的是数学功底,经验和运气,但上述的3个一般步骤的第1步中的观察和小规模下的求解体验还是十分有用的。
后面是算法中的实现:
public class DynamicPrograming { /// <summary> /// 求最快装配路径。其基本思想都是通过对规模n的问题分解成有限个更小规模的子问题,来解决问题; /// 分治策略要求分解后的子问题间相互独立,而动态规划则没有这个要求,但要求子问题重叠,关键其实也在这里, /// 如果子问题不重叠,使用自底而上的动态规划求解就不会比自顶而下的递归优越多少。 /// </summary> /// <param name="a"></param> /// <param name="t"></param> /// <param name="e"></param> /// <param name="x"></param> /// <param name="n"></param> /// <returns></returns> public int[] FastWay(int[,] a,int[,] t,int[] e,int[] x,int n) { int[] theLine1 = new int[n]; int[] theLine2 = new int[n]; theLine1[0] = 1; theLine2[0] = 2; int f1j = e[0] + a[0, 0]; int f2j = e[1] + a[1, 0]; int theF1j = f1j; int theF2j = f2j; for (int j = 1; j < n; j++) { if (f1j + a[0, j] <= f2j + t[1, j - 1] + a[0, j]) { theF1j = f1j + a[0, j]; theLine1[j] = 1; } else { theF1j = f2j + t[1, j - 1] + a[0, j]; theLine1[j] = 2; } if (f2j + a[1, j] <= f1j + t[0, j - 1] + a[1, j]) { theF2j = f2j + a[1, j]; theLine2[j] = 1; } else { theF2j = f1j + t[0, j - 1] + a[1, j]; theLine2[j] = 2; } f1j = theF1j; f2j = theF2j; } if (theF1j < theF2j) { return theLine1; } else { return theLine2; } } private string _MatrixChainString = ""; public void GetMatrixChainOrder(int[] p) { int n = p.Length-1; int Size = n + 1;//因为一般数组是0开始的,为计算方便,0行,0列不用. int[,] theM = new int[Size, Size]; int[,] theS = new int[Size, Size]; for (int l = 2; l <= n; l++) { for (int i = 1; i <= n - l + 1; i++) { int j = i + l - 1; theM[i, j] = int.MaxValue; int theQ = 0; for (int k = i; k < j; k++) { theQ = theM[i, k] + theM[k + 1, j] + p[i - 1] * p[k] * p[j]; if (theQ < theM[i, j]) { theM[i, j] = theQ; theS[i, j] = k; } } } } PrintResult(theS, 1, n); } public void PrintResult(int[,] s, int i, int j) { if (i == j) { _MatrixChainString += "A" + i; } else { _MatrixChainString += "("; PrintResult(s, i, s[i, j]); PrintResult(s, s[i, j] + 1, j); _MatrixChainString += ")"; } } }