考虑不同类型的集合,例如Position
,Color
,Name
。可以使用集合中的相同键来连接这些实例。密钥是64位长度的全局唯一标识符。目前,我使用的是哈希映射,但这并不理想。
// custom types
struct Position { float x, y, z; bool static; };
enum Color { RED, BLUE, GREEN };
// collections
std::unordered_map<uint64_t, Position> positions;
std::unordered_map<uint64_t, Color> colors;
std::unordered_map<uint64_t, std::string> names;
// add some data
// ...
// only access positions collection,
// so the key is not needed here
for (auto i = positions.begin(); i != positions.end(); ++i) {
if (i->second.static) continue;
i->second.x = (rand() % 1000) / 1000.f;
i->second.y = (rand() % 1000) / 1000.f;
i->second.z = (rand() % 1000) / 1000.f;
}
// access to all three collections,
// so we need the key here
for (auto i = positions.begin(); i != positions.end(); ++i) {
uint64_t id = *i->first;
auto position = i->second;
auto color = colors.find(id);
auto name = names.find(id);
if (color == colors.end() || name == names.end()) continue;
draw(*name, *position, *color);
}
我尝试将集合分开,但正如您所看到的,同时还需要收集多个集合的实例。当然,我还需要不时添加或删除单个元素,但这些情况并不是性能关键。
现在我想优化对各个集合的迭代。因此,我尝试连续存储集合,这是数据导向设计思想的一部分。但是,我仍然需要非常快速地访问单个实例。直接使用数组不起作用,因为这将分配太多的内存而不是一种类型的所有实例都具有另一种类型的对应物。另一方面,哈希映射不是迭代的最佳选择。
我认为数据结构必须在内部使用数组。我应该在这里使用哪种数据类型?是否在C ++标准库中实现了这样的数据结构?
答案 0 :(得分:3)
创建unordered_map
以抵消std::vector
。
将您的元素存储在std::vector
中。如果要删除元素,请将其与std::vector
的最后一个元素交换,并删除最后一个元素。浏览将索引存储到unordered_map
的{{1}}并修复指向最后一个元素的那些指向最后一个元素的位置。
删除元素现在是O(n)。如果你一次删除一堆元素,你可以在一次O(n)传递中以一点创造力完成所有这些元素。
添加元素仍为O(1)。
迭代所有元素涉及迭代std::vector
。如果在迭代时需要哈希值,请将其冗余存储在那里,或者即时计算。
将std::vector
和std::vector
包含在强制执行上述规则的类型后面,否则不变量将永远不会持久存在,从而在外部公开类似std::unordered_map
的接口。
作为O(n)删除的替代方法,创建一个存储map
??? std::vector
的并行std::unordered_map<
。仔细跟踪>::iterator
中何时发生重新散列(重新散列是确定性的,但是当它自己发生时你必须解决),以及它何时重建它(因为所有std::unordered_map
都会通过rehash无效)。重新发生很少发生,因此具有摊销的常数成本 1 。当您想要擦除时,您现在可以在O(1)时间内更新iterator
中的索引(记住也要更新第二个unordered_map
- 从最后一个到删除元素的交换位置)。您可以将其存储在与原始数据相同的std::vector
中,但这会将其增加(这将减慢迭代速度)。
1 当容器通过达到std::vector
大小时,重新发生。 max_load_factor()*bucket_count()
然后以指数方式增长,并且元素被移动。与bucket_count
的增长算法非常相似,这可以保证元素的线性数量总元素移动 - 因此它保持摊销的常量插入。反向表的手动重建并不比移动所有现有元素更昂贵,因此它也会产生一个摊销的常量插入时间因子。