经典算法心得

来源:互联网 发布:先锋网络电视免费版 编辑:程序博客网 时间:2024/06/12 21:47

1.      大整数的乘法

方法有两种:

(1)“列表法”

(2)分治法

 

2.      整数划分问题

假设在正整数n的所有不同的划分中,用q(n,m)表示最大加数不大于m的划分个数。

于是有以下基本项和递归项成立:

基本项:

           当n >=1, 则q(n,1) = 1;

递归项:

           当m = n, 则q(n,m) =q(n,m-1) + 1;

           当m < n, 则q(n,m) = q(n,m-1)+ q(n-m,m);

           当 m >n, 则q(n,m) =q(n,n);

注:问题的切入点从最大加数与划分数的关系开始考虑。

 

3.      矩阵连乘问题:找最优的矩阵计算的优先顺序
例如矩阵连乘A1A2A3A4A5A6, 找一个断点(比如A4),将矩阵连乘一分为二((A1A2A3A4)和(A5A6)),使得这个断点可以使计算量最小((A1A2A3A4)的计算量加上(A5A6)的计算量再加上两个子矩阵连乘结果的之积的计算量,就是矩阵连乘A1A2A3A4A5A6总的计算量)。
这个断点事先不知道,但一定存在。用A[i:j]记作矩阵连乘Ai…Aj,断点为k,如果断点k将矩阵连乘分为两部分:A[i:k] 和A[k+1:j];则这两个子矩阵连乘也必须要求是最小计算量,这就是最优子结构性质。子矩阵连乘与原问题相似,并且存在共同的子问题,所以,符合动态规划算法设计的要求。
用m[i][j]保存矩阵连乘A[i:j]的最小计算量,则有以下关系式:
当i = j, m[i][j] = 0;
当i < j, m[i][j] = MINi<=k<j{ m[i][k] + m[k+1][j]+ pi-1pkpj}
如果直接使用递归计算,则会耗费指数时间。注意到在递归计算过程中,不同的子问题个数只有O(n2)个,实际上对于1<=i<=j<=n不同的有序对(i,j)对应于不同的子问题。在递归计算过程中很多子问题重复计算多次。于是,用于一个二维表存储所有子问题的解,当求解m[i][j]的值时,发现i<j,所以,二维表只需使用上三角形部分的空间;如果令k = j – i; 则m[i][j]的值依赖于{m[p][q] | q-p < k, p < q, p>=i, q<= j}. 所以,在求解m[i][j]之前,必须已求出m[p][q].于是,求解子问题的顺序是从k=0,1,2,…,n-1的依次递增的顺序求解m[i][j]. 当最后一步k = n-1时,也即是求解m[1][n]的值,这个值对应原问题的解。

4.      最长公共子序列
假设有两个序列X={x1,x2,x3,…,xm-1,xm},Y= {y1,y2,y3,…,yn-1,yn},它们两者的最长公共子序列假定为Z={z1,z2,z3,…,zk-1,zk}。于是有以下结论:
(1)当xm = yn, 则zk = xm= yn, Zk-1 是序列Xm-1和Yn-1的最长公共子序列。
(2)当xm≠yn, zk≠xm, 则Zk是序列Xm-1和Yn的最长公共子序列。
(3)当xm≠yn, zk≠yn, 则Zk是序列Xm和Yn-1的最长公共子序列。
由于最长公共子序列Z事先未知,当xm≠yn,Zk是序列Xm-1和Yn还是序列Xm和Yn-1的最长公共子序列也是未知的。于是分别求出序列Xm-1和Yn与Xm和Yn-1的最长公共子序列,选择两者较长者即为X和Y的最长公共子序列。当xm = yn,先求出序列Xm-1和Yn-1的最长公共子序列,然后在末尾加上xm (=yn)。
重叠子问题:在分别计算序列Xm-1和Yn与Xm和Yn-1的最长公共子序列时,两者包含子问题Xm-1和Yn-1的最长公共子序列。
最优子结构性质:原两个序列的最长公共子序列包含其两个子序列的最长公共子序列。
用c[i][j]记录序列Xi={x1,x2,…,xi-1,xi}和Yj={y1,y2,…,yj-1,yj}的最长公共子序列的长度。则有以下递归关系:
(1)当i = 0,j = 0, 则c[i][j] = 0。
(2)当i,j > 0, xi = yj , 则c[i][j] = c[i-1][j-1] + 1。
(3)当i,j > 0, xi≠yi, 则c[i][j] = max{c[i-1][j], c[i][j-1]}。
如果直接递归,则计算量将是指数级别的。因此使用自底向上的动态规划算法,从最小的子问题开始求解,朝着较复杂的子问题进行求解,较复杂的子问题依赖于已经求出的较简单的子问题的解。从(3)式可以得到计算顺序(画一个二维表模拟一下):在二维表中先处理i=0的一列,j=0的一行,然后从上而下,从左至右,一行一行处理。

