动态规划与贪心的区别

动态规划和贪心算法的区别
动态规划和贪心算法都是一种递推算法  均用局部最优解来推导全局最优解 

不同点: 
贪心算法: 
1.贪心算法中,作出的每步贪心决策都无法改变,因为贪心策略是由上一步的最优解推导下一步的最优解,而上一部之前的最优解则不作保留。 
2.由(1)中的介绍,可以知道贪心法正确的条件是:每一步的最优解一定包含上一步的最优解。 

动态规划算法: 
1.全局最优解中一定包含某个局部最优解,但不一定包含前一个局部最优解,因此需要记录之前的所有最优解 
2.动态规划的关键是状态转移方程,即如何由以求出的局部最优解来推导全局最优解 
3.边界条件:即最简单的,可以直接得出的局部最优解
==============================================================================
贪心算法与动态规划 
贪心法的基本思路:   
    
从问题的某一个初始解出发逐步逼近给定的目标,以尽可能快的地求得更好的解。当达到某算法中的某一步不能再继续前进时,算法停止。   
该算法存在问题:   
1.   不能保证求得的最后解是最佳的;   
2.   不能用来求最大或最小解问题;   
3.   只能求满足某些约束条件的可行解的范围。实现该算法的过程:   
从问题的某一初始解出发;
   
while   能朝给定总目标前进一步   do   
求出可行解的一个解元素;   
由所有解元素组合成问题的一个可行解 

贪心算法最经典的例子,给钱问题。   
比如中国的货币,只看元,有1元2元5元10元20、50、100   
    
如果我要16元,可以拿16个1元,8个2元,但是怎么最少呢?   
如果用贪心算,就是我每一次拿那张可能拿的最大的。   
比如16,我第一次拿20拿不起,拿10元,OK,剩下6元,再拿个5元,剩下1元   
也就是3张   10、5、1。   
    
每次拿能拿的最大的,就是贪心。   
    
但是一定注意,贪心得到的并不是最优解,也就是说用贪心不一定是拿的最少的张数   
贪心只能得到一个比较好的解,而且贪心算法很好想得到。   
再注意,为什么我们的钱可以用贪心呢?因为我们国家的钱的大小设计,正好可以使得贪心算法算出来的是最优解(一般是个国家的钱币都应该这么设计)。如果设计成别的样子情况就不同了   
比如某国的钱币分为   1元3元4元   
如果要拿6元钱   怎么拿?贪心的话   先拿4   再拿两个1     一共3张钱   
实际最优呢?   两张3元就够了   



求最优解的问题,从根本上说是一种对解空间的遍历。最直接的暴力分析容易得到,最优解的解空间通常都是以指数阶增长,因此暴力穷举都是不可行的。
最优解问题大部分都可以拆分成一个个的子问题,把解空间的遍历视作对子问题树的遍历,则以某种形式对树整个的遍历一遍就可以求出最优解,如上面的分析,这是不可行的。
贪心和动态规划本质上是对子问题树的一种修剪。两种算法要求问题都具有的一个性质就是“子问题最优性”。即,组成最优解的每一个子问题的解,对于这个子问题本身肯定也是最优的。如果以自顶向下的方向看问题树(原问题作根),则,我们每次只需要向下遍历代表最优解的子树就可以保证会得到整体的最优解。形象一点说,可以简单的用一个值(最优值)代表整个子树,而不用去求出这个子树所可能代表的所有值。
动态规划方法代表了这一类问题的一般解法。我们自底向上(从叶子向根)构造子问题的解,对每一个子树的根,求出下面每一个叶子的值,并且以其中的最优值作为自身的值,其它的值舍弃。动态规划的代价就取决于可选择的数目(树的叉数)和子问题的的数目(树的节点数,或者是树的高度?)。
贪心算法是动态规划方法的一个特例。贪心特在,可以证明,每一个子树的根的值不取决于下面叶子的值,而只取决于当前问题的状况。换句话说,不需要知道一个节点所有子树的情况,就可以求出这个节点的值。通常这个值都是对于当前的问题情况下,显而易见的“最优”情况。因此用“贪心”来描述这个算法的本质。由于贪心算法的这个特性,它对解空间树的遍历不需要自底向上,而只需要自根开始,选择最优的路,一直走到底就可以了。这样,与动态规划相比,它的代价只取决于子问题的数目,而选择数目总为1。
------------------------------

找钱问题,可以很大程度上帮助我们理解动态规划法语贪心算法的区别

