常量时间更改数组的前 k 个元素(C++)
Constant time change first k elements of an array in C++?
假设我有一个数组:
bool eleme[1000000] = {false};
在我的代码中的某个时刻,我将这个数组的第一个n
元素中的一些更改为true
。 之后,我想确保数组的所有元素都false
.所以我这样做:
for (int i =0; i < n; ++i)
eleme[i] = false;
成本Θ(n)
.
有没有办法在恒定的时间内做到这一点? 例如,类似make_false(eleme, n);
一般答案
如果你想修改内存中的N个元素,那最终将是一个O(N)操作,不管你是否可以用memset
或std::fill
这样的单个命令来表达它。
如果设计算法时尽可能多地缓存数组,则操作将大大加快。使用优化的内置命令(如memset
)也有助于加快速度。
建议 1
但是,有一个用于恒定时间数组初始化的旧算法技巧,它也适用于您的情况(恒定时间数组重置)——但代价是大量额外的内存使用。
它如下:除了主数组A1
之外,您分配了相同长度的第二个数组A2
,以及一个大小为 N 的堆栈S
。这些结构都不需要初始化(仅仅分配它们可以说是一个 O(1) 操作)。您还需要一个堆栈指针SP
。
最初,堆栈指针为 0(指向堆栈的底部)。
每当你在A1
中输入,比如A1[i]=j
,你设置A2[i]=SP
,S[SP]=i
并递增SP
。
如果要检查是否已设置某个条目A1[i]
,请查找A2[i]
。如果A2[i]<SP
,即小于堆栈指针的当前值,则您知道相应的堆栈条目SP[A2[i]]
之前一定是您设置的。如果该堆栈条目的值为i
,则A1[i]
是有效的条目。否则,它永远不会初始化。
现在,为了重置A1
的所有条目,您只需将堆栈指针设置回 0。这是一个恒定时间操作。
我必须承认,我从来没有遇到过我发现这个技巧实际上有用的情况;通常memset
虽然不是恒定时间,但已经足够快了。
Gonzalo Navarro最近发表了一篇笔记,其中他描述了一组进一步的技巧来压缩额外的数组,以便它们在保留O(1)时间限制的同时使用更少的空间。
建议 2
另一种可能性是仅在必要时以惰性方式重置值。这利用了这样一个事实,即在重置时,实际上只会使用前几个元素中的一些,如您所描述的。
这涉及在变量中维护尚未初始化的最左侧元素的索引(或在最近重置期间重置),以及当要设置元素A[i]
时,初始化(或重置)最左侧未初始化的元素和i
之间的所有元素。
要访问索引i
处的元素,请检查i
是否小于最左边未初始化的元素,在这种情况下,您返回A[i]
;否则它尚未初始化(或重置),因此返回初始化值(可能是 0)作为文字。
要重置数组,您只需将最左侧未初始化元素的索引设置回 0,这是一个常量时间操作。
当然,这意味着更改条目现在是一个 O(N) 操作,但是如果您通常只设置数组的前几个元素,它永远不会变得非常昂贵。另请注意,两次重置之间的所有操作的总成本仍然是 O(N),因为每个元素重置的次数不超过一次。
另一个重要的优点是缓存友好性:每次设置元素时,需要初始化的元素范围可能很小,并且比一次重置所有元素时更有可能完全在缓存中。
在C++中,它可能看起来像这样:
template <typename T, std::size_t N, T init_val>
class FastResetArray
{
std::array<T,N> _data; // the array
unsigned _min_uninitialised; // the left-most non-initialised element
public:
FastResetArray()
:_data(),_min_uninitialised(0)
{}
T at(const unsigned index) {
return (index < _min_uninitialised ? _data[index] : init_val);
}
void set(const unsigned index, const T val) {
if (index > _min_uninitialised)
std::fill_n(begin(_data) + _min_uninitialised,
index - _min_uninitialised,
init_val);
_data[index] = val;
_min_uninitialised = index + 1;
}
void reset() {
_min_uninitialised = 0;
}
};
(请注意,在构造函数中,我将_min_uninitialised
(最左侧未初始化元素的索引)设置为 0。由于std::array
的默认构造函数无论如何都会将整个数组初始化为零,因此如果init_val
为零,我也可以设置为N
。所以上面的实现无助于避免初始 O(N) 初始化——我们只在reset()
中避免 O(N)。
没有办法在恒定的时间内做到这一点[并且不访问超过
n
元素]?
不。您必须设置n
元素,这将采取n
步骤,因此 O(n)。
您可以通过不手动编写循环来使其运行得更快。我想你会发现:
std::fill(eleme, eleme+n, false);
走得比
for (int i =0; i < n; ++i)
eleme[i] = false;
即使它们具有相同的大O复杂性。
根据
http://en.wikipedia.org/wiki/Time_complexity#Constant_time
你的代码已经是恒定的时间
如果元素的数量是事先知道的并且没有改变
从宣言中似乎是正确的
布尔饿了连[1000000] = {假};
如果数组中的元素数为 n(不是常量),则将该数组中的所有值初始化为 false 将始终是线性时间。但是,如果您考虑一下,如果此数组是某个更大算法的一部分,您通常可以找到一种不同的方法来在恒定时间内解决问题(我之所以做出这个假设,是因为如果您将数组中的所有元素初始化为"false",那么您一定不关心当前存储在那里的数据, 那么为什么要在这一点上对它做任何事情呢?
- Mongodb c++驱动程序:如何查询元素的数组
- 将数组作为参数传递给函数安全吗?作为第三方职能部门,可以探索他们想要的之外的其他元素
- 使用strcpy将char数组的元素复制到另一个数组
- 使用不带参数的函数访问结构元素
- 给定n个元素的m个集合.在C++中找到出现在最大集合数中的元素
- C++如何通过用户输入删除列表元素
- lower_bound()返回最后一个元素
- 基于多个条件处理地图中的所有元素
- 调整大小后指向元素值的指针unordered_map有效?
- 使用std::transform将一个范围的元素添加到另一个范围中
- 使用函数"remove"删除重复元素
- 具有最大子序列大小的序列,每个元素都相同
- 如何将两个不同矢量的同一位置的两个元素组合在一起
- 如何将元素添加到数组的线程安全函数?
- 有没有办法将谓词中的元素偏移量传递给 std 算法?
- 我想访问std::unique_ptr中的一个特定元素
- 如何通过 getter 函数删除矢量的元素?
- 向量元素的引用地址与它所指向的向量元素的地址不同.为什么
- 从控制台中删除最后打印的元素
- 擦除while循环中迭代的元素