动态规划 &

一、 动态规划(Dp问题):解决问题的关键点

        1) 递推公式:(最有子结构)

        2) 数据的初始化 

用例分析:

    a.  01 背包的问题(Knapsack Problem)

    定义:  一个背包的容量V; 存在N个物品:w[i]是背包的的容量,v[i]是背包的价值;求:背包能存放的最大价值?

    hint: 每个背包存在两个状态:0不入包,1入包

    分析: 定义 dp[i][j]:前i个物品 放入 容量为j的背包 时 的最大价值;

             dp[i] 与 dp[i-1]的关系  =>对于物品i存在两个状态:0->不放入背包;1->放入背包;

                * 0时: dp[i][j] = dp[i-1][j];

                * 1时: dp[i][j] = dp[i-1][j-w[i]] + v[i];

             所以递推公式:dp[i][j] = max(  dp[i-1][j],   dp[i-1][ j-w[i] ] + v[i]   );

    初始化:i = 0时: dp[0][0-V] = 0;

               j = 0时: dp[0-N][0] = 0;     取值范围: 0<= i <= N; 0<= j <= V;

    返回: return dp[N][V];

int  knap01(vector<int> w, vector<int> v, int V)
{
     if(w.size() < 0) return -1;
     
     int N = w.size(); //背包的数量
     //0<=i<=N; 0<=j<=V;
     vector< vector<int> >dp(N+1,vector<int>(V+1));

      //init   初始化
      for(int i=0; i<=N; i++)
               dp[i][0] = 0;
      for(int j=0; j<=V; j++)
               dp[0][j] = 0;
      
      //dp过程     
      for(int i=1; i<=N; i++)
      {
           for(int j=1; j<=V; j++)
           {
               if(j < w[i-1]){
                    dp[i][j] = dp[i-1][j];
                    cout << dp[i][j] << ' ';
                    continue;
                }
                int v0 = dp[i-1][j];                  //i是物品不入包的情况
                int v1 = dp[i-1][j-w[i-1]] + v[i-1];  //i物品入包的情况
                dp[i][j] = v0 > v1 ? v0 : v1;         //取max
                cout << dp[i][j] << ' ';
           }
           cout << endl;
       }
       return dp[N][V];
}


aa. 背包问题的升级: 即完全背包问题

     与a的不同: 物品i可以取无限个,而不是只能取一个;

     定义:一个背包总容量为V;  现在有N个物品,第i个 物品体积为weight[i],价值为value[i],每个物                品都有无限多件;  现在往背包里面装东西,怎么装能使背包的内物品价值最大?

和01背包问题唯一不同的是:01背包问题是在前一个子问题(i-1种物品)的基础上来解决当前问题(i种物                              品),向i-1种物品时的背包添加第i种物品;

                   而完全背包问题是在解决当前问题(i种物品),向i种物品时的背包添加第i种物品。 

    dp[i] 与 dp[i-1]的关系  =>对于物品i存在两个状态:0->不放入背包;1->放入背包;

     * 0时: dp[i][j] = dp[i-1][j];

     * 1时: dp[i][j] = dp[i][j-w[i]] + v[i];   //代码主要的区别

     所以递推公式:dp[i][j] = max(  dp[i-1][j],   dp[i][ j-w[i] ] + v[i]  );

int  knap01(vector<int> w, vector<int> v, int V)
{
     if(w.size() < 0) return -1;
     
     int N = w.size(); //背包的数量
     //0<=i<=N; 0<=j<=V;
     vector< vector<int> >dp(N+1,vector<int>(V+1));
      //init   初始化
      for(int i=0; i<=N; i++)
               dp[i][0] = 0;
      for(int j=0; j<=V; j++)
               dp[0][j] = 0;
      
      //dp过程     
      for(int i=1; i<=N; i++)
      {
           for(int j=1; j<=V; j++)
           {
               if(j < w[i-1]){
                    dp[i][j] = dp[i-1][j];
                    cout << dp[i][j] << ' ';
                    continue;
                }
                int v0 = dp[i-1][j];                  //i是物品不入包的情况
                int v1 = dp[i][j-w[i-1]] + v[i-1];    //i物品入包的情况; 完全背包问题
                dp[i][j] = v0 > v1 ? v0 : v1;         //取max
                cout << dp[i][j] << ' ';
           }
           cout << endl;
       }
       return dp[N][V];

    a.  Fibonacci序列的问题     DP  实现  http://my.oschina.net/u/573270/blog/479644

01背包问题的优化:

    a:  01背包问题优化

计算f[i][j]可以看出,在计算f[i][j]时只使用了f[i-1][0……j],没有使用其他子问题,因此在存储子问题的解时,只存储f[i-1]子问题的解即可。再进一步思考,计算f[i][j]时只使用了f[i-1][0……j],没有使用f[i-1][j+1]这样的话,我们先计算j的循环时,让j=M……1,只使用一个一维数组即可。

int knap02(vector<int>w, vector<int> v, int V)
{
      if(w.size() < 0) return -1;
      
      int N = w.size();
      vector<int> dp(V+1,0);
      
      //dp
      for(int i=1; i<=N;i++)
      {
           for(int j=V; j>=1; j--)   //通过逆序,f[i-1][..]
    	  {
              if(j < w[i-1]){
            	  dp[j] = dp[j-1];
            	  continue;
              }
              int v0 = dp[j-1];
              int v1 = dp[j - w[i-1]] + v[i-1];
              dp[j] = v0 > v1 ? v0 : v1;
              cout << dp[j] << ' ';
    	  }
      }
      return dp[V];
}

    aa: 完全背包问题的优化

int knap02(vector<int>w, vector<int> v, int V)
{
      if(w.size() < 0) return -1;
      int N = w.size();
      vector<int> dp(V+1,0);
      
      //dp
      for(int i=1; i<=N;i++)
      {
          for(int j=1; j<=V; j++)
          {
              if(j < w[i-1]){
                 dp[j] = dp[j-1];
                 continue;
              }
              int v0 = dp[j];        //完全背包
              int v1 = dp[j - w[i-1] ] + v[i-1];
              dp[j] = v0 > v1 ? v0:v1;
          }
      }
      return dp[V];
}

贪心算法求解背包问题:

01背包不能用贪心求解: 完全背包问题  可以用贪心求解:

      思路:

            计算每个背包的单位重量价值v/w;从大到小排序; 选取价值大的物品加入背包;如果超过背包称重V,则选区次重要的物品加入背包;

struct good{
   int w;
   int v;
   double p;
};
bool cmp(good &g1, good& g2)
{
     return g1.p < g2.p;
}
int greedy(vector<int>w, vector<int>v, int V)
{
   vector<good> goods;
   for(int i=0; i<w.size(); i++)
   {
        good g;
        g.w = w[i];
        g.v = v[i];
        g.p = g.v / g.w;
        goods.push_back(g);
   }
   
   //sort
       sort(goods.begin(), goods.end(),cmp);
   //greedy
       int weight = 0;
       int value = 0;
   for(int i=0; i<goods.size(); i++)
   {
       while(weight + goods[i].w <= V){
           weight += goods[i].w;
           value  += goods[i].v;
      }
   }
   return value;  //返回的值不一定是最优解
}

  =>背包问题可以应用到很多的实际环境中;

  1. 最长公共字序列  VS  最长公共子串

    最长公共字序列:

    dp:

         dp[i][j] 表示 str1的前i个字符   与  str2的前j个字符的公共字序列

          递推关系:

          str1[i] = str2[j]     则, dp[i][j] =  dp[i-1][j-1] + 1;  

          str1[i] != str2[j]      则,dp[i][j] 的值时dp[i-1][j]  与 dp[i][j-1]的最大值; dp[i][j] = max( dp[i-1][j],  dp[i][j-1] );          

//输出长度 =>输出公共子序列
int lcs(string str1, string str2)
{
    if(str1.size() < 0 || str2.size() < 0) return 0;
    
    int row = str1.size();
    int col = str2.size();
    //init
    vector< vector<int > > dp(row+1, vector<int>(col+1));
    for(int i=0; i<= row; i++)
          dp[i][0] = 0;
    for(int j=0; j<=col; j++)
          dp[0][j] = 0;
          
    //dp
    for(int i=1; i<=row; i++)//行
    {
          for(int j=1; j<=col; j++)
          {
               if(str1[i-1] == str2[j-1])
                    dp[i][j] = dp[i-1][j-1] + 1;
               else{//str1[i-1] != str2[j-1]
                    dp[i][j] = dp[i-1][j] > dp[i][j-1] ? dp[i-1][j] : dp[i][j-1];
               }
         }
    }
   
    //输出最长公共字序列:只是输出了其中的一个
    string substr="";
    int i = row;
    int j = col;
    for(;i> 0 && j > 0;)
    {
          if(str1[i-1] == str2[j-1]){
                substr+=str1[i-1];
                i--;
                j--;
          }
          else{
               if(dp[i-1][j] > dp[i][j-1]){
                   i--;  //
               }else{
                   j--;
               }
         }
    }
    cout << substr << endl;
    
    return dp[row][col];
}

  最长公共子串:

    递推公式

    str1[i] = str2[j]    则 dp[i][j] = dp[i-1][j-1] + 1;

    str1[i] <> str2[j] 则 dp[i][j] = 0;

int lcs2(string str1, string str2)
{
    if(str1.size() < 0 || str2.size() < 0) return 0;
    int row = str1.size();
    int col = str2.size();
    
    vector< vector<int> > dp(row+1, vector<int>(col+1));
    for(int i=0; i<=row; i++)
       dp[i][0] = 0;
    for(int j=0; j<=col; j++)
       dp[0][j] = 0;
    
    //dp
    int max = 0;
    for(int i=1; i<=row; i++)
    {
        for(int j=1; j<=col; j++)
        {
             if(str1[i-1] == str2[j-1])
                 dp[i][j] = dp[i-1][j-1] + 1;
             else{
                 dp[i][j] = 0;     //区别的地方
             }
             max = max > dp[i][j] ? max : dp[i][j];
         }
    }
    
    //根据对角线就能求出子串:
    return max;
}


你可能感兴趣的:(动态规划)