问题描述
我正在考虑可以在嵌入式/内存受限/文件系统等环境中使用的数据结构,并想到了一个类似列表的数据结构的想法,它具有 O(1) {access,insert,pop} 而也总是有 O(1) 推送(非摊销),即使它只能以恒定量(即 4KiB)增长。我在任何地方都找不到它的示例,我想知道它是否存在,如果存在,是否有人知道参考实现。
基本结构如下所示:
PyramidList 包含
- a
size_t numSlots
- a
size_t sizeSlots
- 一个
void** slots
指针,指向大小为sizeSlots
的指针数组,其中包含指向索引中值的指针,最大为numSlots
void **slots
数组的每个索引具有以下结构。它们的结构方式是 2^i = maxValues
其中 i
是索引,maxValues
是该索引处可以存在的值的最大数量或更少(即达到该索引的所有值)
- 索引 0:包含直接指向单个值 (
2^0 = 1
) 的指针 - 索引 1:包含直接指向单个值 (
2^1 = 2
) 的指针 - 索引 2:包含一个指向两个值数组的指针 (
2^2 = 4
) - 索引 3:包含一个指向四个值数组的指针 (
2^3 = 8
) - 索引 4:包含一个指向八个值数组的指针 (
2^4 = 16
) - .. 等
- index M:包含一个指向 MAX_NUM_VALUES (
2^M = MAX_NUM_VALUES*2
) 数组的指针 - 索引 M+1:包含一个指向 MAX_NUM_VALUES 数组的指针
- 索引 M+2:包含一个指向 MAX_NUM_VALUES 数组的指针
- 等
现在,假设我想访问索引 i
。我可以使用 BSR 指令来获取索引的“2 的幂”。如果它小于 MAX_NUM_VALUES 的 2 的幂,那么我有我的索引。如果它大于 MAX_NUM_VALUES 的 2 的幂,我可以采取相应的行动(减法和除法)。因此,我可以在 O(1) 时间内查找数组/单值,然后也可以在 O(1) 中访问我想要的索引。推送到 PyramidList 需要(最多):
- 分配一个新的 MAX_NUM_VALUES 并将它的指针添加到
slots
- 在某些情况下,
slots
可能无法保持它并且也必须增长,所以这只是 真的 总是 O(1) 达到某个限制,但是对于此处的用例,该限制可能是极端的。
- 在某些情况下,
- 将值插入到正确的索引中
其他一些好处
- 非常适合具有最大分配大小(即只能分配 4KiB 块)的(嵌入式/文件系统/内核/等)内存管理器
- 当您真的不知道您的向量可能有多大时,效果很好。开始时非常小,并以已知数量增长
- 始终具有(接近)恒定插入可能对时序要求严格的中断/等很有用
- 生长时不会留下碎片空间。可能非常适合将记录附加到文件中。
缺点
在几乎所有方面(甚至插入)都可能比连续向量的性能(摊销)低。移动内存通常比为每个操作添加取消引用的成本更低,因此向量的摊销成本可能仍然较小。
此外,它并不总是 O(1),因为当所有插槽都已满时,slots
向量必须增长,但这仅在自上次增长以来添加了 currentNumSlots*2*MAX_NUM_VALUES
时发生.
解决方法
当你超出了一个大小为 X 的数组的容量,因此分配了一个大小为 2X 的新数组时,你可以在接下来的 X 次追加操作中将 X 项从旧数组增量移动到新数组的开头.之后,当新数组已满时可以丢弃旧数组,就在您必须分配大小为 4X 的新数组之前。
因此,为了实现 O(1) 追加(假设分配为 O(1)),没有必要维护这个递增数组的列表。增量加倍是摊销业务中的一种众所周知的技术,所以我认为大多数希望这种行为的人会首先转向那种行为。
没有像这样的常用,因为内存分配几乎永远不会被认为是 O(1)。无法一次性复制所有块的应用程序通常根本无法使用任何类型的动态内存分配。