5.      最大子段和
解题思路:
(1)简单算法:依次求出i=0,1,2,…,n-1开始的子段的最大和,然后从n个计算结果中选择最大的作为原问题的解。
(2)分治算法:对序列分成长度近视相等的两段,最大子段要么在这两段中的任意一段,要么跨越了这两段(一定包含两段中的分界点)的一个子序列。而这两段的最大子段和与原问题相似,只是规模减小了一半,可借助递归求解。
(3)动态规划算法:动态规划算法的特点是从最小问题开始求解,较大问题的求解依赖于之前已经求出小问题的解。一句话概括,借助已求得小问题的解,循序渐进地求解较大问题,直到原问题出现并求解完毕。用动态规划算法求解最大子段和的思路:依次求出i=0,1,2,…,n-1结尾的子段的最大和(在求解i=x过程中,充分利用之前i<x时的计算结果),对n个结果选择最大的那个,就是原问题的解。

6.      0-1背包问题
最优子结构:第一个物品进行取舍之后,后面所有的物品看成是0-1背包问题的子问题,与原问题相似。原问题的最优包含子问题的最优。
重叠子问题:当一个物品进行取操作之后的剩余所有物品看成0-1背包问题的的子问题,与第一个物品进行舍操作之后的剩余所有物品看成0-1背包问题的的子问题,这两种情况产生的子问题在解决的过程中可能会有状态和规模一样的子问题出现。

7.      哈夫曼编码

8.      单源最短路径
Dijkstra算法是解单源最短路径问题的一个贪心算法。其基本思想是,设置顶点集合S并不断地做出贪心选择来扩充这个集合。一个顶点属于集合S当且仅当从源点到该顶点的最短路径长度已知。初始时,S中仅含有源。设u是图G的某个顶点,从源直接到u的路和从源到u且中间只经过S中顶点的路称为从源到u的特殊路径,并用数组dist记录当前每个顶点所对应的最短特殊路径长度。Dijkstra算法每次从V-S中取出具有最短特殊路径长度的顶点u,将u添加到S中,同时对数组dist做必要的修改。(贪心选择)一旦S包含了所有V中的顶点,dist就记录了从源到所有其他顶点之间的最短路径长度。
Dijkstra算法是以路径长度递增的次序产生相应的顶点到源点间的最短路径算法。

9.      最小生成树Prim算法
最小生成树的Prim算法的基本思想是,首先置S={1},然后,只要S是V的真子集,就做如下的贪心选择:选取满足条件i∈S,j∈V-S,且c[i][j]最小的边,并将顶点j添加到S中。这个过程一直进行到S=V时为止。在这个过程中选取的所有边(一共n-1条边)恰好构成G的一颗最小生成树。
做出上述贪心选择来源一个性质,这个性质称为最小生成树性质(minimum spanning tree,MST)。MST性质描述:设G=(V,E)是连通带权图,U是V的真子集。如果(u,v)∈E,且u∈U,v∈V-U,且在这样的边中,(u,v)的权c[u][v]最小,那么一定存在G的一颗最小生成树包含该边。

10.  最小生成树Kruskal算法
最小生成树Kruskal算法的基本思想是,首先将G的n个顶点看成n个孤立的连通分支,将所有的边按权从小到大排序。然后从第一条边开始,依边权递增的顺序查看每一天边,并按下述方法连接两个不同的连通分支:当查看第k条边(v,w)时,如果端点v和w分别是当前两个不同的连通分支T1和T2中的顶点时,就用边(v,w)将T1和T2连接成一个连通分支,然后继续查看第k+1条边;如果端点v和w在当前的同一个连通分支中,就直接查看第k+1条边。这个过程一直进行到只剩下一个连通分支为止。此时,这个连通分支就是G的一颗最小生成树。
最小生成树Kruskal算法的特点是每次选择的是边,结束条件是n个独立的连通分支连成一个连通分支,时间复杂度O(eloge);而prim算法的特点是每次选择的是顶点,结束条件是所有的顶点都被选择在一个集合中,时间复杂度O(n2)。


11.  单源最短路径问题的分子限界法解题思路(以下步骤解决从源点s到目标点t之间的最短路径):

(1)先对源顶点进行扩展,它的所有儿子结点都会被扩展,然后放入用最小优先队列存储活结点表中,活结点的优先级是所对应的当前路长(也称为该活结点的下界)。

(2)从最小优先队列中取出一个结点进行扩展,对该扩展点的所有儿子结点进行考察(也即对多个分支进行考察),如果儿子结点的当前路长是源结点到儿子结点目前所知的路长最短(实现方法:用一个数组保存所有顶点的目前离源点的最短路径长度)(基本约束条件),同时儿子结点的当前路长小于当前找到的最短路长(下界条件),则将该儿子结点添加到活结点表中,否则,舍去。

(3)重复步骤2,直到活结点表为空(之所以需要活结点表中的所有活结点要进行测试,因为第一次找到了源点到终点的路径长度未必是最短的路径,比如这种情况:是最后一步扩展操作未必能导致最优值的出现,例如,有两个节点,两节点的下界相同,这两个节点再一次被扩展,就到达了终点,先被扩展的未必是问题的最优值)。

(4)扩展结点的所有儿子结点,也即所有分支,判断是否将儿子结点添加到活结点表中有两个剪枝函数,一是约束函数,二是限界函数。

约束函数:儿子结点的当前路长是源结点到儿子结点目前所知的路长最短,这是必须满足的基本约束。

限界函数(下界约束):在满足约束函数的情况下,如果已经求得一条从源点到终点的最短路径,则儿子结点的当前路径长(儿子结点的当前路径长:解空间树中以该结点为根的子树中所有结点所对应的路长的一个下界)必须短于该最短路径才能将儿子结点添加到活结点表中。在算法扩展结点的过程中,一旦一个结点的下界不小于当前找到的最短路长,则算法剪去以该结点为根的子树。限界函数的目的就是判断当下的路径选择是否有机会产生最优值。限界函数常与当前找到的最优值进行比较。

12.  优先队列式分支限界法解决旅行售货员问题:

1.首先,考虑使用优先队列式分支限界法有三个问题:

(1)如何设计活结点和活结点表,用什么作为活结点的优先级标志?

(2)剪枝函数:约束函数和限界函数。这两个函数如何确定?

(3)问题的解是否会在活结点中保存,还是构造一个子集树或排序树存放问题的解?

根据与旅行售货员问题结合,回答上述问题:

答(1):用优先队列(最小堆)当做活结点表,活结点的优先级标志用预测子树费用下界lcost代表。

  (2)解决旅行售货员问题的约束函数:回路约束;限界函数:预测子树费用下界lcost与当前最优值比较。

  回路约束目的是判断当前路径是否满足最基本的回路要求;

预测子树费用下界lcost目的是与当前最优值对比来判断当前路径是否继续下去,也即判断当前扩展结点的某个分支是否有机会产生问题的最优值。

一条路径包含所有顶点且形成环,这个的实现方法:每个活结点中保存一个包含所有顶点的数组,然后在求解过程中,对最终的这个数组进行判断是否能构成回路。

预测子树费用下界的方法:求出每个顶点的最小出边费用,将已经确定的顶点顺序的那部分产生的费用加上未确定顶点顺序的剩余部分的最小出边费用和,就是该活结点预测子树费用下界值。

(3)由于每个活结点遇到的当前处境不同,针对当前处境对应的当前问题的解也不相同,于是可以选择将问题的解(不成熟)存放在每个活结点中。解决旅行售货员问题的最终解就是在某个活结点中保存。对于构造部分排序树以保存问题的解暂时不采用。

1 0
原创粉丝点击