09 动态规划和贪心算法

最优解算法精要

 

    动态规划和贪心算法经常用于求一个问题的最优解。我们先看看怎么用回溯法(其实也就是穷举)求最优解。

 

图表 1

假设要求从18的最短路径。那么回溯法应该是下面这个样子的:

int min=MAX_Integer; //最小值为全局变量。

void backtrack(Status){

  if(Status是解状态&&这个解对应的路径的长度<min){

min=这个路径的长度;

return;

  }

  生成当前状态可能到达的新状态NewStatuses;

  for each status in NewStatuses

backtrack (status);

}

这个算法其实就是比较了每一条从18的路径:

1246812478125681257813468134781356813578

最短路径具有“最优子结构”的性质——比如1234是从14的最短路径,那么23424的最短路径,3434的最短路径。因为如果234不是24的最短路径,那么从24还有一条更短的路径2xyz….4,那么12xyz….4就比1234短,这与1234是最短路径矛盾。

有了这个性质我们可以用下面的算法求最短路径:

int ShortestPath(int from,int to){

   if(from==to) return 0;

   int min= MAX_Integer;

   //求出以from为起点的所有边的终点vertices;

   for v in vertices

       if(edge(from,v)+ShortestPath(v,to)<min)

         min= edge(from,v)+ShortestPath(v,to);

}

用文字来描述算法就是:要求节点from到节点to的最短路径,可以先求from可以到达的节点vto的最短路径,然后加上fromv的这条边。

上面的算法实际上也是比较了18的所有8条边,但它比回溯法要好。

我们来比较一下两个算法的执行过程。

回溯法其实就是穷举(当然在穷举的过程中应用了最终条件的必要条件来“剪枝掉”不可能到达终态的状态,如果您不记得了,可以回前面看看回溯法)。

比如我们要求28的最短路径,那么回溯法是先得到24682478256825784条路径然后分别求这4条路径的长度:6+2+3;6+8+4;4+7+3;4+3+4,共计8次加法。

而后一种算法呢:       2

                     /    /

                   4       5

                  /  /     /  /

                 6   7   6   7

48的最短路径:3+2=5,4+8=12;求58的最短路径:7+3=10,3+4=7;28的最短路径:6+5=11,4+7=11。少了两次加法。少了那两次呢?6+8+44+7+3。因为8+4>3+2所以6+8+4>6+3+2,所以6+8+4肯定不是最短路径,用最优子结构性质表述就是:478不是48的最短路径那么2478就不是28的最短路径。

当然,上面的例子中利用了最优子结构性质的算法还得用3次比较,比较512107中的较小者,1111。而回溯法也要3次比较才能从4个中选出最小的数。

我们再把用后一种算法求18的路径的递归调用图画出来:

                                1

                            /        /

2                3

                     /    /             /    /

                   4       5          4     5

                  /  /     /  /        /  /   /  /

                 6   7   6   7      6   7  6  7

我们发现在求解的过程中,求4858的最短路径时它们被算了2次。这就是所谓的“重叠子问题”。很自然的,我们想避免重复的计算。办法就是把计算过的结果保存下来,如果下次遇到就直接拿出来。这就是“备忘录”方法,其实就是用空间换时间的策略。如果子问题的“重叠程度”很高,而且子问题“很难”,那么这么做是值得的。什么叫子问题“很难”?就是求子问题很费时间。比如,求28的最短路径这个子问题就比求68的最短路径这个问题“难”。因此如果空间有限,而重叠程度差不多的情况下,应该优先保存树中层次较低的问题的答案。

除了“备忘录”方法,另一种方法就是“动态规划”。

“备忘录”方法是自顶向下的一种方法——当需要用到的时候才求。比如上面要求18的最短路径,需要用到28的最短路径,这时才求28的最短路径,而28的最短路径又要用到48的最短路径,……。而动态规划恰好相反:既然每一条路径都最终是要通过6或者7到达8的,那么我们可以先求678的最短路径,然后利用67458的最短路径……

