滚动数组

蓝桥杯国赛快来了,赶快找来往届赛题瞅瞅先,看到最后一题,发现需要用到动态规划,但是感觉数据比较大,单纯的用动态规划怕是要超内存,怎么办呢?这时发现了一个新名词,最起码对我而言的新名词——滚动数组,顿时懵了,啥叫滚动数组?怎么个滚动发儿?
于是开始搜索资料,找到一个不错的解释。当然,最好的解释一定是代码了>>>

ACM模版

滚动数组

一维

int main()
{
    int i;
    long long d[80];
    d[0] = 1;
    d[1] = 1;
    for(i = 2; i < 80; i++)
    {
        d[i] = d[i - 1] + d[i - 2];
    }
    printf("%lld\n",d[79]);
    return 0;
}
上面这个循环d[i]只依赖于前两个数据d[i - 1]和d[i - 2]; 为了节约空间用滚动数组的做法。
int main()
{
    int i;
    long long d[3];
    d[0] = 1;
    d[1] = 1;
    for(i = 2; i < 80; i++)
    {
        d[i % 3] = d[(i - 1) % 3] + d[(i - 2) % 3];
    }
    printf("%lld\n", d[79%3]);
    return 0;
}
上面的取余运算,我们成功地只保留了需要的最后3个解,数组好象在“滚动”一样,所以叫滚动数组(对于二维也可以用)。所以,很明显,滚动数组可以通过取余(%)来实现的,但是这里存在一个通病,那就是时间换内存一定会牺牲时间。因此,滚动数组一般用在时间比较充裕,而内存不够的情况下。

二维

对于二维数组,我们可以这样子改造:

int i, j, d[100][100];
for(i = 1; i < 100; i++)
    for(j = 0; j < 100; j++)
        d[i][j] = d[i - 1][j] + d[i][j - 1];

上面的d[i][j]只依赖于d[i - 1][j], d[i][j - 1];
运用滚动数组

int i, j, d[2][100];
for(i = 1; i < 100; i++)
    for(j = 0; j < 100; j++)
        d[i % 2][j] = d[(i - 1) % 2][j] + d[i % 2][j - 1];

附录

附上另外一种滚动数组的写法(其实和取余一个路子):
斐波那契数列……

int Fib[3];

int fib(int n)
{
    Fib[1] = 0;
    Fib[2] = 1;
    for(int i = 2; i <= n; ++i)
    {
        Fib[0] = Fib[1];
        Fib[1] = Fib[2];
        Fib[2] = Fib[0] + Fib[1];
    }
    return Fib[2];
}

int main()
{
    int ncase, n, ans;
    scanf("%d", &ncase);
    while(ncase--)
    {
        scanf("%d", &n);
        ans = fib(n);
        printf("%d\n", ans);
    }
    return 0;
}
### 滚动数组与压缩数组的概念及用法 #### 概念定义 滚动数组是一种优化技术,主要用于动态规划问题中降低空间复杂度。其核心思想是利用状态转移方程的特点,仅保留计算当前状态所需的部分历史状态,从而避免存储整个多维数组[^1]。 相比之下,压缩数组则是通过对原始数据进行某种形式的转换或编码,减少存储需求的技术。这种技术通常依赖于数据本身的特性(如连续性、重复性),并通过特定算法实现更高效的表示方式[^4]。 --- #### 实现方式对比 ##### **滚动数组** 滚动数组的核心在于通过调整遍历顺序和更新策略,使得一维数组可以替代传统的二维 dp 数组。以下是其实现的关键点: - 遍历顺序:当使用一维 `dp` 数组时,为了防止覆盖未使用的旧值,需采用倒序遍历的方式处理背包容量[^2]。 - 更新逻辑:每次迭代只涉及有限数量的状态变量,因此可以用较小的空间保存必要的中间结果。 下面是一个基于 C++ 的简单例子展示如何运用滚动数组求解斐波那契数列: ```cpp #include <iostream> using namespace std; int main() { int a[3]; a[0] = 1; a[1] = 1; for (int i = 1; i <= 35; ++i) { // 计算第36项Fibonacci数值 a[2] = a[0] + a[1]; // 新状态由前两个状态决定 a[0] = a[1]; // 移动指针保持最新三个状态即可 a[1] = a[2]; } cout << a[2] << endl; return 0; } ``` 此代码片段展示了如何借助少量固定大小的缓冲区完成原本可能需要线性增长内存的任务[^3]。 --- ##### **压缩数组** 压缩数组则更多关注于原始输入数据结构本身是否存在冗余或者模式可被挖掘出来加以简化表达。例如,在面对一系列接近均匀分布的数据集合时,我们可以记录初始基准值加上后续变化量构成的新序列作为代替方案;这样既节省了绝对位置信息又不失原意。 具体操作如下所示: 假设有一系列整型数字 `{8, 19, 26, 39, 41, 47, 62, 74}` ,如果直接储存每条独立记录的话总共占用约 \(4 \times 9 = 36\) 字节资源。然而注意到相邻成员之间差距不大这一事实之后便能设计更加紧凑的形式——即先指定起始参照物再列举其余各步增减幅度形成最终产物 `[85103], [8, 19, 26, 39, 41, 47, 62, 74]`. 这种方法尤其适合那些具有较强规律性的大数据集场合下应用实践当中去探索发现潜在价值所在之处。 --- #### 使用场景区别 | 特性/类别 | 滚动数组 | 压缩数组 | |-------------------|---------------------------------------------|--------------------------------------------| | 主要目标 | 减少 DP 中间过程中的临时存储开销 | 缩短长期存档文件长度 | | 数据特征要求 | 不改变原有业务语义下的局部最优子结构性质 | 存在明显趋势或周期特性的大规模静态资料库 | | 技术难度等级 | 较易理解并实施 | 对领域专业知识有一定门槛 | 综上所述可以看出两者虽然都致力于提升效率但是侧重点完全不同而且适用范围也互有侧重所以应该根据实际情况灵活选用合适的方法论来进行开发工作之中. ---
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值