恒定时间在C ++中改变数组的前k个元素?

时间:2012-10-13 02:57:59

标签: c++ arrays

我们假设我有一个数组:

bool eleme[1000000] = {false};

在我的代码中的某个时刻,我将此数组的第一个n元素中的一些更改为true。 之后我想确保数组的所有元素都是false。所以我这样做:

for (int i =0; i < n; ++i)     
       eleme[i] = false;

费用为Θ(n)

有没有办法在恒定时间内完成这项工作?例如。就像是 make_false(eleme, n);

4 个答案:

答案 0 :(得分:3)

一般答案
如果要修改内存中的N个元素,那么最终将是一个O(N)操作,无论您是否可以使用memsetstd::fill这样的单个命令来表达它。

如果您设计算法使得尽可能多的数组在缓存中,操作将会快得多。使用memset等优化的内置命令也有助于加快速度。


建议1
但是,有一个旧的算法技巧,用于恒定时间阵列初始化,这也适用于您的情况(恒定时间阵列重置) - 但代价是显着的额外内存使用。 / p>

如下所示:除了主数组A1之外,还分配了一个长度相同的第二个数组A2和一个大小为N的堆栈S。这些结构需要初始化(并且仅仅分配它们可以说是O(1)操作)。您还需要一个堆栈指针SP

最初堆栈指针为0(指向堆栈的底部)。

每当您在A1中输入A1[i]=j时,请设置A2[i]=SPS[SP]=i并增加SP

如果您想检查某个条目A1[i]是否已设置,请查找A2[i]。如果A2[i]<SP,即小于堆栈指针的当前值,则您知道之前必须已设置相应的堆栈条目SP[A2[i]]。如果该堆栈条目的值为i,则A1[i]是有效条目。否则它从未被初始化。

现在,为了重置A1的所有条目,只需将堆栈指针设置回0.这是一个恒定时间操作。

我必须承认,我从未遇到过这样一种情况,即我发现这个技巧实际上是有用的;通常memset,虽然不是恒定时间,但速度足够快。

Gonzalo Navarro最近发表了一篇note,其中他描述了另一组压缩额外数组的技巧,以便在保留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的默认构造函数无论如何都将整个数组初始化为零,如果N为零,也可以设置为init_val。因此上面的实现无助于避免初始O(N)初始化 - 我们只避免reset()中的O(N)。 )

答案 1 :(得分:1)

  

有没有办法在恒定时间内完成此操作[并且不能访问超过n个元素]?

没有。您必须设置n个元素,这些元素将采用n个步骤,因此为O(n)。

通过不手动编写循环,可以使速度更快。我想你会发现:

std::fill(eleme, eleme+n, false);

更快
for (int i =0; i < n; ++i)     
   eleme[i] = false;

即使他们具有相同的大O复杂性。

答案 2 :(得分:1)

根据

http://en.wikipedia.org/wiki/Time_complexity#Constant_time

您的代码已经是固定时间

如果事先知道元素的数量并且没有改变

从声明

看来是正确的

bool eleme [1000000] = {false};

答案 3 :(得分:0)

如果数组中的元素数是n(非常数),则将该数组中的所有值初始化为false将始终处于线性时间。但是,如果你考虑一下,如果这个数组是一些更大的算法的一部分,你通常可以找到一种不同的方法来解决你的问题在恒定的时间(我正在做这个假设,因为如果你正在初始化所有的元素数组为'false',那么你一定不关心当前存储的数据,那么为什么要用它做什么呢?)。