这两种方法各有其优缺点。备忘录方法只是在需要的时候才计算,不会有多余的计算。

图表 2

比如上面的图中,如要求18的最短路径,则备忘录方法只求1478这条路径,而动态规划则要求出5,6,78的最短路径,利用它们求2,3,48的最短路径,最后利用2,3,48的最短路径求出18的最短路径。

而动态规划的优点是可以利用问题的特点节省空间。

比如最上面的图它的特点是:从起点(1)到终点(8),其余的顶点被分成k3)个集合,这些集合内的顶点是没有边连接的,它其实是一个k段图。

用备忘录求上面的问题时需要6个空间分别存储2,3,4,5,6,78的最短路径。而动态规划只要2个空间就够了。因为2,38的最短路径只依赖与4,58的最短路径,而4,58的最短路径只依赖于6,78的最短路径。因此,可以先用这两个空间求出6,78的最短路径,然后还利用这两个空间求4,58的最短路径,然后还利用这两个空间求2,38的最短路径,最后求18的最短路径。

一般来讲,如果一个问题的所有子问题都至少要求一次(比如图1的情况),用动态规划可能比较好;而如果一个问题的有些子问题不用求解的话,则备忘录的方法可能比较好。

它们共同的特点是:利用了问题的最优子结构性质和重叠子问题的特点。

下面举几个例子。

(1)  01背包问题。

   n个物品和一个背包。物品i的重量是Wi,i的价值是Vi,背包的容量是C。把哪些物品放到包里面,使得包里面物品的价值最大。

我们先看看问题是否具有最优子结构性质。设(y1,y2,…,yn)是一个最优解(y1=1表示把第一个物品放进包中,y1=0则不放进包)。则(y2,…,yn)是下面这个子问题的最优解。

子问题:把2,3,….,nn-1个物品放进容量为C-y1*W1的包中。

反证,如果(y2,…,yn)不是子问题的最优解,则存在(z2,…,zn)是最优解,这样(y1,z2,…zn)比(y1,y2,….,yn)得到的价值要大,这与(y1,y2,…,yn)是最优解矛盾。

根据最优子结构性质,我们可以求出一下递归关系:

