/*
在我写这篇文章的时候,这个页面上的所有解决方案都会加倍(或者
更多)存储数组所需的空间量。以下解决方案
将浪费的空间量从 Ω(n) 减少到 θ(n/w),其中 w 是
计算机“单词”中的位。在我的机器上是 64。
这个答案中的这篇散文是在 C cmets 中的,所以你可以复制粘贴这个
逐字回答并使用您的 C 编译器进行编译。
*/
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
/*
问题是支持在 O(1) 时间内读取和写入数组中的值
以及批量写入,其中数组中的所有值一次写入
O(1) 时间。这可以使用 Aho、Hopcroft 和
厄尔曼,据我所知。由于Gonzalo Navarro,
"Constant-Time Array Initialization in Little
Space",我将提供一个版本。
这个想法是将三个元数据数组与数据数组一起保留。我们也
保留两个整数:unset,这是批量写入中使用的最后一个值
操作,和size,一个近似值的数量已经
自上次批量写入以来设置。在任何时候,不同值的数量
自上次批量写入后写入的时间介于 size 和 w * size 之间。
三个元数据数组描述了关于 w 值块的信息
数据数组。它们是:
nth: nth[i] 是自上次批量写入以来要写入的第 i 个唯一块
写
inverse_nth: inverse_nth[i] 是第 i 个块的顺序
数组已写入,在最后一次批量写入时从 0 开始计数。
bitset:数组单元编号时bitset[i]的第j位为1
自上次批量写入以来已写入 64*i + j。
bitset[i] 和 inverse_nth[i] 如果i 不是
集合 {nth[0], nth[1], ... , nth[size-1]} 的成员。换一种说法,
inverse_nth[i] 和 bitset[i] 有效当且仅当 inverse_nth[i] < size
和nth[inverse_nth[i]] == i。
我没有存储三个相同长度的单独数组,而是选择存储一个
数组,is_set,包含三个字段。
*/
typedef struct {
int nth_;
int inverse_nth_;
uint64_t bitset_;
} IsSetCell;
typedef struct {
int unset_;
int size_;
IsSetCell is_set_[];
} IsSetArray;
typedef struct {
IsSetArray * metadata_;
int payload_[];
} ResettableArray;
/*
要构造一个数组,我们需要一个默认值来在读取a时返回
从未被写入的值。
*/
ResettableArray * ConstructResettableArray(int n, int unset) {
ResettableArray* result =
malloc(offsetof(ResettableArray, payload_) + n * sizeof(int));
if (!result) return NULL;
n = (n + 63) / 64;
result->metadata_ =
malloc(offsetof(IsSetArray, is_set_) + n * sizeof(IsSetCell));
if (!result->metadata_) {
free(result);
return NULL;
}
result->metadata_->size_ = 0;
result->metadata_->unset_ = unset;
return result;
}
void DestructResettableArray(ResettableArray * a) {
if (a) free(a->metadata_);
free(a);
}
/*
算法的大部分内容是写入和读取元数据。后
IsSet() 和 Set() 已定义(如下),读取和写入数组是
直截了当。
*/
bool IsSet(const IsSetArray * a, int i);
void Set(IsSetArray * a, int i);
int GetValue(const ResettableArray * a, int i) {
if (!IsSet(a->metadata_, i)) return a->metadata_->unset_;
return a->payload_[i];
}
void SetValue(ResettableArray * a, int i, int v) {
a->payload_[i] = v;
Set(a->metadata_, i);
}
void SetAllValues(ResettableArray * a, int v) {
a->metadata_->unset_ = v;
}
/*
读写的复杂部分是双向关系
在inverse_nth 和nth 之间。如果它们在位置 i 相互指向
(is_set[is_set[i].inverse_nth].nth == i) 然后位置 i 包含有效数据
在最后一次批量写入之后写入,只要is_set[i].inverse_nth <
size。
*/
uint64_t OneBit(int i) {
return UINT64_C(1) << i;
}
bool IsSet(const IsSetArray * a, int i) {
const int cell = i/64, offset = i & 63;
const int inverse_nth = a->is_set_[cell].inverse_nth_;
return inverse_nth < a->size_ && a->is_set_[inverse_nth].nth_ == cell &&
a->is_set_[cell].bitset_ & OneBit(offset);
}
void Set(IsSetArray * a, int i) {
const int cell = i/64, offset = i & 63;
const int inverse_nth = a->is_set_[cell].inverse_nth_;
if (inverse_nth >= a->size_ || a->is_set_[inverse_nth].nth_ != cell) {
a->is_set_[cell].inverse_nth_ = a->size_;
a->is_set_[cell].bitset_ = 0;
a->is_set_[a->size_].nth_ = cell;
++a->size_;
}
a->is_set_[cell].bitset_ |= OneBit(offset);
}