动态规划(dp)的初体验

最近在看dp的题目,首先是白书(《算法竞赛入门经典》)里面找了些看,再者自己也百度着跟着看。点击打开链接这个是我看dp的网站之一,现在才看到初级一维的dp,然后对最长递增子序列感兴趣就各种百度起来了。然而这个的解法有三种至少,dp只是其中比较笨的一种。。汗。。所以就把三个都看了,感觉有点像拓宽经脉2333.

http://qiemengdao.iteye.com/blog/1660229这里概括得很棒,三种方法。

1、最长公共子序列,。我看那些文字的描述看得头晕=。=然后果然看代码好很多么。no talk just code?忘记是不是这么一句话了。反正我怀疑自己的中文阅读能力了哈哈哈。恩,这里的话主要的是c[i,j]=max(c[i,j-1],c[i-1,j]),其实就是比较两个序列的公共序列。一个序列是题目给的,一个是将前面的序列sort一下之后得到的,那么最长的公共子序列自然就是最长的递增子序列了。

2、动归(dp)(时间复杂度O(n^2))。主要就是双层循环,对于当前这个数循环它前面所有数,然后将比它小的数的x+1变成自己的x,x代表以它结尾可以获得的最长递增子序列的长度

  1.   for (int j=1; j<len; j++) {  
  2.         for (int i=0; i<j; i++) {  
  3.             if (arr[j]>arr[i] && longest[j]<longest[i]+1){ //注意longest[j]<longest[i]+1这个条件,不能省略。  
  4.                 longest[j] = longest[i] + 1; //计算以arr[j]结尾的序列的最长递增子序列长度  
  5.             }  
  6.         }  
  7.     }  =
(by上面给出的链接)
然后再for一遍判断下就好了。。。当然,在双层循环的时候直接定义一个ans来记录当前得到的最大的递增子序列长度也很棒,省了循环一遍。ans>?=longest[j]就行了。
3、O(nlgn)的算法。这个我花的时间比前两个长一些,当然也有可能是看太多了眼睛有点累?程序猿伤不起。
这个算法的话,主要在于针对当前的数操作,建立一个l[i], l[i]代表子序列的长度是i的时候最后那一个的最小值是多少,那么当前我get一个数x,要对它进行操作。首先二分来查找它在一众的l[i]中的位置,如果它比l[i]小却比l[i-1]大,那么将l[i]的值更新为x。如果它比所有值都大那么新建个l[i+1]=x。如果比所有值都小则修改l[1]=x。最后答案就是i的最大值,当然i这个变量名不好容易搞混。。。
  1.   
  2. int LIS(int *array, int n)  
  3. {  
  4.     len = 1;  
  5.     B[0] = array[0];  
  6.     int i, pos = 0;  
  7.   
  8.     for(i=1; i<n; ++i)  
  9.     {  
  10.         if(array[i] > B[len-1]) //如果大于B中最大的元素,则直接插入到B数组末尾  
  11.         {  
  12.             B[len] = array[i];  
  13.             ++len;  
  14.         }  
  15.         else  
  16.         {  
  17.             pos = BiSearch(B, len, array[i]); //二分查找需要插入的位置  
  18.             B[pos] = array[i];  
  19.         }  
  20.     }  
  21.   
  22.     return len;  
  23. }  
  24.   
(同上)


----------------------------------------------于是暂时搞定了最长递增子序列的问题,不要跑题,再去看dp好吗(╯‵□′)╯︵┻━┻------------------------------------------
然后上算法课的时候老师讲到了01背包问题,正好白书里面有,还是dp专题的,于是兴冲冲去看。(可是老师我们上的不是贪心吗?)
白书里面讲了三种实现方法。结果大概是白书的代码真心太简洁(?)我看着那段小代码默默泪了。如下:
for(int i=n;i>=1;i--)
<span style="white-space:pre">	</span>for(int j=0;j<=c;j++)
<span style="white-space:pre">	</span>{
<span style="white-space:pre">		</span>d[i][j]=(i==n?0:d[i+1][j]);
<span style="white-space:pre">		</span>if(j>v[i])
<span style="white-space:pre">			</span>d[i][j]>?=d[i+1][j-v[i]]+w[i];
}
其中d[i][j]代表当前在第i层或者说第i个数的时候,j代表当前的背包容量。可以理解为剩余可使用容量。
v[i]代表第i个物体占的容量,w[i]代表这个物体的重量或者说价值。c是题目给的背包容量。
这个我一直有点迷惑,直到看到了一张表格,忽然想通了(下图和上面代码倒过来了,前面是逆序这里是顺序,虽然只是n的取值问题罢了)。
01pack-1
这里其实有很多的j乍看之下是无意义的多余的,而我就是在这里没想清楚。j怎么可以从0到c随便取呢?
现在看表格,就会发现对于每一层,得到的数值是和上面一行的数值相关的。比如d[2][7]=9,就是因为7>4,所以先试j-v[i]即7-4=3,发现d[i-1][3]=4,那么试d[i-1][j-v[i]]+w[i],就是取当前这个物体的结果。在当前剩余空间不足以存放当前物体时不取当前物体,则d[i][j]=d[i-1][j]。若是足够了,就取这个物体,然后加上上一层中剩余空间为当前空间减去取来的物体的空间的d[][]值,比如取了之后空间不足以存放其它物体,就是+0.然后将得到的值和不取当前物体的值对比取其中大的值。这里j++虽然看上去很无理,实际上是 为了之后计算而做的准备活动,理解这点就好很多了。
</pre></div><div style="text-align: left;"></div><div style="text-align: left;">白书第二种是正序方式,形式和上面的很像,但是能够不储存每个物体的数据,直接边输入边建立d[][]。其实上面的表格到这里用才是正确的吧。。。</div><div style="text-align: left;"><pre name="code" class="cpp">for(int i=1;i<=n;i++)
{
	scanf("%d%d",&v,&w);
	for(int j=0;j<=c;j++)
	{
		f[i][j]=(i==1?0:f[i-1][j]);
		if(j>=v)
			f[i][j]>?=f[i-1][j-v]+w;
	} 
}
其实还是很好理解的。

第三种是“滚动数组”,即将f变成一维的。因为看上面一个种的过程我们不难发现,计算出当前的d[i][j]只需要用到d[i-1][]里面的数据,那么就可以把再前面的数据覆盖掉。而且随着j--,引用到的数定然是比j要前面的数,因为我们是逆序,所以前面要引用的数我们还没来得及更改,所以是一种巧妙的优化。代码如下:
memset(f,0,sizeof(f))
for(int i=1;i<=n;i++)
{
	scanf("%d%d",&v,&w);
	for(int j=c;j>=0;j--)
	{
		if(j>=v)
			f[i][j]>?=f[i-1][j-v]+w;
	} 
}

这里感叹下,白书里用的>?=比max要简练多了。学习~~~
以上是今天的dp学习的总结,渣渣眼都要花了,关爱程序员从我做起!希望以后能看得更顺利迅捷吧。嘛,一步一步来也很棒啊,加油↖(^ω^)↗

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值