m(i,j)表示把第i,i+1,…,n个物品放到容量为j的包中的最大价值。

       max{m(i+1,j),m(i+1,j-Wi)+Vi      j>=Wi

m(i,j)={

       m(i+1,j)                        j<Wi

用文字描述就是:如果第i件物品太大(Wi>j),那么就不能选第i件物品,应该从i+1,….,n这些物品中选择,包的容量还是j;否则就有放i和不放i两种选择:如果放i,则得到价值Vi,包的容量变成了j-Wi;如果不放i,则包的容量还是j

递归的出口是:m(n,j)=Vn   if j>=Wn

              m(n,j)= 0    if j<Wn

即当只有第n这一个物品时,如果能放得下,最大的价值就是Vn;放不下自然就是0了。

那么用动态规划该怎么实现呢?从递推的关系可以看出,m(i,j)只依赖于m(i+1,j)m(i+1,j-Wi),因此我们可以先求出m(n,0),…,m(n,C),利用它们求m(n-1,0),…,m(n-1,C),一直进行下去直到求出m(1,C)

但我们发现并不是每个子问题都是要至少求一次的。

比如一个具体的实例:n=5,C=10,W={2,2,6,5,4},V={6,3,5,4,6}

使用动态规划要求m(5,0)….m(5,10),….m(2,0)….m(2,10)4×1144个子问题。

而如果使用备忘录方法呢?

                                    (1,10)

                            /                     /

                      (2,10)                           (2,8)

                   /         /                      /       /

               (3,10)        (3,8)                (3,8)         (3,6)

             /      /      /      /             /      /      /     /

           (4,10)  (4,4)   (4,8)   (4,2)         (4,8)    (4,2)  (4,6)    (4,0)

           /   /    |    /   /     |           /   /     |    /    /     |

         (5,10)(5,5) (5,4) (5,8) (5,3) (5,2)        (5,8) (5,3) (5,2) (5,6)  (5,1) (5,0)

一共就20个。空间呢?动态规划只要11个,而备忘录要44个。那么备忘录方法能不能少用些空间呢?可以使用Hash表来保存20个子问题的解就可以了,但是Hash可能有冲突,而且Hash表的存取时间虽然也是常数,但比起表格(表格也可以看出是一个Hash表,就这个问题来说,它是不会有冲突的,但浪费了一些空间)来说,还是会慢一些。当空间实在有限(或者问题规模实在太大比如指数增长的问题),那么使用Hash表是可行的策略。

 

(2) 最优二叉搜索树

给定有序的关键码集合{key[1],key[2],…,key[n]},查找key[i]的概率为p[i],查找的范围在(key[i],key[i+1])的概率为q[i],其中key[0]定义为负无穷,key[n+1]定义为正无穷。

现在要我们以这些关键码构造一棵BST,使得查找关键码的比较次数的数学期望最小(叫做这棵BST的平均比较次数)。

设在某棵BSTkey[i]的深度为c[i], (key[i],key[i+1])的深度为d[i]。则比较次数的数学期望是:

我们来看最优BST是否具有最优子结构性质。【这就涉及到怎么把问题转变成规模较小的同一问题。这在回溯法里讨论过,比如前面的01背包问题。如果用回溯法,每个物品都有2种状态(选择或者不选择),对比一下前面用回溯法求n个数的全排列的方法。最初的状态是没有做出任何选择,然后是第一个物品有放和不放两种可能,因此得到两个新的状态,然后在作出了第一次选择之后的状态下做第二个选择,,这样保证了每一种可能的解状态都尝试过了一次。】

即假设T是一棵最优BST,我们想证明它的左右子树都是最优BST

要想证明这一点,我们先得弄清楚T和它的子树的平均比较次数的关系。

T(i,j)是包含节点key[i],…,key[j]和叶子(key[i-1],key[i]),(key[i],key[i+1]),…, (key[j],key[j+1])BST,它的平均比较次数是m(i,j)1<=i<=j<=n

设它的根是k(i<=k<=j),设左子树的平均比较次数是m(i,k-1),右子树是m(k+1,j)

                root(i,j)=k

               /        /

              L         R

平均比较次数是

对应根节点k,它的比较次数是概率是p[i],比较次数是1,相乘后是p[i]

对应左子树中的节点v,假设它的概率是x,比较次数是y,现在把左子树作为k的子树后访问v要多与根比较一次,变成了x(y+1)。同理,访问右子树的任意节点是也有多与根比较一次,所以m(i,j)-m(i,k-1)-m(k+1,j)T中所有节点的概率之和。

为了简便,定义W(i,j)=p[i]+…p[j]+q[i-1]+…+q[j]

m(i,j)=m(i,k-1)+m(k+1,j)+W(i,j)

有了树T和它的子树的关系后就容易证明最优BST的最优子结构性质了。

假设最优BSTT)的左子树不是最优BST,那么一点存在一棵BST它的平均比较次数比左子树小,那么把T的左子树换成比较次数小的BST后,得到新的BST的比较次数比T还小,这就得到矛盾。

假设T(i,j)表示是包含节点key[i],…,key[j]和叶子(key[i-1],key[i]),(key[i],key[i+1]),…, (key[j],key[j+1])的最优BST(注意前面求T和它的子树的关系时并没有假设是最优的BST,而是一般的BST),它的平均比较次数是m(i,j)

m(i,j)=min{m(i,k-1)+m(k+1,j)+W(i,j)}(i<=k<=j)

       =W(i,j)+min{m(i,k-1)+m(k+1,j)}(i<=k<=j)

有了上面的递推关系后就容易用动态规划解这个问题了。

 

(3)所有顶点间的最短路径(Floyd算法)

求带权图所有顶点间的最短路径。

先来看求两个指定顶点(uv)间的最短路径。

从前面的例子中我们已经知道,如果uv的最短路径是u,v1,v2…vn,vv1,v2,…,vn,v是从v1v的最短路径,这说明路径具有最优子结构性质。因此我们可以使用备忘录方法来求它们间的最短路径。不过值得注意的是要避免递归死循环。

比如现在是无向图的情况(或者说两个顶点间各有两条有向边)。用回溯法就得小心一点了(前面回溯法时没有讲,这里补充一点)。比如要求uv的一条路径(这其实就是用回溯法走迷宫的问题),则可以先求12v的一条路径。在生成新的状态是要注意,不能有一条边就生成一个状态,这样的话就会出问题。

比如不加限制的生成新状态,则u能到达1这种状态,1又能回到u这种状态,……,这就会死循环。因此在用回溯法解迷宫问题是应该把根到当前状态的所有状态记录下来,在生成当前状态的子状态时要除去根到当前状态路径上的所有状态。

如果直接用回溯法,如果边很多(比如是完全图的情况),则回溯法会搜索每条简单路径(顶点不重),差不到有n!条。

用备忘录或动态规划呢?我们先还是来看看回溯法解决的问过程,假设现在有1,2,3,44个顶点的完全图,要求14的最短路径。

                     1

                 /    |    /

                2    [3]    4

               /  /  /  /  

              (3)  4 2  4

              |     |

              4    4

上面是回溯法的全过程,现在假设我们想用备忘录方法,把已经解决过的子问题保存下来。比如在1一直向左的过程中,会出现3(用小括号括起来的那个3),1的第二个子节点3(中括号括起来的那个)。这两个3里都表示:求34的最短路径这个问题吗?都不是。先看[3],它其实表示的是从34的路径中不经过1的最短的一条。因为根据我们生成子状态的规则,[3]的子状态不会有1[3]的子状态的子状态也不会有1。为什么1要生成[3]呢?因为求14的最短路径时,如果经过了3,比如这条路径是1….3….4,那么34之间不会再有1了,否则这条路径就不是最短。

同理(3)表示的是从34的所有路径中不经过12的最短的一条。因此我们在使用备忘录方法时就遇到了麻烦,这两个问题不是同一个问题。因此我们存问题时应该存的是类似(<3,4>{1,2})这样的问题,表示34的不经过12的最短路径。这样的子问题有没有重叠的呢?如果没有重叠,存它还有什么劲!4个顶点好像没有,但顶点多一点,比如5个就有。现在是1,2,3,4,55个顶点,求15的最短路径。

如下图(树太大,我只画了一部分):

                      1

                   /  |  |  /

                  2  3  4  5

                 /    |

                3    2

                |     |

                4    4

比如最下层的两个4都表示求45的不经过123的最短路径。

即使是这样,存的问题也很多,而且比较两个问题是否相同也很费时,比如这样两个问题(<100,200>,{1,2,3,….,99}),(<100,200>,{1,2,3,…,98})。共有 ×2n

如果用思路的动态规划方法呢?比如求5个顶点的情况。先求(<1,5>,{2,3,4})(<2,5>,{1,3,4}),(<3,5>,{1,2,4}),(<4,5>,{1,2,3})(其实它们就是边(1,5),(2,5),(3,5),(4,5)的长度)…..(<1,4>,{2,3,5})…….。然后利用它们求(<1,5>,{2,3}),……。这其实同时求出了其它节点间的最短路径。   

比如(<1,5>,{2,3})=min{(<1,5>,{2,3,4}),(<1,4>,{1,2,3})+(<4,5>,{1,2,3})}

太复杂了。是太多了,比如15就有(<1,5>,{}),(<1,5>,{2,3,4}), (<1,5>,{3,4})….我只写大括号里的了…{2,3}..{2,4}..{2}{3}{4}…

有没有什么好办法解决这个问题呢?我们来看看Floyd算法是怎么解决这个问题的。它把所有这些问题分成许多类(就像前面的K段图一样),这些类内部的点是没有边的(边代表依赖关系),而且某一类只依赖前面的某一类。说起来很抽象,我们来具体看一下。它把这样的问题放在一类中:按中间节点的最大编号分类。比如下面这个问题(<1,5>,{2,3})(<1,5>,{1,3})都是放在中间节点最大编号是3的这一类中。

现在我们看看这种分类方法是否满足上面说的两个条件。

(1)    同一类的问题之间没有依赖关系。

(2)    当前这一类的问题的解决只一类于前一类的问题的答案。

现在我们用(<u,v>,max)来表示uv的中间节点的编号最大为max的所有路径中的最短路径的长度。

如果我们直接用最优子结构性质,会得到下面公式:

(<u,v>,max)=min{(<u,w>,max)+(<w,v>,max)} (k是不同于uv的其它所有顶点)

但我们发现问题并没有变小,我们想把最大编号那个参数变小。这就要分两种情况:uv的路径上有编号为max的顶点;没有。

(<u,v>,max)=min{(<u,v>,max-1) ,  (<u,max>,max-1)+(<max,v>,max-1)}

从这个公式可以看出它是满足上面的两个条件的。

 

 

可见,使用动态规划的难点是怎么把许多的子问题分类,是它们满足那两个条件。

 

 

 

 

 

 

 

接着来说说贪心算法。

        前面的算法不论是分治法,回溯法还是动态规划,都是把原来的问题分解成规模较小的问题,然后根据这些小问题,求出原来大问题的解。也就是说,大问题的解决依赖于小问题的解决。这当然很自然,如果大问题的解“完全的不依赖于”小问题,那么我们干吗还要费劲求小问题呢!而贪心算法中,大问题的解只是“部分的依赖于”小问题的解,或者说解的一部分不依赖与小问题而可以直接计算出。

在实际问题中经常表现为:存在一个最优解包含这一部分解。这就是所谓的贪心选择性质。

我们来看一个例子。

(1)最小生成树(MST)

   求简单无向带权连通图的权和最小的生成树。

要找一个图的生成树就是找n-1条边,不产生回路(树的定义――n-1条无回路的边就是树)。

现在要是这n-1条边的和最小,那么我们很自然的想到先把最小的边选进来。但这样做行不行呢?也就是说是否存在一棵MST,它包含这条最小的边。(我们仅仅要求存在一棵MST包含最小的边就可以了,而不是要求所有的MST都包含最小的边。如果最小的边只有一条,那么所有MST都会包含这条最小边,但我们只要有MST包含就能使用贪心算法了)。

证明,反设任何MST都不包含最小边(u,v)。将(u,v)加入其中一棵MST中,会产生一个圈,圈至少还有一条边,设为(x,y),则c(u,v)<=c(x,y),删去(x,y)这条边后的圈仍然是一个生成树,且新树的边的权和不大于原来的MST,因为原来是MST,所以新树的权和也不能小于MST,所有只好相等。这样新树就是MST,矛盾。

既然至少存在一棵MST包含这条最小的边,那么我们就得到了部分解――一条边。

    然后我们要找其它的n-2条边,因为树不能有圈,所以我们选择的边不能和已经现在的边构成圈,两条边是不会形成回路的(如果两个顶点间有重边,那么我们只取最小的就可以了,其余的边可以删去,如果顶点上有环,则可以假定没有)。我们现在可以选择的边就是去掉第一次选择的边后剩下的所有边。与前面一样,我们可能会选择一条最小的边,但是否会有一棵MST同时包含这两条边呢?

        证明:因为至少存在一棵MST包含最小的边,如果这些包含最小边的MST中有一棵包含第二小的边,则说明存在一棵MST同时包含最小和次小的边。否则,所有包含最小边的MST都不包含次小的边。我们来推一下矛盾。

        假设其中一棵包含最小边的MSTT,那么与上面类似,我们把次小边加入后得到一个圈,由于圈至少有3条边,那么一定有一条边的权大于等于次小边,再删去这条边仍然是一棵生成树,且它的权和<=T,TMST,所以只能相等,这样新树也是MST且包含最小和次小边,矛盾。

        从上面可以看出,我们每次从那些不会与已经选择的边形成回路的边中选择最小的边就会有一棵MST包含已经选择的边和这条最小的边。这样一直进行下去,就可以得到一棵完整的MST

        前面我们证明了加入第一和第二两条边的情况,一般的是否成立呢?可以用归纳法证明。

        假设存在一棵MST包括边e1,e2,…,ekk<n-1,它们没有回路),那么把除了这些边之外的加入后不会形成回路的边中权值最小的边e加入后,仍然存在一棵MST同时包含e1,e2,…,ek,e

        反设任何一棵包含e1,e2,…,ekMST都不包含e=(u,v)。把e加入后会形成一个圈,如果圈中有一条边不是e1,e2,….,ek中的边,那么去掉它,得到的新树是包含eMST,矛盾。所以圈中的边只能是e1,e2,….,ek中的边。即包含e的圈的边只能是由e1,e2,…,ek中的边和e构成。这些圈外至少还有一条边(x,y)(否则k=n-1)。显然xy中至少有一个点不是{u,v}中的点,否则边(x,y)=(u,v)了。设x不属于{u,v},如果y也不属于{u,v}。因为MST是树,它得连通,所以存在一条从ux的路径,同样的存在一条从vy的路径再加上e=(u,v) 就构成了一个包含(x,y)的圈,这样去掉(x,y)的新树是包含eMST,矛盾。

        上面的算法思路就是Kruskal算法(避圈法),不过要实现它还得判断哪些边加入后不会形成圈。这可以用Union/Find算法实现。

        

         另一种思路是Prim算法。它的想法是:先找到最小的边e1=(u,v),肯定有MST包含它。这样就得到了一条边的树,而且他是包含在一棵MST中的。现在我们向求包含两条边的树,而且它也是包含在某一棵MST中。由于要求它是树,所以找边的时候就只能在与uv关联的边中找了(这里的或只能是异或了)。同样,我们也选择与uv关联的边中权值最小的边e2,希望存在一棵MST同时包含e1e2

         问题:e1=(u,v)是最小的边,而e2=(x,y)是所有与uv关联的边中权值最小的边,则存在一棵MST同时包含e1e2

         证:假设所有包含e1MST都不包含e2。由于对称性,不妨设yv。设T是包含e1但不包含e2MST,因为T是连通的,所以xv的任意一条路径,这条路径上都至少有一条边它的一个顶点属于{u,v}另一个不属于{u,v}。把这条边删去然后加上e2就得到一棵BST包含e2

         一般的问题是:设T是一棵树(当然还有顶点没有加进去),它包含在一棵MST中,e是满足下面条件的边中最小的边。条件:边的一个顶点在T中,另一个不在。

        证明与两个节点时类似。

       

        现在看看贪心算法和动态规划有什么关系。

        上面的方法,隐含的利用“最优子结构”性质。

        我们来看看怎么利用动态规划解决这个问题。

        首先,我们该怎么描述这个问题。

        在边集E中求k条边,这k条边再加上已经选择的边集后不会形成圈,且这k条边的权和是最小的。

        假设(E,k,SE)表示从E中选择k条边且与已经选择的边集SE不会形成圈的最小权和。它具有最优子结构性质:假设{e1,e2,…,ek}是最优解,那么{e2,…,ek}(E-{e1},k-1,SE+{e1})的最优解。反证,如果{e2,…ek}不是(E-{e1},k-1,SE+{e1})的最优解,而{edge2,…edgek}是最优解,则说明{edge2,…,edgek}中边的权和小于{e2,…ek}的权和,并且{edge2,…,edgek}S+SE+{e1}不会形成回路,则{e1,edge2,…edgek}+SE没有回路且它的权和比{e1,e2,…,ek}小,这是不可能的。

        有了最优子结构性质,我们可以得到如下递推公式:

        (E,k,SE)=min{(E-{e},k-1,SE+{e})+|e|}(其中e属于E并且SE+{e}没有回路)

        递归出口:(E,0,SE)=0

        从上面我们又知道这个问题具有贪心选择性质:假设SE是已经选择的边集(即存在一个最优解包含它),且e是所有满足{e}+SE不产生回路这个条件中权值最小的边,那么一定存在一个最优解包含{e}+SE

        贪心选择性质说明了:

        假设e1是属于E并且SE+{e}没有回路中权值最小的边。则有,