二、问题

      现只有面额为 11元、5元、1元的三种人民币。

      给定一个 数目为 money 的人民币,如何用这三种面额的人民币 找开它,且用的人民币张数最少

      如:给定 10元,我们可以有以下找法:

            2张  5元面额

            1张  5元面额  + 5 张  1元面额

            10张 1元面额

      我们 选择第一种找法。只用两张人民币。

三、分析

 利用动态规划法可以找到最优解。

        利用贪心算法可以找到最优解(问题满足贪心选择性质时。该找钱问题在 11、5、1三种面额的情况下不满足该性质)

              或者找到近似 最优解(在本题设定的三种面额的情况下 便是如此)

        如果现在要找开 15元钱,则

        1. 根据动态规划法的解题思想,得到最优解为       3张  5元面额的 ,                                   总共 3张

        2. 根据贪心算法的解题思想,得到的近似最优解为 1张 11元面额的  加上  4张 1元面额的,     总共 5张

        从这就可以大略的看到 两个的区别

四、代码实现找钱问题的 动态规划法与贪心算法 两种解法,形成对比

view plaincopy to clipboardprint?
01./**********************************************************  
02. *问  题:有最小面额为 11 5 1的三种人民币,用最少的张数找钱  
03. *描  述:动态规划与贪心算法 解决问题 比较  
04. *作  者:JarvisChu  
05. **********************************************************/  
06.#include<stdio.h>   
07.#define N 4      
08.#define VALUE1 11                  //面额为 11元的人民币 (可以修改)   
09.#define VALUE2 5                   //面额为  5元的人民币 (可以修改)   
10.#define VALUE3 1                   //面额为  1元的人民币 (不要修改,不然会有找不开的情况)   
11.#define MAX_MONEY 1000             //能找开的钱的上限   
12.  
13./***************************动态规划法********************************  
14. *方法:  
15. *     int Num[MAX_MONEY];                  //Num[i]保存要找开 i 元钱,需要的最小人民币张数  
16. *     int Num_Value[N][MAX_MONEY];         //Num_Value[i][j] 表示 要找 j元钱,需要面额 VALUEi 的人民币张数  
17. *  
18. *     Num[i] = i;          0<= i <=4  
19. *     Num[i] = min(Num[i-VALUE1]+1,Num[i-VALUE2]+1,Num[i-VALUE3]+1)  
20. */  
21.  
22.//-------------------------求最小值---------------------------------   
23.int min(int a,int b,int c){   
24.    return a<b ? (a<c ? a:c):(b<c ? b:c);   
25.}   
26.//-------------------------求最优值---------------------------------   
27.int DP_Money(int money,int Num[]){   
28.                                                         //获得要找开money元钱,需要的人民币总张数              
29.    int i;   
30.    for(i=0;i<=VALUE2;i++){                               //0~4 全用 1元    
31.        Num[i]=i;   
32.    }   
33.    for(i=VALUE2;i<=money;i++){                           //从5元开始 凑钱   
34.        if(i-VALUE1 >= 0){                                //如果比 11 元大,说明多了一种用11元面额人民币的可能   
35.                                                         //从用 11元、5元、1元中 选择一个张数小的   
36.            Num[i] = min(Num[i-VALUE1]+1,Num[i-VALUE2]+1,Num[i-VALUE3]+1);   
37.        }               
38.        else{                                            //从5元、1元中 选择一个张数小的   
39.            Num[i] = (Num[i-VALUE2]+1) < (Num[i-VALUE3]+1) ? (Num[i-VALUE2]+1):(Num[i-VALUE3]+1);   
40.//          Num[i] = min(Num[i-VALUE2]+2,Num[i-VALUE2]+1,Num[i-VALUE3]+1);    
41.        }   
42.    }   
43.    return Num[money];   
44.}   
45.//-------------------------求最优解---------------------------------   
46.void BestChoice(int money,int Num[],int Num_Value[N][MAX_MONEY]){   
47.                                                           //要找 money 元钱,总人民币张数放在Num[money]中   
48.                                                           //Num[1~3][money]分别保存三种面额的张数   
49.    int i;                                                 
50.    for(i=0;i<VALUE2;i++){                            
51.        Num_Value[1][i] = 0;   
52.        Num_Value[2][i] = 0;   
53.        Num_Value[3][i] = i;   
54.    }   
55.    for(i=VALUE2;i<=money;i++){   
56.        if((i>=VALUE1) && (Num[i] == (Num[i-VALUE1]+1))){   //i 是由 i-11+11 构成  即i元是由 i-11元 加上一张面额11元的人民币构成   
57.            Num_Value[1][i] = Num_Value[1][i-VALUE1]+1;     //多一张 11元面额人民币   
58.            Num_Value[2][i] = Num_Value[2][i-VALUE1];       // 5元面额人民币 张数一样多   
59.            Num_Value[3][i] = Num_Value[3][i-VALUE1];       // 1元面额人民币 张数一样多   
60.        }   
61.        else if(Num[i] == (Num[i-VALUE2]+1)){               //i 是由 i-5+5 构成            
62.            Num_Value[1][i] = Num_Value[1][i-VALUE2];       //11元面额人民币 张数一样多   
63.            Num_Value[2][i] = Num_Value[2][i-VALUE2]+1;     //多一张 5元面额人民币   
64.            Num_Value[3][i] = Num_Value[3][i-VALUE2];       // 1元面额人民币 张数一样多   
65.        }   
66.        else if(Num[i] == (Num[i-VALUE3]+1)){               //i 是由 i-1+1 构成      
67.            Num_Value[1][i] = Num_Value[1][i-VALUE3];       //11元面额人民币 张数一样多   
68.            Num_Value[2][i] = Num_Value[2][i-VALUE3];       // 5元面额人民币 张数一样多   
69.            Num_Value[3][i] = Num_Value[3][i-VALUE3]+1;     //多一张 1元面额人民币   
70.        }   
71.        else{   
72.        }   
73.    }   
74.}   
75.  
76./***************************贪心算法********************************  
77. *方法:  
78. *     Num_Value[i]表示 面额为VALUEi 的人民币用的张数  
79. *     能用大面额的人民币,就尽量用大面额  
80. */  
81.int Greed(int money,int Num_Value[]){   
82.                                                            //要找开 money元人民币,Num_Value[1~3]保存 三种面额人民币的张数   
83.    int total=0;                                            //总张数,返回值也即是总张数。   
84.    Num_Value[1] = 0;   
85.    Num_Value[2] = 0;   
86.    Num_Value[3] = 0;   
87.    for(int i=money;i>=1;){   
88.        if(i >= VALUE1){   
89.            Num_Value[1]++;   
90.            i -= VALUE1;   
91.            total++;   
92.        }   
93.        else if(i >= VALUE2){   
94.            Num_Value[2]++;   
95.            i -= VALUE2;   
96.            total++;   
97.        }   
98.        else if(i >= VALUE3){   
99.            Num_Value[3]++;   
100.            i -= VALUE3;   
101.            total++;   
102.        }   
103.        else{   
104.        }   
105.    }   
106.    return total;   
107.}   
108.void main(){   
109.    //测试 动态规划法   
110./*  int i;  
111.    int money = 23;  
112.    int Num[MAX_MONEY];                  //Num[i]保存要找开 i 元钱,需要的最小人民币张数  
113.    int Num_Value[N][MAX_MONEY];         //Num_Value[i][j] 表示 要找 j元钱,需要面额 VALUEi 的人民币张数  
114.    printf("%d\n",DP_Money(money,Num));  
115.    printf("-------------------------------------------\n");  
116.    BestChoice(money,Num,Num_Value);  
117.    printf("-------------------------------------------\n");  
118.    for(i=0;i<=money;i++){  
119.        printf("Num[%d]=%4d, %3d, %3d, %3d\n",i,Num[i],Num_Value[1][i],Num_Value[2][i],Num_Value[3][i]);  
120.    }  
121.*/  
122.  
123.    //测试 贪心算法   
124./*  int i;  
125.    int Num_Value_Greed[4];  
126.    for(i=0;i<=40;i++){                 //从0 元到 40 元的每一个找钱方式  
127.        Greed(i,Num_Value_Greed);  
128.        printf("%d---->>> %d,%d,%d\n",i,Num_Value_Greed[1],Num_Value_Greed[2],Num_Value_Greed[3]);  
129.    }  
130.*/  
131.       
132.    //比较两个算法   
133.    int i;   
134.    int dp,grd;                         //分别保存动态规划法和贪心算法得到的人民币总张数   
135.    int money;                          //要找的钱   
136.    int Num[MAX_MONEY];                 //Num[i]保存要找i花费的银币的数目   
137.    int Num_Value[N][MAX_MONEY];        //Num_Value[i][j] 表示 要找 j 花费的 面值为 VALUEi 的硬币 的数目   
138.    int Num_Value_Greed[N];             //Num_Value_Greed[i] 表示 面值为VALUEi 的人民币 数目    
139.    money = 15;                         //可以为任意非负整型值(15 元是一个比较典型的可以区分两种算法的值)   
140.    dp = DP_Money(money,Num);          //动态规划法   
141.    BestChoice(money,Num,Num_Value);   
142.    grd = Greed(money,Num_Value_Greed); //贪心算法   
143.    printf("要找的钱 为:%d\n\n",money);   
144.    printf("  算法    张数  11元  5元  1元\n");   
145.    printf("动态规划  %-4d  %-4d  %-3d  %-3d\n",dp,Num_Value[1][money],Num_Value[2][money],Num_Value[3][money]);   
146.    printf("贪心算法  %-4d  %-4d  %-3d  %-3d\n",grd,Num_Value_Greed[1],Num_Value_Greed[2],Num_Value_Greed[3]);   

147.} 


