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

时间:2015-09-22 23:47:31

标签: c++ algorithm stl

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

  1. O(log(n))时间
  2. 中插入任何元素(应该能够包含重复元素)
  3. 同时删除O(log(n))时间内的元素。
  4. 如果我想查询范围[a,b]中的元素数量,我 应该在O(log(n))时间......
  5. 得到这个数

    如果我是从头开始编写的,对于第1部分和第2部分,我会使用setmultiset我会修改他们的find()方法(以{{运行} 1}} time)返回索引而不是迭代器,这样我才能做到 O(log(N))所以我得到log(N)时间内的元素数。但不幸的是,abs(find(a)-find(b))返回并使用了迭代器。

    我调查了find(),我无法在multiset()时间内完成要求3。它需要O(log(n))

    有任何提示可以轻松完成吗?

5 个答案:

答案 0 :(得分:5)

虽然不是STL的一部分,但您可以使用Policy-Based Data Structures作为gcc扩展的一部分;特别是,您可以初始化order statistics tree,如下所示。代码使用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);
}

答案 1 :(得分:4)

虽然标准库确实功能齐全,但我认为你不会在那里找到任何有这些特殊要求的东西。如您所述,类似于集合的结构返回非随机访问迭代器 - 提供随机访问(或某种距离函数,如您所需)会带来显着的复杂性。

您可以通过实现可索引的跳过列表来实现目标,该列表提供O(log(n))插入,删除和索引查找,如下所述: https://en.wikipedia.org/wiki/Skip_list#Indexable_skiplist

可在此处找到实施指南: http://cg.scs.carleton.ca/~morin/teaching/5408/refs/p90b.pdf

答案 2 :(得分:4)

此任务的两个明显的数据结构是跳过列表(Jack O&Reilly已经提到过)和订单统计树的一些变体(Behzad提到但没有真正解释)。 / p>

订单统计树在每个节点中存储一条额外的信息。您可以存储许多不同的东西,但我最容易理解的是每个节点是否在其左子树中存储元素的数量。

插入时,当您沿树向下移动以存储元素时,每次在树中向左下降时都会递增计数。由于您只修改了您要遍历的节点,因此不会更改O(log N)插入。当您重新平衡时,您必须相应地进行调整(但是,您再次只修改您在旋转时已经修改的节点中的计数,因此(再次)您不会影响整体复杂性。

当您需要找到从一个元素到另一个元素的距离时,您只需找到两个节点,每个节点都具有O(log N)复杂度。通过从根目录初始化索引,然后从那里更新它来获取树中每个元素的索引,然后从那里更新它(当你下降到左边时减去计数,当你下降到右边时添加)。

答案 3 :(得分:2)

您应该能够使用标准或略微修改的B树来完成此任务。

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

答案 4 :(得分:0)

它当然不是最节省空间的,给出O(3n),但它满足上面列出的插入,移除和距离标准。以下使用链表,map和unordered_map。

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

该映射用于获取有关插入节点的列表中的位置的提示。这是通过为给定键创建一个新的映射条目,然后将迭代器递减1.这个新迭代器的值为链接列表提供了适当的插入位置。

unordered_map为我们提供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;

};