关于滚动数组的一些初学随笔

什么是滚动数组

简单来说,滚动数组就是一种具有短暂记忆力的数组,它会牺牲时间来节省空间,用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个数据的作用。

使用情况(浅提动态规划)

那什么时候使用呢?

  1. 在不在意时间,只需要节省空间的情况。滚动数组的本质是通过for循环多次覆盖不用的数值,增加了时间,又使用取余动态改变数组下标,节省了空间。
  2. 多用于动态规划问题(Dynamic Programing,DP)。  

  在这不得不说到动态规划问题,但由于篇幅所限,在此仅浅谈一下,后续有缘更新。DP主要用于求解以时间划分阶段的动态过程的优化问题,但是一些与时间无关的静态规划(如线性规划、非线性规划),只要人为地引进时间因素,把它视为多阶段决策过程,也可以用动态规划方法方便地求解。

    多阶段决策过程的特点是每个阶段都要进行决策,具有n个阶段的决策过程的策略是由n个相继进行的阶段决策构成的决策序列。由于前阶段的终止状态又是后一阶段的初始状态,因此确定阶段最优决策不能只从本阶段的效应出发,必须通盘考虑,整体规划。就是说,阶段k的最优决策不应只是本阶段的最优,而必须是本阶段及其所有后续阶段的总体最优。

   而DP 的有最重要的两个理论--最优化原理和无后效性原则

  • 最优化理论,即最优子问题。其思想总结就是最优策略的任何一部分子策略也必须是最优的。举个例子,挑选一段回家的最短路程(最优策略),路上会经过的各种检查点(阶段),该路的任何一个地方到家的路径也是同阶段到家路径的最短路径(子问题最优)。
  • 无后效原则。某阶段的状态旦确定,则此后过程的演变不再受此前各状态及决策的影响。也就是说,“未来与过去无关”(非常好理解)。一个很有意思的点:通过二维数组区分、寄存指定状态,解决后效性问题。

例题说明

斐波那契数列

  1. 因为乘数指定,即只有一条路能走,故符合最优原理。
  2. 乘积固定,没有其它因素影响,所以符合无后效性原则。

因此可以使用滚动数组方法,代码:

void func2()
{
    int d[3];
    d[0] = 1;
    d[1] = 1;
    for (int i = 0; i < 100; i++)
    {
        d[i % 3] = d[(i - 1) % 3] + d[(i - 2) % 3];
    }
    printf("%d", d[99 % 3]);
}

01背包

  1. 整体最优是由一步步的子问题最优组成,即n个空间的包最优解是由1~n-1个空间背包的最优解组合而成,故符合最优原理。
  2. 每个数值固定,无论前面问题是怎样解,后面背包总空间不变,往后的任何决策都不会改变。故符合无后效性。

因此可以使用滚动数组方法,代码:

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e3+10;
int t,n,v;
int dp[maxn];
int value[maxn];
int weight[maxn];
int main()
{
    scanf("%d",&t);
    while(t--)
    {
        memset(dp,0,sizeof dp);
        scanf("%d %d",&n,&v);
        for(int i = 1;i <= n;i++)
            scanf("%d",&value[i]);
        for(int i = 1;i <= n;i++)
            scanf("%d",&weight[i]);
        // for(int i = 1;i <= n;i++)    原始二维dp方程
        //     for(int j = 0;j <= v;j++)
        //     {
        //         if(j >= weight[i])        //若取得下,则可以选择取或不取
        //             dp[i][j]=max(dp[i-1][j],dp[i-1][j-weight[i]]+value[i]);
        //         else
        //             dp[i][j]=dp[i-1][j];
        //     }
        for(int i = 1;i <= n;i++)    //使用滚动数组优化后的dp方程
            for(int j = v;j >= weight[i];j--)    //倒序保证数据更新的有序性,保证只取一次,正序则是完全背包的写法
                dp[j]=max(dp[j],dp[j - weight[i]] + value[i]);
        printf("%d\n",dp[v]);
    }
    return 0;
}

最后,滚动数组只是动态问题中的一小部分,后续还有更多有趣的知识,例如动态问题和搜索、分治法的联系和区别。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猰貐的新时代

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值