【问题标题】:What data structure to find number of elements in a given range in O(log n) time?什么数据结构可以在 O(log n) 时间内找到给定范围内的元素数量?
【发布时间】:2015-09-22 23:47:31
【问题描述】:

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

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

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

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

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

【问题讨论】:

  • 请不要在没有 cmets 的情况下投反对票!!
  • 我没有值得信赖的指导书,但如果你能拿到“Skiena, Steven S”的“算法设计手册”,那就去吧。是我的算法来源。
  • 我很确定哈希表应该有 log(n) 时间,但我不确定
  • @RNar,哈希表的插入和删除为 O(1),但在给定范围内查找元素的数量是 O(n)。除了重复值也是一个问题
  • @upr 如果我想到了这些:Similar questionSumarySTL 我已将它们添加为书签。

标签: c++ algorithm stl


【解决方案1】:

虽然不是 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);
}

【讨论】:

    【解决方案2】:

    虽然标准库确实功能完善,但我认为您不会在其中找到任何具有这些特殊要求的东西。正如您所指出的,类似集合的结构返回非随机访问迭代器——提供随机访问(或某种距离函数,根据您的需要)会引入很大的复杂性。

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

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

    【讨论】:

    • 跳过列表是此任务的正确数据结构。 “计数范围内的元素”任务变成了两个 O(log(n)) 查找。
    【解决方案3】:

    这个任务的两个明显的数据结构是跳过列表(Jack O'Reilly 已经提到过)和顺序统计树的一些变体(Behzad 提到但没有真正解释)。

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

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

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

    【讨论】:

      【解决方案4】:

      您应该能够使用标准或稍微修改的 B-Tree 来完成此操作。

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

      【讨论】:

        【解决方案5】:

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

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

        映射用于获取关于在列表中的何处插入节点的提示。这是通过为给定键创建一个新的映射条目,然后将迭代器减 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;
        
        };
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-12-28
          • 2013-02-20
          • 1970-01-01
          • 2013-11-19
          • 2013-02-23
          • 1970-01-01
          相关资源
          最近更新 更多