另参考资料:

1.分治法

     分治法(divide-and-conquer):将原问题划分成n个规模较小而结构与原问题相似的子问题;递归地解决这些子问题,然后再合并其结果,就得到原问题的解。


   分治模式在每一层递归上都有三个步骤:

  • 分解(Divide):将原问题分解成一系列子问题;
  • 解决(conquer):递归地解各个子问题。若子问题足够小,则直接求解;
  • 合并(Combine):将子问题的结果合并成原问题的解。

 

 

   合并排序(merge sort)是一个典型分治法的例子。其对应的直观的操作如下:

 

  • 分解:将n个元素分成各含n/2个元素的子序列;
  • 解决:用合并排序法对两个子序列递归地排序;
  • 合并:合并两个已排序的子序列以得到排序结果。

 

2. 动态规划法

 

   动态规划算法的设计可以分为如下4个步骤:

 

  • 描述最优解的结构
  • 递归定义最优解的值
  • 按自底向上的方式计算最优解的值
  • 由计算出的结果构造一个最优解

 

     分治法是指将问题划分成一些独立地子问题,递归地求解各子问题,然后合并子问题的解而得到原问题的解。与此不同,动态规划适用于子问题独立且重叠的情况,也就是各子问题包含公共的子子问题。在这种情况下,若用分治法则会做许多不必要的工作,即重复地求解公共的子问题。动态规划算法对每个子子问题只求解一次,将其结果保存在一张表中,从而避免每次遇到各个子问题时重新计算答案。

   适合采用动态规划方法的最优化问题中的两个要素:最优子结构和重叠子问题。 

   最优子结构:如果问题的一个最优解中包含了子问题的最优解,则该问题具有最优子结构。

       重叠子问题:适用于动态规划求解的最优化问题必须具有的第二个要素是子问题的空间要很小,也就是用来求解原问题的递归算法课反复地解同样的子问题,而不是总在产生新的子问题。对两个子问题来说,如果它们确实是相同的子问题,只是作为不同问题的子问题出现的话,则它们是重叠的。

    “分治法:各子问题独立   动态规划:各子问题重叠”

     算法导论: 动态规划要求其子问题既要独立又要重叠,这看上去似乎有些奇怪。虽然这两点要求听起来可能矛盾的,但它们描述了两种不同的概念,而不是同一个问题的两个方面。如果同一个问题的两个子问题不共享资源,则它们就是独立的。对两个子问题俩说,如果它们确实是相同的子问题,只是作为不同问题的子问题出现的话,是重叠的,则它们是重叠的。

 

