【问题标题】:Get random element and remove it获取随机元素并将其删除
【发布时间】:2012-03-02 09:18:20
【问题描述】:

问题:我需要为一个容器获取一个随机元素,并从该容器中删除它。容器不需要分类。 我不在乎订单。

  • 向量可以在O(1) 中获取随机元素,但只能在O(N) 中删除它
  • 列表删除O(1)中的元素但只能获取O(N)中的随机元素

所以我想到了制作一个自定义向量的想法,它允许您按其索引删除任何元素,O(1)+ 复杂度。 这个想法是交换最后一个元素和要删除的元素,然后pop_back()。 如果您需要删除最后一个元素 - 只需 pop_back()。 向量的顺序不会相同,但您会得到一个快速删除方法。

据我所知,双端队列的索引访问速度较慢,删除复杂性比我的解决方案更差,但我不是 100% 确定。

我很好奇O(1)O(logN) 中是否存在随机访问和元素删除的数据结构,按索引或按值按mb?

【问题讨论】:

  • 为什么需要为此制作自定义向量?只需将元素交换到最后并从那里删除它?这不需要是一个特殊的类。
  • 如果你想保持元素的顺序,我给了你一个解决方案,那就是 O(log N) 复杂度。
  • @NicolBolas 他找到了一个解决方案(不知道为什么他想要一个新的集合),但询问是否有 O(1) 或 O(log N) 解决方案。我们知道有一个恒定的时间解决方案(正如他自己发现的那样),因此 O(log N) 只能表示保持秩序的解决方案。

标签: c++ performance data-structures vector big-o


【解决方案1】:

复杂度为 O(n)

vec.erase(vec.begin() + randomIdx); randomIdx 将介于 0 和 vec.size()-1

之间

如果您想要 O(1) 复杂度,您可以使用列表容器,或者将元素与最后一个元素交换并删除它。 (正如其他人所提到的)

【讨论】:

  • 真的吗?为什么会这样?这实际上只是一个指针重新分配,不是吗?
  • @guitarflow :因为索引 n 之后的每个元素都必须重新定位。
  • 如何重新分配指针?它是一个数组,而不是指针数组。要从数组中间删除一个元素,您必须将其后的每个元素向下移动一个。
  • 好的,我还没有意识到这一点。我认为向量会以链表的方式组织。
  • @guitarflow : std::vector<> 必须对其元素进行连续存储,因此它实际上必须以数组的形式实现。 (如果它是一个链表,为什么还有std::list<>?)
【解决方案2】:

您有解决方案,而且看起来非常好。用 C++ 编写它的惯用方式不是创建另一个类( don't inherit from std::vector),而只是编写一个函数:

template <typename T>
void remove_at(std::vector<T>& v, typename std::vector<T>::size_type n)
{
    std::swap(v[n], v.back());
    v.pop_back();
}

用法:

remove_at(v, 42);

这提供与std::swap&lt;T&gt; 相同的异常保证。

现在,如果您想返回对象,并且可以访问 C++11 编译器,则可以通过以下方式进行。困难的部分是在所有情况下都提供基本的异常保证:

template <typename T>
T remove_at(std::vector<T>&v, typename std::vector<T>::size_type n)
{
    T ans = std::move_if_noexcept(v[n]);
    v[n] = std::move_if_noexcept(v.back());
    v.pop_back();
    return ans;
}

确实,如果在移动操作期间引发异常,您不希望向量处于无效状态。

【讨论】:

  • 我想你的意思是v.pop_back()
  • 我还需要归还我正在移除的东西,但你的权利。我会那样做。谢谢。
  • 内容将是您的矢量的内容,删除了元素但顺序尚未保持。
  • @CashCow:对象的顺序不是问题,除非我理解错误。
  • 这将调用 n 的元素析构函数两次。如何避免这种情况?
【解决方案3】:

是的,有一个解决方案,一个平衡良好的二叉树。

每个节点一个,您需要存储每侧的节点数。从这里找到第 n 个元素是 O(log N)。

删除第 n 个元素也是 O(log N),因为您必须向上遍历树以更正所有计数。任何重新平衡最多也是 O(log N)。

如果没有叶子节点比另一个节点深 2 个节点,则认为树是平衡良好的。查找 AVL 树以获得 rabalancing 算法。

如果标准库“开放”使用用于 std::set 和 std::map 的树作为自定义树的公共接口,那实际上会很好。

【讨论】:

  • @CashCow:你说得对,我看错了。我删除了我的评论并投了反对票。
  • 你描述的 std::set。如果元素的顺序不能改变,那么如果元素的数量预计会增长,这可能是正确的解决方案。如果你只有几个元素,std::vector + erase 也可以(并且可能比set 更快)。
  • 不,std::set 要求元素被排序且唯一。这里不需要任何一个。当然,如果 map 在 O(log N) 时间内找到第 n 个元素(并且应该),并且如果您为每个元素创建一些额外的“键”,它可能会与 map 一起使用,从而导致元素插入到您想要的位置。我正在研究算法的观点,即您可以随机访问删除(或插入)并保持 O(log N) 复杂度的顺序。
  • 是的,我后来想通了。似乎确实没有办法访问 O(log n) 中集合的第 n 个元素,因为它应该是可能的。尽管如此,我不会在这里重新实现 STL 树,并尝试找到另一种解决方案,而不是按索引访问元素。
  • 另见en.wikipedia.org/wiki/AA_tree,这似乎比RB树更容易实现
猜你喜欢
  • 1970-01-01
  • 2019-07-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-04-18
  • 1970-01-01
相关资源
最近更新 更多