哪种数据结构按插入排序并具有快速"contains"检查?

Which data structure is sorted by insertion and has fast "contains" check?

本文关键字:contains 检查 数据结构 插入排序      更新时间:2023-10-16

我正在寻找一个数据结构,该数据结构保留了插入元素并提供快速"包含"谓词的顺序。我还需要迭代器和随机访问。插入或删除过程中的性能无关紧要。我也愿意在记忆消耗方面接受开销。

背景:我需要存储一个对象列表。对象是称为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,使用模板参数提供我自己的比较器结构。但是Neuron类本身不会在Layer中放置其位置。此外,我想让*someNeuronIterator = anotherNeuron在不搞砸订单的情况下工作。

另一个想法是使用一个普通的旧C数组。由于我不在乎添加新的Neuron对象的性能,因此我认为我可以确保Neuron对象始终存储在内存中。但这将使我传递给addNeuron()的指针无效;至少我必须将其更改以指向我创建的新副本,以保持线性对齐。对吗?

另一个想法是在Layer对象中使用两个数据结构:该顺序的向量/列表,以及用于查找的MAP/HASH。但这与我对允许没有const参考的operator*的迭代器的愿望矛盾,不是吗?

我希望有人可以暗示一个可以满足我需求的数据结构或概念的想法,或者至少给我一个替代方案的想法。谢谢!

如果此contains检查确实是您需要最快的执行的位置,并且假设您可以对源代码有些侵入性,则是检查Neuron是否属于一层的最快方法将其插入一层时简单地标记它(ex:bit flag)。

您保证O(1)在此时检查Neuron是否属于一层,并且在微级别也很快。

如果可以有许多图层对象,则可能会变得更棘手,因为除非Neuron只能一次单层属于单层,否则您需要一个单独的位置。但是,如果图层的数量相对较大,则可以合理地管理。

如果后一种情况和神经元只能一次属于一层,那么您所需要的只是Layer*的反点击插入。要查看神经元是否属于层中,只需查看该反点击量是否指向图层对象。

如果 Neuron can can 一次属于多层,但一次又属于多个层,那么您可以像这样的少量背景台上存储:

struct Neuron
{
    ...
    Layer* layers[4]; // use whatever small size that usually fits the common case
    Layer* ptr;
    int num_layers;
};

如果Neuron属于4个或更少的层,则将ptr初始化为layers。如果还有更多,请在免费商店中分配。在破坏者中,如果ptr != layers,请释放内存。如果常见情况大约是1层,则还可以优化num_layers,在这种情况下,无效的解决方案可能会更好。要查看神经元是否属于层,只需通过ptr进行线性搜索即可。对于神经元的数量而言,这实际上是恒定的时间复杂性。

您也可以在此处使用vector,但是您可能会在这些常见情况下的缓存命中,因为它总是将其内容放在单独的块中,即使神经元仅属于1或2层。

这可能与您想要的通用,非侵入性数据结构所寻找的东西有所不同,但是如果您的性能需求确实偏向于这些设置操作,那么侵入的解决方案将是总体上最快。它不太漂亮,并将您的元素融为容器,但是,如果您需要最大性能...

另一个想法是使用一个普通的旧C数组。由于我不在乎添加一个新的神经元对象的性能,因此我认为我可以确保神经元对象始终存储在内存中。但这将使我传递给addNeuron()的指针无效;[...]

是的,但不会无效 indices 。虽然不像指针那样方便使用,但是如果您正在使用诸如网格或发射器的粒子之类的质量数据,则通常在此处使用索引来避免无效,并且可能在每条条目上节省32位额外的32位64位系统。

更新

鉴于一次神经元一次仅在一层中存在,我会采用背部指针方法。查看神经元是否属于一层是一个简单的问题,即检查后指针是否指向同一层。

由于涉及API,我建议,只是因为听起来您正在推动大量数据并已经对其进行了介绍,因此您专注于围绕聚合的界面(例如),比单个元素(神经元)。当您的客户不在单个标量元素型界面执行操作时,它只会给您很多空间来交换基础表示。

使用O(1)contains实现和无序的要求,我将使用一个简单的连续结构(例如std::vector)。但是,您确实会暴露于插入的潜在无效。

因此,如果可以的话,我建议您在这里使用索引。但是,这变得有点笨拙,因为它要求您的客户在除了其索引之外属于神经元属于的层的两个指针(尽管如果您这样做,则在客户跟踪事物所属的地方时,背光媒体将变得不必要)。。

减轻这种情况的一种方法是简单地使用std::vector<Neuron*>ptr_vector之类的东西。但是,这可以使您暴露于缓存失误并堆放开销,如果您想优化这一点,这就是固定分配器派上用场的地方。但是,这在对齐问题和一些研究主题上有点痛苦,到目前为止,您的主要目标似乎不是优化插入或顺序访问,就像此contains检查一样多,所以我将从std::vector<Neuron*>

您可以获得O(1)包含检查,O(1)插入并保留插入顺序。如果您使用的是Java,请查看LinkedHashmap。如果您不使用Java,请查看LinkedHashMap,并找出一个并行数据结构,可以自己执行或实施它。

这只是一个具有双重链接列表的哈希图。链接列表是为了保留订单,hashmap是允许o(1)访问。因此,当您插入元素时,它将使用键进行输入,并且地图将指向链接列表中数据将驻留的节点。要查找,您可以转到哈希表,直接找到指针到您的链接列表节点(而不是头部),然后在o(1)中获取值。要顺序访问它们,您只需穿越链接列表即可。

堆听起来对您有用。它就像一棵树,但最新元素总是插入顶部,然后根据其值向下工作,因此有一种方法可以快速检查它是否存在。

否则,您可以用神经元的键存储一个哈希表(快速检查神经元是否包含在表中),值为:神经元本身,以及插入神经元的时间段(要检查其时间顺序插入时间)。