3. 贪心算法

 

    对许多最优化问题来说,采用动态规划方法来决定最佳选择有点“杀鸡用牛刀”了,只要采用另一些更简单有效的算法就行了。贪心算法是使所做的选择看起来都是当前最佳的,期望通过所做的局部最优选择来产生出一个全局最优解。贪心算法对大多数优化问题来说能产生最优解,但也不一定总是这样的。

    贪心算法只需考虑一个选择(亦即,贪心的选择);在做贪心选择时,子问题之一必须是空的,因此只留下一个非空子问题。

    贪心算法与动态规划与很多相似之处。特别地,贪心算法适用的问题也是最优子结构。贪心算法与动态规划有一个显著的区别,就是贪心算法中,是以自顶向下的方式使用最优子结构的。贪心算法会先做选择,在当时看起来是最优的选择,然后再求解一个结果子问题,而不是先寻找子问题的最优解,然后再做选择。

        贪心算法是通过做一系列的选择来给出某一问题的最优解。对算法中的每一个决策点,做一个当时看起来是最佳的选择。这一点是贪心算法不同于动态规划之处。在动态规划中,每一步都要做出选择,但是这些选择依赖于子问题的解。因此,解动态规划问题一般是自底向上,从小子问题处理至大子问题。贪心算法所做的当前选择可能要依赖于已经做出的所有选择,但不依赖于有待于做出的选择或子问题的解。因此,贪心算法通常是自顶向下地做出贪心选择,不断地将给定的问题实例归约为更小的问题。贪心算法划分子问题的结果,通常是仅存在一个非空的子问题。

 


你可能感兴趣的:(dp,学习笔记,贪心)