滚动数组

滚动数组

本节将要介绍另一个十分实用的小技巧——滚动数组。它常被用来完成常数优化和减少代码量。假设有如下状态转移方程:
在这里插入图片描述
按照该状态转移方程,可以用二维数组保存其状态值,通过如下代码片段完成其状态的转移(这里仅作说明,不考虑边界情况):

for (int i = 1;i <= n;i ++) {
    for (int j = 1;j <= m;j ++) {
        dp[i][j] = max(dp[i - 1][j + 1],dp[i - 1][j - 1]);
    }
}
int ans = dp[n][m]; 

考虑到每次状态的转移仅与上一行有关,可以将二维数组优化到使用一维数组保存。如下:

for (int i = 1;i <= n;i ++) {
    for (int j = 1;j <= m;j ++) {
        buf[j] = max(dp[j + 1],dp[j - 1]);
    }
    for (int j = 1;j <= m;j ++) { 
        dp[j] = buf[j];
    }
}
int ans = dp[m]; 

如该代码片段所示,将原本二维的状态空间优化到了一维,对应的需要在每次状态转移过后进行一次循环次数为 m 的赋值操作。该操作不仅增加了代码量,还增加了程序的耗时。于是使用滚动数组,对其再次进行优化:
定义大小为 2*m 的数组为其状态空间:int dp[2][M];
初始状态保存在 dp[0][i]中。
设定两个 int 类型指针

    int *src; //源指针
    int *des; //目的指针

由于初始状态保存在 dp 数组的第 0 行中,初始时

 src = dp[1];
 des = dp[0]; 

按照状态转移方程进行状态转移

for (int i = 1;i <= n;i ++) {
    swap(src,des); //交换源和目的指针
    for (int j = 1;j <= m;j ++) {
        des[j] = max(src[j + 1],src[j - 1]);
    }
}
int ans = des[m];

如代码所示,在每次循环进行状态转移之前交换源数组和目的数组的指针,使程序能够正确的从源数组中转移状态到目的数组中。当状态转移完成时,新得到状态保存于目的数组中,但它在下一次循环的状态转移中又将变为源数组,于是在下次状态转移开始前再次交换源数组和目的数组指针,这就是滚动数组的工作原理。

滚动数组这个技巧不仅优化了原始的状态空间,还减少了循环次数节约了程序运行时间,同时对代码量的缩减也有很好的效果,是一个值得学习的小技巧。

### 滚动数组与压缩数组的概念及用法 #### 概念定义 滚动数组是一种优化技术,主要用于动态规划问题中降低空间复杂度。其核心思想是利用状态转移方程的特点,仅保留计算当前状态所需的部分历史状态,从而避免存储整个多维数组[^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 中间过程中的临时存储开销 | 缩短长期存档文件长度 | | 数据特征要求 | 不改变原有业务语义下的局部最优子结构性质 | 存在明显趋势或周期特性的大规模静态资料库 | | 技术难度等级 | 较易理解并实施 | 对领域专业知识有一定门槛 | 综上所述可以看出两者虽然都致力于提升效率但是侧重点完全不同而且适用范围也互有侧重所以应该根据实际情况灵活选用合适的方法论来进行开发工作之中. ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值