关于 动态规划的 一些总结

最近一直在准备秋招,也一直在研读各种常见算法,对于动态规划,这几乎是各种笔试必考的内容。看了相关书籍和相关博客后,对动态规划有了一些感悟,特此记下,以备后续查看。

首先根据一道典型的动态规划的题目,给出各种常见的解法与代码实现,最后对动态规划的解题思路做一个简单的总结。

 

笔试题目:甲同学负责一次活动礼品采购,每一款礼品的受欢迎程度(热度值)各不相同,现给出总金额以及各礼品的单价和热度值,且每个礼品只采购一个,如何购买可以使得所有礼品的总热度值最高。

输入:

第一行是一个正整数,表示总金额(不大于1000);

第二行是一个长度为n的正整数数组,表示礼品单价(n不大于100);

第三行是一个长度为n的正整数数组,表示对应礼品热度值。

输出:一个正整数,表示可获得的最高总热度值。

样例输入:1000

                  200   600   100   180   300    450

                  6       10     3        4       5        8

 

即已知  capcity=1000

            礼品单价数组为:weight[6]={200,600,100,180,300,450}

            礼品价值数组为:value[6]={6,10,3,4,5,8}

 

对于动态规划而言,有两种不同的解题思路:1)自顶向下的备忘录法;2)自底向上的动态规划

(1)自顶向下的备忘录法

这种方法利用的递归的思想,并通过把已经计算出的某个子问题的最优解存在备忘录里,在计算下一个子问题的最优解之前,首先检查备忘录里是否已经有了该子问题的最优解,这样避免了重复的运算。这样的备忘录可以用散列表,二叉搜索树等表示,这里我们用数组来表示。

这里用二维数组表示备忘录,int y[6][1001]  ,存储的是每个子问题对应的最优解。

for (int i = 0; i < 6; i++)
{
    for (int j = 0; j < 1001; j++)
    y[i][j] = -1;
}

key1:把weight[5]当成顶,即y[i][j]表示剩余容量为j,剩余物品为n,n-1,...,i的背包问题的最优解的值。

即有初始状态的值为:

y(0,j)=\left\{\begin{matrix} 0 &0\leq j\leq weight[0] \\ value[0] &j\geq weight[0] \end{matrix}\right.

状态转移方程为:

y(i,j)=\left\{\begin{matrix} y(i-1,j) &0\leq j< weight[i]] \\ max\begin{Bmatrix} f(i-1,j),f(i-1,j-weight[i])+value[i] \end{Bmatrix}&j\geq weight[i] \end{matrix}\right.

由此可得代码实现为:

int top_to_button_1(int i,int capcity)
{
   if (y[i][capcity] > 0)
   {
      return y[i][capcity];
   }
   else
   {
      if (i == 0)
      {
	y[i][capcity] = (weight[i] > capcity ? 0 : value[i]);
	return y[i][capcity];
      }
      if (weight[i] > capcity)
      {
	  y[i][capcity] =top_to_button_2(i-1,capcity);
	  return y[i][capcity];
      }
      else
      {
	y[i][capcity] = max(top_to_button_2(i-1,capcity),top_to_button_2(i-1,capcity-
                             weight[i])+value[i]);
        return y[i][capcity];
       }
   }
}

key2: 把weight[0]当成顶,即y[i][j]表示剩余容量为j,剩余物品为i,i+1,...,n的背包问题的最优解的值。

即有初始状态的值为

y(5,j)=\left\{\begin{matrix} 0 &0\leq j\leq weight[5] \\ value[5] &j\geq weight[5] \end{matrix}\right.

状态转移方程为

y(i,j)=\left\{\begin{matrix} y(i+1,j) &0\leq j< weight[i]] \\ max\begin{Bmatrix} f(i+1,j),f(i+1,j-weight[i])+value[i] \end{Bmatrix}&j\geq weight[i] \end{matrix}\right.

由此可得代码实现为

int top_to_button_2(int i,int capcity)
{
      if (y[i][capcity] > 0)
	  return y[i][capcity];
      else
      {
	   if (i == numberindex)
           {
		y[i][capcity] = (weight[i] > capcity ? 0 : value[i]);
		return y[i][capcity];
	   }
	   if (weight[i] > capcity)
	   {
		y[i][capcity] =top_to_button_1(i + 1, capcity);
		return y[i][capcity];
	   }
	   else
	   {
		y[i][capcity] = max(top_to_button_1(i + 1, capcity),top_to_button_1(i + 
                                1, capcity - weight[i]) + value[i]);
		return y[i][capcity];
	   }
      }

}

(2)自底向上的动态规划

通过确定初始状态的值,并通过递推来求解其余子问题对应的最优解。

key1:把weight[0]当成底

即有初始状态的值为

y(0,j)=\left\{\begin{matrix} 0 &0\leq j\leq weight[0] \\ value[0] &j\geq weight[0] \end{matrix}\right.

状态转移方程为

y(i,j)=\left\{\begin{matrix} y(i-1,j) &0\leq j< weight[i]] \\ max\begin{Bmatrix} y(i-1,j),y(i-1,j-weight[i])+value[i] \end{Bmatrix}&j\geq weight[i] \end{matrix}\right.

由此可得代码实现为

int button_to_top_1(int capcity)
{
    for (int i = capcity; i >= 0; i--)
    {
	if (weight[0]<i)
	{
	    y[0][i] = value[0];
	}
	else
	    y[0][i] = 0;
     }


     for (int i=1;i<6;i++)
     { 
	for (int j = capcity; j >= 0; j--)
	{
	     if (weight[i] < j)
	     {
		y[i][j] = max(y[i - 1][j], y[i - 1][j - weight[i]] + value[i]);
	     }
	     else
		y[i][j] = y[i - 1][j];
	}
		
     }

     
   return y[5][1000];
}

   key2:  把weight[5]当成底

      即有初始状态的值为

             y(5,j)=\left\{\begin{matrix} 0 &0\leq j\leq weight[5] \\ value[5] &j\geq weight[5] \end{matrix}\right.

     状态转移方程为

             y(i,j)=\left\{\begin{matrix} y(i+1,j) &0\leq j< weight[i]] \\ max\begin{Bmatrix} y(i+1,j),y(i+1,j-weight[i])+value[i] \end{Bmatrix}&j\geq weight[i] \end{matrix}\right.

       由此可得代码实现为

int button_to_top_2(int capcity)
{
   for (int i = capcity; i >= 0; i--)
   {
	if (weight[5]<i)
	{
	   y[5][i] = value[5];

	}
	else
	   y[5][i] = 0;
   }

	for (int i = 4; i >= 0; i--)
	{
	    for (int j = capcity; j >= 0; j--)
	    {
		if (weight[i] < j)
		{
		   y[i][j] = max(y_1[i + 1][j], y[i + 1][j - weight[i]] + value[i]);
		}
		else
		   y[i][j] = y[i + 1][j];
	    }
	}
   
    return y[0][1000];

}

key3: 还可以在上面的基础上,进一步对空间进行缩减,即有一维数组表示备忘录  int y[1001]

for (int i = 0; i < 1001; i++)
{
    y[i] = 0;
}

        首先针对第一个物品,或者最后一个物品,初始化y[1001],然后依次根据每一个物品,更新y[1001]。

        注意:针对每一个物品,进行y[1001]更新时,应该从y[capcity]更新,这样能保证y[capcity-weight[i]]是上一个状态所产生的最优解,否则上一个状态产生的最优解会被覆盖。

           初始状态的值为

            y(j)=\left\{\begin{matrix} 0 &0\leq j\leq weight[0] \\ value[0] &j\geq weight[0] \end{matrix}\right.

           状态转移方程为

           y(j)=\left\{\begin{matrix} y(j) &0\leq j< weight[i]] \\ max\begin{Bmatrix} y(j),y(j-weight[i])+value[i] \end{Bmatrix}&j\geq weight[i] \end{matrix}\right.

          由此可得代码实现为

          

int button_to_top_3(int capcity)
{
     for (int i = capcity; i >= 0; i--)
     {
	if (weight[0] < i)
	{
	    y[i] = value[0];
	}
	else
	    y[i] = 0;
     }

     for (int i = 1; i < 6; i++)
     {
	for (int j = capcity; j >= 0; j--)      //注意!!
	{
	      if (weight[i] <j)
	      {
		  y[j] = max(y[j], y[j - weight[i]] + value[i]);
	       }
			
	}

      }


     return y[1000];
}

      上述就是典型的动态规划常见的解题思路和对应的代码实现。   

      通过这道题,对动态规划作了以下总结

      1. 将原问题分解为子问题

     (1)把原问题分解若干个子问题,子问题与原问题形式相同或类似,子问题都解决了,原问题即解决。

     (2)确定状态

        将和子问题相关的各个变量的一组取值称之为一个“状态”,一个“状态”对应于一个或多个子问题,某个“状态”下的“值”就是这个”状态“所对应的子问题的解。

      (3)确定一些初始状态的值

      (4)确定状态转移方程

         找出不同状态之间如何转移----即如何从一个或多个”值“已知的"状态”,求出另一个“状态”的“值”。状态的转移可以用递归公式表示。

        2.动态规划常用的两种形式

      (1)自顶向下的备忘录法

         即建立一个备忘录,将已经计算出的“值”放入备忘录中,当要计算下一个值时,先检查备忘录中是否已有该“状态”对应的“值”,如果已经存在,则直接从备忘录中取出该值,否则将计算后的“值”加入到备忘录中。

      (2)自底向上的动态规划

         即首先计算出初始状态的值,再根据递推计算出其余状态的值。

        

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值