【问题标题】:How do I further optimize this Data Structure?如何进一步优化此数据结构?
【发布时间】:2014-11-11 20:56:33
【问题描述】:

我最近被要求构建一个支持四种操作的数据结构,即,

  1. 推送:向 DS 添加一个元素。
  2. Pop:移除最后推送的元素。
  3. Find_max:从当前存储的元素中找出最大的元素。
  4. Pop_max:从 DS 中删除最大元素。

元素是整数。

这是我建议的解决方案:

  1. 拿一叠。
  2. 在其中存储一对元素。该对应该是 (element, max_so_far),其中 element 是该索引处的元素,max_so_far 是迄今为止看到的最大值元素。
  3. 在将元素压入堆栈时,检查最顶部堆栈元素的 max_so_far。如果当前数字大于该值,则将当前对的 max_so_far 值作为当前元素的值,否则存储前一个 max_so_far。这意味着推送只是一个O(1) 操作。
  4. 对于pop,只需从堆栈中弹出一个元素。同样,这个操作是O(1)
  5. 对于Find_max,返回栈顶元素max_so_far的值。再次,O(1)
  6. 在分配新的 max_so_far 值之后,弹出最大元素将涉及遍历堆栈并显式删除最大元素并将其顶部的元素推回。这将是线性的。

有人要求我改进它,但我做不到。

就时间复杂度而言,我猜如果所有操作都发生在O(logn) 中,整体时间可以得到改善。如何做到这一点,是我无法得到的。

【问题讨论】:

  • 你写过代码吗?
  • 编写代码非常简单。问题不在于代码,而在于算法。
  • 当你得到一个实现编码和工作时,考虑把它发布到Code Review :) 他们会有关于代码以及算法的指针。
  • 很大程度上取决于您希望拥有多少项目,以及每个操作的调用频率
  • @IanRingrose 是的!你说得对。但是由于我们被告知要构建一个数据结构,我们可以放心地假设每个方法将以相同的频率被调用。在这种情况下,我的解决方案将花费 O(n) 操作的二次时间,而接受的解决方案将花费 O(nlogn)。

标签: algorithm data-structures time-complexity


【解决方案1】:

一种方法是将指向元素的指针存储在双向链表以及max-heap 数据结构中(按值排序)。

每个元素都会在双向链表和最大堆中存储其位置。

在这种情况下,您的所有操作在双向链表中都需要 O(1) 时间,在堆数据结构中需要 O(log(n)) 时间。

【讨论】:

  • 很好的解决方案!简洁大方。
  • 我看不到 DLL 在这里有什么帮助。 find_max 是 O(1),剩下的 O(log(n)) 只有一个堆。
  • +1:DLL提供pop()函数所需的插入顺序。
【解决方案2】:

获得 O(log n) 时间操作的一种方法是将两个数据结构混合在一起,在这种情况下是一个双向链表和一个优先级队列(配对堆是一个不错的选择)。我们有一个像

这样的节点结构
struct Node {
    Node *previous, *next;  // doubly linked list
    Node **back, *child, *sibling;  // pairing heap
    int value;
} list_head, *heap_root;

现在,为了推动,我们推动两种结构。为了 find_max,我们返回配对堆的根值。要pop或pop_max,我们从相应的数据结构中弹出,然后使用其他节点指针在其他数据结构中删除。

【讨论】:

    【解决方案3】:

    通常,当您需要按质量 A(值)和质量 B(插入顺序)查找元素时,您开始关注一个数据结构,该数据结构实际上有两个相互引用的数据结构,或者以其他方式交错的。

    例如:谁的键是质量 A 和质量 B 的两个映射,谁的值是指向结构的共享指针,该结构包含返回两个映射的迭代器和值。然后你有 log(n) 通过任一质量找到一个元素,擦除是 ~O(logn) 从任一映射中删除两个迭代器。

    struct hybrid {
        struct value {
            std::map<std::string, std::shared_ptr<value>>::iterator name_iter;
            std::map<int, std::shared_ptr<value>>::iterator height_iter;
            mountain value;
        };
        std::map<std::string, std::shared_ptr<value>> name_map;
        std::map<int, std::shared_ptr<value>> height_map;
    
        mountain& find_by_name(std::string s) {return name_map[s]->value;}
        mountain& find_by_height(int height h) {return height_map[s]->value;}
        void erase_by_name(std::string s) {
            value& v = name_map[s];
            name_map.erase(v.name_iter);
            height_iter.erase(v.height_iter); //note that this invalidates the reference v
        }
    };
    

    但是,在您的情况下,您可以做得比这个 O(logn) 更好,因为您只需要“最新的”和“次高的”。为了使“pop最高”快速,您需要一种快速检测下一个最高值的方法,这意味着需要在插入时预先计算。要找到相对于其他位置的“高度”位置,您需要某种地图。要使“pop most recent”快速,您需要一种快速的方法来检测下一个最近的,但计算起来很简单。我建议创建一个映射或节点堆,其中键是查找最大值的值,值是指向下一个最近值的指针。这为您提供 O(logn) 插入,O(1) 查找最新,O(1) 或 O(logn) 查找最大值(取决于实现),以及通过任一索引擦除 ~O(logn)。

    【讨论】:

    • 感谢您提供额外的见解。
    • @KarolyHorvath:我认为最后一个节点的迭代器擦除将非常接近 O(1),但经过审查,复杂度确实是 log(n)。
    【解决方案4】:

    另一种方法是:-

    使用元素创建最大堆。通过这种方式,我们将能够在 O(1) 操作中获取/删除最大元素。 除此之外,我们可以维护一个指向最后推送元素的指针。据我所知,堆中的删除可以在 O(log n) 中构建。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-03-04
      • 1970-01-01
      • 2018-05-29
      • 2015-09-27
      • 2011-11-24
      • 1970-01-01
      • 2011-11-30
      • 2016-01-24
      相关资源
      最近更新 更多