zoukankan      html  css  js  c++  java
  • 学习利用动态规划解决若干问题

    学习了一下动态规划算法,按正统的描述此类问题应该是具有“最优子结构”和“重叠子问题”的一大类问题,很多地方有非常规范的描述,这里不赘述。

    简单的说,我的理解就是:

    (1)大的思想是空间换时间

    (2)首先分析是否“有规律”,然后定义一个状态,即为一维或二维子结点的状态,这个状态,往往是问题的解,但是也可能是隐含了解的数据,比如,求最长子串时,这个状态就是当前最长子串长度

    (3)在定义状态的同时,也要分析转移方程,也叫递推公式,其实就是本结点和前面的那些已经处理过的结点之间,存在什么关系,往往是前面的结点中的最好的与当前结点存在非常特殊的关系。

    (4)最后是要分析如何回溯求解,这就要配合转移方程,一般是倒序回溯,从后往前找满足转移方程的前一个结点

      1 #include <stdlib.h>
      2 #include <string>
      3 #include <time.h>
      4 
      5 using namespace std;
      6 
      7 int min_of_three(int a, int b, int c)
      8 {
      9     if(a < b)
     10         if(a < c)
     11             return a;
     12         else
     13             return c;
     14     else if(b < c)
     15         return b;
     16     else
     17         return c;
     18 }
     19 
     20 int max_of_three(int a, int b, int c)
     21 {
     22     if(a > b)
     23         if(a > c)
     24             return a;
     25         else
     26             return c;
     27     else if(b > c)
     28         return b;
     29     else
     30         return c;
     31 }
     32 
     33 
     34 int main()
     35 {
     36     // ① 斐波纳契数列
     37     // 问题定义:f(0) = 0; f(1) = 1; f(n) = f(n-1) + f(n-2); (n > 1),输入n,求f(n)
     38     {
     39         // 状态定义:a[i] 是当前f(i)
     40         // 转移方程:a[i] = a[i-1] + a[i-2]
     41         const int n = 7;
     42         int* aux = new int[n+1];
     43         aux[0] = 0;
     44         aux[1] = 1;
     45         for (int i = 2; i <= n; i++)
     46             aux[i] = aux[i-1] + aux[i-2];        // 根据转移方程,求出每一步的状态
     47         printf(">> 斐波纳契数列问题
    ");
     48         printf("n = %d
    ", n);
     49         printf("result = %d
    ", aux[n]);
     50         delete[] aux;
     51     }
     52     // ② 取硬币问题
     53     // 问题定义:有币值为1、3、5的硬币,数量不限,输入n,求总币值为n时至少需要多少枚硬币
     54     {
     55         // 状态定义:a[i] 是当前币值下最小硬币数
     56         // 转移方程:a[i] = 1 + min(a[i-1]:if(i>1), a[i-3]:if(i>3), a[i-5]:if(i>5))
     57         const int n = 18;
     58         int* aux = new int[n+1];
     59         aux[0] = 0;
     60         aux[1] = 1;
     61         for (int i = 2; i <= n; i++)            // 根据转移方程,求出每一步的状态
     62         {
     63             if(i < 3)
     64                 aux[i] = aux[i-1]+1;
     65             else if(i < 5)
     66                 aux[i] = (aux[i-1]<aux[i-3]?aux[i-1]:aux[i-3])+1;
     67             else
     68                 aux[i] = min_of_three(aux[i-1], aux[i-3], aux[i-5])+1;
     69         }
     70         printf(">> 取硬币问题
    ");
     71         printf("n = %d
    ", n);
     72         printf("result = %d
    ", aux[n]);
     73 
     74         // O(n)时间找出选择的硬币
     75         printf("coins are : ");
     76         for (int i = n, j = n; i >= 0; i--)
     77         {
     78             if(aux[j] - aux[i] == 1)
     79             {
     80                 printf("%d ", j-i);
     81                 j = i;
     82             }
     83         }
     84         printf("
    ");
     85         delete[] aux;
     86     }
     87 
     88     // ③ 最长递增子序列问题
     89     // 问题定义:在一个字符串序列中,找出一个成递增状态的子序列,如314253,最长递增子序列为:123
     90     {
     91         // 状态定义:a[i] = 当前最长子序列长度
     92         // 转移方程:a[i] = 1+max(a[j]:(s[j]<s[i]),j<i) 
     93         char* str = "AETBDTCSEEDFJEIFUGHEISDGUYUEGHSJDHDFJSDJFJKESJKJFSJFJLASDKJIWMJIFNVHISYIOSDFPEAQDMRSDYICNSHYUIYUTIIPUSDFEIVASDFWETIAZXETYLALZLQPKDPWQOWI";
     94         const int n = strlen(str);
     95         int* aux = new int[n];
     96         memset(aux, 0, n*sizeof(int));
     97         for (int i = 0; i < n; i++)
     98         {
     99             for (int j = 0; j < i; j++)
    100                 if ((str[i] > str[j]) && (aux[i] < aux[j]))
    101                     aux[i] = aux[j];
    102             aux[i]++;
    103         }
    104         int len = 0;
    105         for (int i = 0; i < n; i++)
    106             if (aux[i] > len)
    107                 len = aux[i];
    108         printf(">> 最长递增子序列问题
    ");
    109         printf("str = %s
    ", str);
    110         printf("result = %d
    ", len);
    111 
    112         // 回溯数组,找出最长子序列
    113         printf("substring is : ");
    114         char* sub = new char[n];
    115         memset(sub, 0, n);
    116         for (int i = n-1, j = 0; i >= 0 && len; i--)
    117             if (aux[i] == len && len)        // 找到满足长度条件,而且递减的下一个字符,存入缓冲区
    118                 sub[--len] = str[i];
    119         printf("%s
    ", sub);
    120         delete[] aux;
    121         delete[] sub;
    122     }
    123     // ④ 最长公共子序列问题
    124     // 问题定义:两个字符串序列,找出他们公共的子序列中最长的那个,例如41253和42125,最长公共子序列为4125
    125     {
    126         // 状态定义:a[i][j] = str1[0:i]与str2[0:j]的最长公共子序列长度
    127         // 转移方程:a[i][j] = max(a[i-1][j], a[i][j-1], a[i-1][j-1] + 1:if(str1[i] == str2[j]))
    128         char* str1 = "1208347018256081230951823759028375830182837518END";
    129         char* str2 = "0827508375812398789738636472734865098127398572396239E0ERND7";
    130         const int m = strlen(str1);
    131         const int n = strlen(str2);
    132         int* aux = new int[m*n];
    133         memset(aux, 0, m*n*sizeof(int));
    134         for (int i = 0; i < m; i++)
    135             for (int j = 0; j < n; j++)
    136             {                                    // 利用转移方程,计算每一步的最长子序列长度
    137                 if(i == 0 || j == 0)
    138                     aux[i*n+j] = str1[i]==str2[j]?1:0;
    139                 else
    140                     aux[i*n+j] = max_of_three(aux[(i-1)*n+j-1] + (str1[i]==str2[j]?1:0), aux[(i-1)*n+j], aux[i*n+j-1]);
    141             }
    142         printf(">> 最长公共子序列问题
    ");
    143         printf("str1 = %s
    str2 = %s
    ", str1, str2);
    144         printf("result = %d
    ", aux[m*n-1]);
    145 
    146         // 回溯矩阵,找出最长公共子序列
    147         printf("substring is : ");
    148         int len = aux[m*n-1];
    149         char* sub = new char[m];
    150         memset(sub, 0, m);
    151         int i = m-1;
    152         int j = n-1;
    153         while (i&&j&&len)
    154         {
    155             while(i && (aux[(i-1)*n+j]==len))    // 找左上角,向上移动
    156                 i--;
    157             while(j && (aux[i*n+j-1]==len))        // 找左上角,向左移动
    158                 j--;                            
    159                                                 // 到此处时,找到了左上角,锁定一个字符,该字符一定满足str1[i] == str2[j]
    160             sub[--len] = str1[i];                // 因此可以不写条件:if(str1[i] == str2[j])
    161             
    162             i--;j--;                            // 向左上方移动一格
    163         }
    164         printf("%s
    ", sub);
    165         delete[] aux;
    166         delete[] sub;
    167     }
    168     // ⑤ 格子取数问题
    169     // 问题定义,有一个m列n行的格子,其中一部分格子中放有数字,只能从左上角开始,向右或下方向移动至右下角,问:如何移动才能使经过的格子中的数之和最大
    170     {
    171         // 状态定义:a[i][j] = 从左上角到a[i][j]的最优路径所取数字之和
    172         // 转移方程:a[i][j] = max(a[i-1][j], a[i][j-1]) + num[i][j]
    173         const int m = 8;
    174         const int n = 10;
    175         int* num = new int[m*n];
    176         int* aux = new int[m*n];
    177         srand(time(0));
    178         for (int i = 0; i < m*n; i++)
    179             num[i] = (rand()%5 == 0)?rand()%4+1:0;
    180         
    181         aux[0] = num[0];
    182         for (int i = 1; i < m; i++)
    183             aux[i*n] = aux[(i-1)*n] + num[i*n];
    184         for (int j = 1; j < n; j++)
    185             aux[j] = aux[j-1] + num[j];
    186         for (int i = 1; i < m; i++)
    187             for (int j = 1; j < n; j++)
    188                 aux[i*n+j] = (aux[(i-1)*n+j]>aux[i*n+j-1]?aux[(i-1)*n+j]:aux[i*n+j-1]) + num[i*n+j];
    189 
    190         printf(">> 格子取数问题
    ");
    191         printf("num = 
    ");
    192         for (int i = 0; i < m; i++)
    193         {
    194             for (int j = 0; j < m; j++)
    195                 printf(" %2d ", num[i*n+j]);
    196             printf("
    ");
    197         }
    198         printf("
    ");
    199 
    200         // 回溯数组,找出沿路所取的数字
    201         printf("the path is : ");
    202         int sum = aux[m*n-1];
    203         int* path = new int[m+n];
    204         memset(path, 0, (m+n)*sizeof(int));
    205         int i = m-1;
    206         int j = n-1;
    207         while (sum)
    208         {
    209             while(i && (aux[(i-1)*n+j]==sum))    // 找左上角,向上移动
    210                 i--;
    211             while(j && (aux[i*n+j-1]==sum))        // 找左上角,向左移动
    212                 j--;                            
    213             // 到此处时,找到了左上角,再比较哪个数最大
    214             path[i+j] = num[i*n+j];
    215             sum -= num[i*n+j];
    216             if(aux[(i-1)*n+j]==sum)                // 跳到前面一个最优路径上的格子里
    217                 i--;
    218             else
    219                 j--;
    220         }
    221         for (int i = 0; i < m+n; i++)
    222             if(path[i] != 0)
    223                 printf(" %d ", path[i]);
    224         printf("
    ");
    225         delete[] aux;
    226         delete[] path;
    227 
    228 
    229     }
    230 
    231     system("pause");
    232     return 0;
    233 }

     输出结果:

    >> 斐波纳契数列问题
    n = 7
    result = 13
    >> 取硬币问题
    n = 18
    result = 4
    coins are : 3 5 5 5
    >> 最长递增子序列问题
    str = AETBDTCSEEDFJEIFUGHEISDGUYUEGHSJDHDFJSDJFJKESJKJFSJFJLASDKJIWMJIFNVHISYIOSDFPEAQDMRSDYICNSHYUIYUTIIPUSDFEIVASDFWETIAZXETYLALZLQPKDPWQOWI
    result = 26
    substring is : ABCDEFGHIJKLMNOPQRSTUVWXYZ
    >> 最长公共子序列问题
    str1 = 1208347018256081230951823759028375830182837518END
    str2 = 0827508375812398789738636472734865098127398572396239E0ERND7
    result = 26
    substring is : 20837581239879837583823END
    >> 格子取数问题
    num =
      0   0   4   0   0   0   2   2
      4   2   0   0   0   0   0   0
      0   3   0   0   0   0   0   0
      0   0   0   0   0   0   0   0
      0   0   0   0   2   0   0   0
      0   0   0   0   0   0   0   0
      0   0   1   0   4   2   0   0
      0   0   0   1   0   1   0   3
    
    the path is :  4  2  3  2  4  2  1  3
    请按任意键继续. . .
  • 相关阅读:
    每天一个JavaScript实例-从一个div元素删除一个段落
    Ewebeditor最新漏洞及漏洞大全
    WebKit历史项管理的实现
    C# 字符串处理
    Handler具体解释系列(四)——利用Handler在主线程与子线程之间互发消息
    (linux shell)第一章--小试牛刀(上)
    关于如何使用three.js的小教程&lt;一&gt;
    Readprocessmemory使用方法
    CentOS 6.4 U盘启动盘制作、安装及遇到的问题解决
    poj 2774 Long Long Message,后缀数组,求最长公共子串 hdu1403
  • 原文地址:https://www.cnblogs.com/zanzan101/p/3338850.html
Copyright © 2011-2022 走看看