【问题标题】:Data structure with quick min, delete, insert, search for big compute job具有快速最小、删除、插入、搜索大型计算作业的数据结构
【发布时间】:2021-09-22 15:51:53
【问题描述】:

我正在寻找一种可以让我高效执行所需操作的数据结构。我希望在 1011 和 1013 次之间遍历一个循环,因此 Ω(n) 操作是正确的。 (我会尝试修剪 n 以便它可以放入缓存中,但它不会很小。)每次通过循环我都会调用

  • 最少一次
  • 只删除一次(删除最小值,如果有帮助的话)
  • 插入0到2次,平均略多于1次
  • 为每个插入搜索一次

我只关心平均或摊销的性能,而不是最坏的情况。 (计算需要很长时间,如果计算的位不时停止也没关系。)数据不会是对抗性的。

我应该考虑什么样的结构?也许有某种堆被修改为快速搜索?

【问题讨论】:

  • 顺便说一句,如果你有资源(备忘单等)我可以自己回答这个问题,我会很高兴了解它。
  • 您描述的每个操作都需要 O(logn) 最坏情况帮助的数据结构的实现吗?这就是您所说的“也许修改了某种堆以进行快速搜索”的意思吗?
  • @Titan3 绝对。 (我希望这是一个足够简单的问题,不需要那种程度的工作,但如果这是你所拥有的,我当然很乐意接受。)

标签: optimization data-structures language-agnostic computer-science amortized-analysis


【解决方案1】:

根据您告诉我们的情况,我想我会使用开放地址哈希表进行搜索,并使用堆来跟踪最小值。

在堆中,您将存储指向哈希表中项目的索引/指针,而不是存储值。这样当你从堆中删除 min 时,你可以按照指针从哈希表中找到你需要删除的项。

每个项目的总内存开销为 3 或 4 个单词 - 与平衡树大致相同,但实现更简单、更快。

【讨论】:

    【解决方案2】:

    我建议的数据结构需要更多的工作来实现,但它确实达到了预期的结果; 具有{insert, delete, findMin, search} 操作的数据结构可以使用 AVL 树来实现,该树确保每个操作在 O(logn) 中完成,findMin 在 O(1) 中完成。

    我将深入了解一下实现:

    树将包含一个指向最小节点的指针,该指针在每次插入和删除时都会更新,因此findMin 需要O(1)

    insert 在每个采用O(logn) 的 AVL 树中实现(使用平衡因子和旋转/交换来平衡树)。插入元素后,您需要从树根一直到左侧来更新最小节点指针,这也需要O(logn),因为树高是O(logn)

    同样,在使用delete 之后,您需要以同样的方式更新最小指针,因此它需要O(logn)

    最后,search 还需要O(logn)

    如果给出更多假设,例如插入的元素在最小值的一定范围内,那么你也可以给树中的每个节点successorpredecessor指针,在插入和删除时也可以在O(logn)中更新,从而可以访问在O(1) 中,无需遍历整个树。并且可以更快地搜索插入的元素。

    插入节点的后继节点可以通过先到右子节点然后一直到左边来更新。但是,如果不存在右孩子,那么只要当前节点不是其父母的左孩子,您就需要爬上父母。 前身以完全相反的方式更新。

    在 C++ 中,节点看起来像这样

    template <class Key,class Value>
    class AvlNode{
    private:
        Key key;
        Value value;
        int Height;
        int BF; //balance factor
        AvlNode* Left;
        AvlNode* Right;
        AvlNode* Parent;
        AvlNode* Succ;
        AvlNode* Pred;
    
    public:
    ...
    }
    

    虽然树看起来像这样:

    template <class Key,class Value>
    class AVL {
    private:
        int NumOfKeys;
        int Height;
        AvlNode<Key, Value> *Minimum;
        AvlNode<Key, Value> *Root;
    
        static void swapLL(AVL<Key, Value> *avl, AvlNode<Key, Value> *root);
        static void swapLR(AVL<Key, Value> *avl, AvlNode<Key, Value> *root);
        static void swapRL(AVL<Key, Value> *avl, AvlNode<Key, Value> *root);
        static void swapRR(AVL<Key, Value> *avl, AvlNode<Key, Value> *root);
    
    public:
    ...
    }
    

    【讨论】:

      【解决方案3】:

      balanced tree 是一个非常适合这种用法的数据结构。所有指定的操作都在O(log n) 中计算。我认为您可以编写一个优化的树实现,以便可以在O(1) 中检索最小值(通过将迭代器保持在最小值和可能的值以更快地获取)。算法的结果时间将是O(m log n),其中m 是迭代次数,n 是数据结构中的项目数。

      这是最优算法复杂度。事实上,假设每次迭代都可以在(摊销)O(1) 中完成,那么这四个操作中的每一个都必须具有这样的复杂性。假设可以使用这样的属性构建数据结构S。可以编写如下算法(用 Python 编写):

      def superSort(input):
          s = S()
          inputSize = len(input)
          for i in range(inputSize):
              s.insert(item[i])
          output = list()
          for i in range(inputSize):
              output.append(s.getMin())
              s.deleteMin()
          return output
      

      superSort 的(摊销)复杂度为O(n)。然而,基于比较的排序has been proven 的理论最优精确算法复杂度为O(n log (n))。因此,S 不存在,至少需要在O(log n) 时间内完成 4 种操作中的至少一种。

      请注意,幼稚的二叉树实现通常效率很低。您可以执行很多优化以使它们更快。例如,您可以打包节点(参见B-trees),将节点放入数组中(假设项目数是有界的),使用可能基于随机属性的宽松平衡(参见Treaps),使用小引用(例如,16 位索引或 32 位索引,而不是 64 位指针)等。您可以从幼稚的 AVLsplay-tree 开始。

      【讨论】:

      • 谢谢,很有帮助!我不认为最优性证明是正确的——一个向后排序的数组有 O(1) min 和 delete-min,以及从这两者中进行的微不足道的 O(n) 排序(或 O(1) 什么都不做,如果您可以颠倒顺序)-尽管我也不希望所有四个操作都可以在 O(1) 中完成。
      • 证明不是很详细/清楚。我改进了它。关于您的示例,向后排序的数组实际上是一种数据结构,需要首先从一组值中构建。为此,除非您的输入本身已经排序(问题中未指定),否则需要对项目进行排序。此外,请注意,排序数组中的插入位于O(n) 中。有一些O(n) 排序,但它们都不是基于比较(例如基数排序)。
      • 谢谢,这个证明是可靠的(而且更清晰:它表明 min、delete-min 和 insert 中的至少一个必须至少花费对数时间,你不需要第四次操作) .
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-07-14
      • 2015-11-10
      • 1970-01-01
      • 2011-05-11
      • 2010-10-27
      • 2014-11-10
      相关资源
      最近更新 更多