我应该使用哪个容器进行随机访问,廉价的添加和删除(没有取消/分配),具有已知的最大大小
Which container should I use for random access, cheap addition and removal (without de/allocation), with a known maximum size?
我需要更轻的容器,它必须存储到 128 个无符号的 int。它必须快速添加、编辑和删除访问它的每个元素,而无需每次都分配新内存(我已经知道它最多会是 128 个)。
如:
add int 40 at index 4 (1/128 item used)
add int 36 at index 90 (2/128 item used)
edit to value 42 the element at index 4
add int 36 at index 54 (3/128 item used)
remove element with index 90 (2/128 item used)
remove element with index 4 (1/128 item used)
。等等。因此,每次我可以迭代时,仅trought添加到容器中的实际元素数量,而不是全部,并检查是否为NULL。
在此过程中,正如我所说,它一定不能分配/重新分配新内存,因为我使用的是管理"音频"数据的应用程序,这意味着每次触摸内存时都会出现故障。
哪个容器是正确的候选者?这听起来像一个"索引"队列。
据我了解,您有两个操作
在单元格索引
处插入/替换元素值删除单元格索引
处的元素
和一个谓词
单元格索引当前是否被占用?
这是一个数组和一个位图。 插入/替换时,将值粘贴到数组单元格中并设置位图位。 删除时,将清除位图位。 询问时,查询位图位。
你可以只使用 std::vector<int>
并执行vector.reserve(128);
来防止向量分配内存。但是,这不允许您跟踪特定索引。
如果您需要跟踪"索引",您可以使用std::vector<std::pair<int, int>>
。不过,这不允许随机访问。
如果你只需要便宜的设置和擦除值,只需使用数组。你可以通过在另一个数组(或位图)中标记它们来跟踪使用了哪些单元格。或者仅将一个值(例如 0 或 -1)定义为"未使用"的值。
当然,如果您需要遍历所有使用的单元格,则需要扫描整个数组。但这是您需要做出的权衡:要么在添加和擦除期间执行更多工作,要么在搜索期间执行更多工作。(请注意,vector<>
中间的.insert()
将移动数据。
无论如何,128 个元素是如此之少,以至于扫描整个数组的工作可以忽略不计。坦率地说,我认为任何比vector
更复杂的事情都是矫枉过正。:)
大约:
unsigned data[128] = {0}; // initialize
unsigned used[128] = {0};
data[index] = newvalue; used[index] = 1; // set value
data[index] = used[index] = 0; // unset value
// check if a cell is used and do something
if (used[index]) { do something } else { do something else }
我建议使用串联向量,一个用于保存活动索引,另一个用于保存数据:
class Container
{
std::vector<size_t> indices;
std::vector<int> data;
size_t index_worldToData(size_t worldIndex) const
{
auto it = std::lower_bound(begin(indices), end(indices), worldIndex);
return it - begin(indices);
}
public:
Container()
{
indices.reserve(128);
data.reserve(128);
}
int& operator[] (size_t worldIndex)
{
return data[index_worldToData(worldIndex)];
}
void addElement(size_t worldIndex, int element)
{
auto dataIndex = index_worldToData(worldIndex);
indices.insert(it, worldIndex);
data.insert(begin(data) + dataIndex, element);
}
void removeElement(size_t worldIndex)
{
auto dataIndex = index_worldToData(worldIndex);
indices.erase(begin(indices) + dataIndex);
data.erase(begin(indices) + dataIndex);
}
class iterator
{
Container *cnt;
size_t dataIndex;
public:
int& operator* () const { return cnt.data[dataIndex]; }
iterator& operator++ () { ++dataIndex; }
};
iterator begin() { return iterator{ this, 0 }; }
iterator end() { return iterator{ this, indices.size() }; }
};
(免责声明:编译器未触及代码,省略前提条件检查)
这个具有对数时间元素访问,线性时间插入和删除,并允许迭代非空元素。
您可以使用双向链表和节点指针数组。预分配 128 个列表节点并将它们保留在 freelist
上。创建一个空itemlist
。分配一个由 128 个节点指针组成的数组,称为 items
- 插入
i
:从freelist
弹出头节点,将其添加到itemlist
,将items[i]
设置为指向它。 - 若要访问/更改值,请使用
items[i]->value
- 要在
i
处删除,删除items[i]
指向的节点,将其重新插入"自由列表" - 要迭代,只需走
itemlist
一切都是 O(1),除了迭代,它是 O(Nactive_items)。 唯一需要注意的是,迭代不是按索引顺序排列的。
自由列表可以是单链接的,甚至可以是节点数组,因为您所需要的只是弹出和推送。
class Container {
private:
set<size_t> indices;
unsigned int buffer[128];
public:
void set_elem(const size_t index, const unsigned int element) {
buffer[index] = element;
indices.insert(index);
}
// and so on -- iterate over the indices if necessary
};
您可以使用多种方法,我将按花费的努力顺序引用它们。
最实惠的解决方案是使用Boost非标准容器,特别感兴趣的是flat_map
。从本质上讲,flat_map
通过动态数组提供的存储提供map
接口。
您可以在开始时调用其reserve
成员,以避免之后的内存分配。
稍微复杂的解决方案是编写自己的内存分配器。
分配器的接口相对容易处理,因此对分配器的编码非常简单。创建一个永远不会释放任何元素的池分配器,预热它(分配 128 个元素),您就可以开始使用了:它可以插入任何集合以使其无需内存分配。
这里特别感兴趣的当然是std::map
.
最后,是自己动手的道路。很明显,涉及更多:标准容器支持的操作数量只是......巨大。
不过,如果您有时间或只能接受这些操作的子集,那么这条路有一个不可否认的优势:您可以根据您的需求专门定制容器。
这里特别令人感兴趣的是拥有 128 个元素std::vector<boost::optional<int>>
的想法......除了由于这种表示在空间上效率很低,我们使用面向数据的设计来代替它成为两个向量:std::vector<int>
和 std::vector<bool>
,这更紧凑,甚至......
struct Container {
size_t const Size = 128;
int array[Size];
std::bitset<Size> marker;
}
既紧凑又无分配。
现在,迭代需要迭代当前元素的位集,乍一看似乎很浪费,但所述位集只有 16 个字节长,所以轻而易举!(因为在这样的规模下,内存局部性胜过大O复杂性)
为什么不使用std::map<int, int>
,它提供随机访问并且是稀疏的。
vector
(预先保留)不够方便,请查看 Boost.Container 以获取各种"扁平"索引集合。这将把所有内容存储在一个向量中,不需要内存操作,但在上面添加一个层,使其成为一个集合或映射,可通过哪些元素存在来索引,并能够分辨哪些元素不存在。
- 将数组的地址分配给变量并删除
- 删除一个线程上有数百万个字符串的大型哈希映射会影响另一个线程的性能
- C/C++编译器通常会删除重复的库吗
- 挂起和取消挂起一个文件DLL
- 文件删除(使用取消链接)与释放所有磁盘空间之间的延迟
- 删除通过取消引用新对象初始化的对象
- 是否可以删除取消引用的指针
- 从 QTreeView 中删除项目时取消选择所有行
- 从矢量C++中删除时,矢量迭代器不可取消引用
- 如何从TBB :: Flow :: Graph中删除/取消消息
- 如何从属性表中删除确定,取消和应用按钮
- C/C++:取消分配或删除动态创建的内存块
- 我应该删除一个取消引用的指针数组吗?
- 优化器删除指针取消引用行
- 正在从矢量中删除取消引用的元素
- SortedList C++从数组中删除和取消删除元素
- 何时在C++中删除/取消引用
- 我们是否应该在取消注册时删除观察者
- 取消脱壳:删除模板角度之间的空间不起作用
- 我应该使用哪个容器进行随机访问,廉价的添加和删除(没有取消/分配),具有已知的最大大小