(E-{e1},k-1,SE+{e1})+|e1|<=(E-{e2},k-1,SE+{e2})+|e2|

(E-{e1},k-1,SE+{e1})+|e1|<=(E-{e3},k-1,SE+{e3})+|e3|

……

为什么?如果(E-{e1},k-1,SE+{e1})+|e1|>(E-{e2},k-1,SE+{e2})+|e2|,则说明没有最优解包含e1。反证,如果{e1,e2,…ek}是最优解。根据最优子结构性质,{e2,…,ek}(E-{e1},k-1,SE+{e1})的最优解,即|e2|+…+|ek|=( E-{e1},k-1,SE+{e1}),所以最优解的权和是|e1|+|e2|+…+|ek|=|e1|+(E-{e1},k-1,SE+{e1}) ,而(E-{e2},k-1,SE+{e2})+|e2|是原问题的一个解(不一定最优),但它竟然会比最优解还小,这是不可能的。

把这些不等式应用到递推公式中就得到新的公式:

(E,k,SE)=(E-{e},k-1,SE+{e})+|e|

(其中e是满足属于E并且SE+{e}没有回路这两个条件中的权值最小的边)

 

Prim的思路与此类似,只不过问题变成了:

(E,k,T),在E中找k条边,它这k条边加入T后得到的图仍然是树,满足上面条件的权和最小的k条边。

 

        我们来总结一下回溯法,分治法,动态规划和贪心算法在求最优解时的特点。

        回溯就是穷举,应该是最通用的算法,不能用回溯法的问题基本上不能用计算机来解决(背包问题不能回溯但能用贪心算法。因为这是一个连续的问题)。

        回溯是利用状态的变化,把初态到终态的问题转化成初态能够到达的新状态到终态的问题,如果状态之间有明显的“大小关系”(比如新状态比初态容易到达终态),这就变成了分治法了。

        如果问题具有最优子结构性质,那么就可以利用这个特点排除掉一些状态(排除的过程参考本章最前面的例子)。如果子问题大量重复出现,则可以考虑使用备忘录方法或动态规划算法。

        如果问题不但有最优子结构性质,而且还有贪心选择性质,那么在可以利用贪心选择性质进行“剪枝”(参考下图和两个递推公式)。

       

        上图如果没有贪心选择性质,则求18的最短路径时,先要求2838以及48的最短路径。而有了贪心选择性质(假设这个性质是:一定有一条最短路径包含2)3848的最短路径就不用求了(虽然348有可能也有最短路径)。

        一般有贪心选择性质和最优子结构性质我们就可以使用贪心算法,那么如果只有贪心选择性质而没有最有子结构性质呢?

        假设只有贪心选择性质,我们可能得到这样的结论:

        16有一条最短路径包含e1,我们假设是蓝色的路径e1,e3,e6

        26有一条最短路径包括e4,我们假设是e4,e7

        但我们并不能知道e1,e4,e7是不是最短路径(没有最优子结构性质)。

 
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值