关于滚动数组的一些菜鸟随笔
什么是滚动数组
简单来说,滚动数组就是一种具有短暂记忆力的数组,它会牺牲时间来节省空间,用size=3的数组来“存储”30000个数据。这么说有点离谱、抽象,毕竟a[3]怎么存储a[30000]里面的东西呢。这就是滚动数组的特性,即只记录少量的后续需要使用的数据,而将之前用过且不再需要调用的数据抛弃、覆盖,这样就将a[30000]中不要的数据所占的空间节省出来,以达到a[3]就能达成的任务目标。
滚动数组的核心:取余
在开始学习C语音的时候,接触到了一个新的数学运算符:取余%,和除号 / 类似的是都多用在特殊的循环或者是取一串数字的某一位,除法多取高位,取余多取低位。在滚动数组中,取余用于数组下标的动态改变,以达到[3]存[30000]的效果,例如:
int m=30000;//一个原先大的数据空间 int n=3;//所需要的一个滚动数组空间 void fun() { for (int i = 0; i < m; i++) { d[i % n] = d[(i - 1) % n] + d[(i - 2) % n]; } }
通过取余的特点可以看出,动态数组在取模和循环的作用下只用3个空间就可以做到存储30000个数据的作用。
使用情况(浅提动态规划)
那什么时候使用呢?
- 在不在意时间,只需要节省空间的情况。滚动数组的本质是通过for循环多次覆盖不用的数值,增加了时间,又使用取余动态改变数组下标,节省了空间。
- 多用于动态规划问题(Dynamic Programing,DP)。
在这不得不说到动态规划问题,但由于篇幅所限,在此仅浅谈一下,后续有缘更新。DP主要用于求解以时间划分阶段的动态过程的优化问题,但是一些与时间无关的静态规划(如线性规划、非线性规划),只要人为地引进时间因素,把它视为多阶段决策过程,也可以用动态规划方法方便地求解。
多阶段决策过程的特点是每个阶段都要进行决策,具有n个阶段的决策过程的策略是由n个相继进行的阶段决策构成的决策序列。由于前阶段的终止状态又是后一阶段的初始状态,因此确定阶段最优决策不能只从本阶段的效应出发,必须通盘考虑,整体规划。就是说,阶段k的最优决策不应只是本阶段的最优,而必须是本阶段及其所有后续阶段的总体最优。
而DP 的有最重要的两个理论--最优化原理和无后效性原则:
- 最优化理论,即最优子问题。其思想总结就是最优策略的任何一部分子策略也必须是最优的。举个例子,挑选一段回家的最短路程(最优策略),路上会经过的各种检查点(阶段),该路的任何一个地方到家的路径也是同阶段到家路径的最短路径(子问题最优)。详情可见:动态规划的基本概念和最优化原理_Vasari的博客-CSDN博客_动态规划的最优化原理
- 无后效原则。某阶段的状态旦确定,则此后过程的演变不再受此前各状态及决策的影响。也就是说,“未来与过去无关”(非常好理解)。更具体消息的可以参考:动态规划——无后效性及如何消除后效性_大司马学编程的博客-CSDN博客_动态规划 无后效性里面指出一个很有意思的点:通过二维数组区分、寄存指定状态,解决后效性问题。
例题说明
斐波那契数列
- 因为乘数指定,即只有一条路能走,故符合最优原理。
- 乘积固定,没有其它因素影响,所以符合无后效性原则。
因此可以使用滚动数组方法,代码:
1 void func2() 2 { 3 int d[3]; 4 d[0] = 1; 5 d[1] = 1; 6 for (int i = 0; i < 100; i++) 7 { 8 d[i % 3] = d[(i - 1) % 3] + d[(i - 2) % 3]; 9 } 10 printf("%d", d[99 % 3]); 11 }
01背包
- 整体最优是由一步步的子问题最优组成,即n个空间的包最优解是由1~n-1个空间背包的最优解组合而成,故符合最优原理。
- 每个数值固定,无论前面问题是怎样解,后面背包总空间不变,往后的任何决策都不会改变。故符合无后效性。
因此可以使用滚动数组方法,代码:
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int maxn = 1e3+10; 4 int t,n,v; 5 int dp[maxn]; 6 int value[maxn]; 7 int weight[maxn]; 8 int main() 9 { 10 scanf("%d",&t); 11 while(t--) 12 { 13 memset(dp,0,sizeof dp); 14 scanf("%d %d",&n,&v); 15 for(int i = 1;i <= n;i++) 16 scanf("%d",&value[i]); 17 for(int i = 1;i <= n;i++) 18 scanf("%d",&weight[i]); 19 // for(int i = 1;i <= n;i++) 原始二维dp方程 20 // for(int j = 0;j <= v;j++) 21 // { 22 // if(j >= weight[i]) //若取得下,则可以选择取或不取 23 // dp[i][j]=max(dp[i-1][j],dp[i-1][j-weight[i]]+value[i]); 24 // else 25 // dp[i][j]=dp[i-1][j]; 26 // } 27 for(int i = 1;i <= n;i++) //使用滚动数组优化后的dp方程 28 for(int j = v;j >= weight[i];j--) //倒序保证数据更新的有序性,保证只取一次,正序则是完全背包的写法 29 dp[j]=max(dp[j],dp[j - weight[i]] + value[i]); 30 printf("%d\n",dp[v]); 31 } 32 return 0; 33 }
<-------------------------------------------------------------------------------------------->
最后,滚动数组只是动态问题中的一小部分,后续还有更多有趣的知识,例如动态问题和搜索、分治法的联系和区别,随缘更新。By the way,更新这篇时正赶上国庆,摆了几天,当我正常学习的时候,电脑开摆了,这么点字,它疯狂蓝屏、黑屏,问题是没保存,导致写了好几次,有些小细节因为写太多烦了,就没打,现在反而忘了,华硕天选2,你恶事做尽!!!!
文章部分节选: