C++ 在O(logn)时间内查找给定范围内元素数量的数据结构是什么?

C++ 在O(logn)时间内查找给定范围内元素数量的数据结构是什么?,c++,algorithm,stl,C++,Algorithm,Stl,我正在解决一个问题,我意识到我需要一个具有以下属性的数据结构,但即使在谷歌搜索几个小时后也找不到。我相信STL图书馆太丰富了,不可能没有这个,所以我在这里问 在O(log(n))time中插入任何元素(应该能够包含重复的元素) 同时删除O(log(n))time中的一个元素 如果我想查询[a,b]范围内的榄香烯数,我 应该在O(log(n))time中获取该计数 如果我从头开始编写,对于第1部分和第2部分,我将使用集合或多集合,并修改它们的find()方法(在O(log(N))time中运行),

我正在解决一个问题,我意识到我需要一个具有以下属性的数据结构,但即使在谷歌搜索几个小时后也找不到。我相信STL图书馆太丰富了,不可能没有这个,所以我在这里问

  • O(log(n))
    time中插入任何元素(应该能够包含重复的元素)
  • 同时删除
    O(log(n))
    time中的一个元素
  • 如果我想查询[a,b]范围内的榄香烯数,我 应该在
    O(log(n))
    time中获取该计数
  • 如果我从头开始编写,对于第1部分和第2部分,我将使用
    集合
    多集合
    ,并修改它们的
    find()
    方法(在
    O(log(N))
    time中运行),以返回索引而不是迭代器,这样我就可以
    abs(find(a)-find(b))
    所以我得到了log(N)时间中的元素计数。但不幸的是,
    find()
    返回和迭代器

    我已经研究了
    multiset()
    ,但在
    O(log(n))
    时间内我无法完成要求3。需要
    O(n)


    有什么提示可以轻松完成吗?

    尽管标准库的功能确实很好,但我认为您不会在其中找到任何符合这些特定要求的内容。正如您所注意到的,类似集合的结构返回非随机访问迭代器——提供随机访问(或您需要的某种距离函数)将带来极大的复杂性

    您可以通过实现可索引的跳过列表来实现目标,该列表提供O(log(n))插入、删除和索引查找,如下所述:

    可在此处找到实施指南:

    您应该能够通过标准或稍加修改的B-树来实现这一点


    通常,对于B树实现,大多数标准操作都是O(log(n))。

    虽然不是STL的一部分,但您可以使用gcc扩展的一部分;具体而言,您可以按如下方式初始化。代码使用
    gcc
    编译,无需任何外部库:

    #include<iostream>
    #include<ext/pb_ds/assoc_container.hpp>
    #include<ext/pb_ds/tree_policy.hpp>
    
    using namespace __gnu_pbds;
    using namespace std;
    
    int main()
    {
        tree<int,         /* key                */
             null_type,   /* mapped             */
             less<int>,   /* compare function   */
             rb_tree_tag, /* red-black tree tag */
             tree_order_statistics_node_update> tr;
    
        for(int i = 0; i < 20; ++i)
            tr.insert(i);
    
        /* number of elements in the range [3, 10) */
        cout << tr.order_of_key(10) - tr.order_of_key(3);
    }
    
    #包括
    #包括
    #包括
    使用名称空间_gnu_pbd;
    使用名称空间std;
    int main()
    {
    树tr;
    对于(int i=0;i<20;++i)
    tr.插入(i);
    /*范围[3,10]中的元素数*/
    
    cout这个任务的两个明显的数据结构是跳过列表(Jack O'Reilly已经提到)和顺序统计树的一些变体(Behzad提到了,但没有真正解释)

    顺序统计树在每个节点中存储一条额外的信息。您可以存储许多不同的内容,但我发现最容易理解的是,如果每个节点在其左子树中存储元素的数量

    插入时,当您沿着树向下移动以存储元素时,每次下降到树的左侧时,您都会增加计数。因为您只是修改要遍历的节点,所以这不会更改插入的
    O(log N)
    。当您平衡时,您必须进行相应的调整(但是,同样,在进行旋转时,您只修改已经修改的节点的计数,因此(同样)不会影响总体复杂性


    当您需要查找从一个元素到另一个元素的距离时,只需查找两个节点,每个节点的复杂度为O(logn)。通过从根目录初始化索引,然后从根目录更新索引,即可获得树中每个元素的索引(向左下降时减去计数,向右下降时相加).

    它当然不是最节省空间的,给出了O(3n),但它满足上面列出的插入、删除和距离标准。下面使用了链表、映射和无序映射

    该列表用于维护顺序。通常按顺序插入列表是线性时间,但在键到列表的迭代器映射的帮助下,我们可以插入常量时间。将有序数据存储在列表中的优点是,它使用随机访问迭代器,这意味着您可以获得常量时间std::distance调用

    映射用于获取关于在列表中的何处插入节点的提示。这是通过为给定键创建一个新的映射项,然后将迭代器递减1来完成的。此新迭代器的值为我们提供链接列表中适当的插入位置

    无序的_映射为我们提供了随机访问列表迭代器的o(1)查找,允许我们获得o(logn)删除和距离时间

    class logn
    {
    public:
        using list_it = std::list<int>::iterator;
    
        void insert(int i)
        {
            const bool first = list.empty();
            auto original_it = map.insert({i,list.end()}).first; // o(logn)
            auto hint = original_it;
            if (!first)
                --hint; 
            umap[i] = list.insert(hint->second,i);  // o(1)
            original_it->second = umap[i]; 
        }
    
        void remove(int i)
        {
            auto it = umap.find(i); // o(1)
            list.erase(it->second); // o(1)
            umap.erase(it);         // o(1)
            map.erase(map.find(i)); // o(logn)
        }
    
        list_it get(int i) const
        {
            return umap.find(i)->second;
        }
    
        unsigned int distance(int lower, int upper) const
        {
            return std::distance(get(lower), get(upper));
        }
    
    private:
    
        std::list<int> list;
        std::unordered_map<int, list_it> umap;
        std::map<int, list_it> map;
    
    };
    
    类日志
    {
    公众:
    使用list_it=std::list::iterator;
    空白插入(int i)
    {
    const bool first=list.empty();
    auto original_it=map.insert({i,list.end()}).first;//o(logn)
    自动提示=原始提示;
    如果(!第一个)
    --暗示;
    umap[i]=list.insert(提示->第二个,i);//o(1)
    初始值->秒=umap[i];
    }
    无效删除(int i)
    {
    autoit=umap.find(i);//o(1)
    list.erase(它->秒);//o(1)
    umap.erase(它);//o(1)
    map.erase(map.find(i));//o(logn)
    }
    列表获取(int i)常量
    {
    返回umap.find(i)->second;
    }
    无符号整数距离(整数下限,整数上限)常数
    {
    返回标准::距离(get(下)、get(上));
    }
    私人:
    std::列表;
    std::无序地图umap;
    地图;
    };
    
    请不要无评论的否决票!!我没有可靠的guid手册,但是如果你能找到“Skiena,Steven S”的“算法设计手册”,那么就这样做。这是我的算法来源。我很确定哈希表应该有记录时间,但我没有sure@RNar,哈希表有O(1)对于insert和delete,但在给定范围内查找元素的数量是O(n)。此外,具有重复值也是一个问题problem@upr如果我想到了这些:,我就把它们标记为书签。跳过列表是这个任务的正确数据结构