我正在寻找一种数据结构,它保留了插入元素的顺序,并提供了一个快速的"包含"谓语。我还需要迭代器和随机访问。插入或删除期间的性能无关紧要。我也愿意接受内存消耗方面的开销。
背景:我需要存储一个对象列表。对象是名为Neuron
的类的实例,存储在Layer
中。 Layer
对象具有以下公共接口:
class Layer {
public:
Neuron *neuronAt(const size_t &index) const;
NeuronIterator begin();
NeuronIterator end();
bool contains(const Neuron *const &neuron) const;
void addNeuron(Neuron *const &neuron);
};
当软件运行时,contains()
方法经常被调用,我已断言使用callgrind。我试图规避对contains()
的一些调用,但仍然是一个热点。现在我希望能够完全优化这种方法。
我想过使用std::set
,使用template参数提供我自己的比较器结构。但是Neuron
类本身并没有在Layer
中找到它的位置。另外,我希望*someNeuronIterator = anotherNeuron
能够在不搞砸订单的情况下工作。
另一个想法是使用普通的旧C数组。由于我不关心添加新Neuron
对象的性能,我想我可以确保Neuron
对象始终在内存中保持线性。但这会使我传递给addNeuron()
的指针失效;至少我必须改变它以指向我创建的新副本以保持线性对齐。正确?
另一个想法是在Layer
对象中使用两个数据结构:订单的向量/列表,以及用于查找的映射/散列。但这与我对迭代器的期望相矛盾,迭代器允许operator*
没有const引用,不会吗?
我希望有人可以提出一个满足我需求的数据结构或概念的想法,或者至少给我一个替代方案的想法。谢谢!
答案 0 :(得分:1)
如果这个contains
检查确实是您需要最快执行的地方,并且假设您可能对源代码有点干扰,那么检查Neuron
是否属于某个层的最快方法是在将其插入图层时简单地标记它(例如:位标志)。
你已经保证在那一点上进行O(1)检查,看看Neuron
是否属于一个层,而且它在微观层面也很快。
如果可能存在多个图层对象,这可能会变得有点棘手,因为除非Neuron
只能属于单个图层,否则您需要为神经元可以属于的每个潜在图层分别使用一个位。一旦。但是,如果层数相对固定,则可以合理地管理。
如果后一种情况和一个神经元一次只能属于一个层,那么你需要的只是Layer*
的后向指针。要查看神经元是否属于图层,只需查看该backpointer是否指向图层对象。
如果Neuron
可以一次属于多个图层,但一次不能太多,那么您可以存储像这样的一小部分后置指针:
struct Neuron
{
...
Layer* layers[4]; // use whatever small size that usually fits the common case
Layer* ptr;
int num_layers;
};
如果ptr
所属的层数不超过4个,则初始化layers
以指向Neuron
。如果还有更多,请在免费商店中分配。在析构函数中,如果ptr != layers
释放内存。如果常见情况类似于1层,您还可以优化num_layers
,在这种情况下,以空值终止的解决方案可能会更好。要查看神经元是否属于某个图层,只需通过ptr
进行线性搜索即可。就神经元的数量而言,这实际上是恒定时间的复杂性,条件是它们不会同时属于大量的层。
你也可以在这里使用vector
,但是你可以减少那些常见情况下的缓存命中,因为它总是将其内容放在一个单独的块中,即使Neuron只属于1或2层
这可能与您使用通用的非侵入式数据结构所寻求的有点不同,但如果您的性能需求确实偏向于这些类型的集合操作,那么侵入式解决方案将是一般来说最快。它不是很漂亮,并且将你的元素与容器结合在一起,但是嘿,如果你需要最大的性能......
另一个想法是使用普通的旧C数组。由于我不关心添加新的Neuron对象的性能,我想我可以确保Neuron对象始终在内存中保持线性。但这会使我传递给addNeuron()的指针无效; [...]
是的,但它不会使指数无效。虽然不像指针那样方便使用,但是如果你正在处理像网格顶点或发射器粒子这样的海量数据,那么通常在这里使用索引来避免失效并且可能在每个条目上节省额外的32位。 64位系统。
鉴于神经元一次只存在于一个层中,我会采用后向指针方法。查看神经元是否属于一个层变得很简单,检查后指针是否指向同一层。
由于涉及到API,我建议,因为它听起来像是在推动大量数据并且已经对其进行了分析,因此您专注于围绕聚合(层,例如)的界面比个别元素(神经元)。当您的客户端没有在单个标量元素类型接口上执行操作时,它会给您留下很大的空间来换掉底层表示。
使用O(1)contains
实现和无序要求,我会选择一个简单的连续结构,如std::vector
。但是,您确实会在插入时暴露自己潜在的失效。
因此,如果可以,我建议在这里使用索引。然而,这变得有点笨拙,因为它要求你的客户端存储一个指向神经元所属的层的指针以及它的索引(尽管如果这样做,后面的指针变得不必要,因为客户端正在跟踪事物所属的位置)
缓解此问题的一种方法是使用std::vector<Neuron*>
或ptr_vector
(如果可用)。但是,这可能会使您遇到缓存未命中和堆开销,如果您想要优化它,那么这就是固定分配器派上用场的地方。但是,对于协调问题和一些研究主题来说,这有点痛苦,到目前为止,您的主要目标似乎不是像contains
检查那样优化插入或顺序访问,所以我从std::vector<Neuron*>
开始。
答案 1 :(得分:1)
您可以获得O(1)包含检查,O(1)插入并保留插入顺序。如果您使用的是Java,请查看LinkedHashMap。如果您不使用Java,请查看LinkedHashMap并找出并行数据结构,或者自己实现它。
它只是一个带有双向链表的散列图。链接列表是为了保留顺序,而hashmap是允许O(1)访问。因此,当您插入元素时,它会使用键创建一个条目,并且映射将指向链接列表中您的数据所在的节点。要查找,请转到哈希表以直接找到指向链接列表节点(而不是头部)的指针,并在O(1)中获取值。要按顺序访问它们,只需遍历链接列表。
答案 2 :(得分:0)
一堆声音听起来对你有用。它就像一棵树,但最新元素总是插在顶部,然后根据它的值向下工作,所以有一种方法可以快速检查它是否存在。
否则,你可以存储一个哈希表(用于检查神经元是否包含在表中的快速方法)和神经元的密钥,以及以下值:神经元本身以及神经元插入的时间步长(检查其按时间顺序插入时间。)