Get random element and remove it
问题:我需要为一个容器获取一个随机元素,并从该容器中删除它。容器不需要排序。我不在乎订单。
- 矢量可以在O(1)中得到随机元素,但只能在O(N)中删除。
- list删除O(1)中的元素,但只能得到O(N)中的随机元素。
因此,我想出了一个主意,制作一个定制的向量,允许您通过它的索引删除具有O(1)+复杂性的任何元素。其思想是交换最后一个元素和要删除的元素,然后交换pop_back()。如果您需要删除最后一个元素-仅限于pop_back()。向量的顺序将不相同,但您会得到一个快速删除方法。
据我所知,deque的索引访问速度较慢,删除复杂性比我的解决方案更差,但我不能百分之百确定。
我很好奇,在O(1)或O(logN)中是否存在随机访问和元素删除的数据结构(按索引)或mb(按值)?
- 为什么需要为此制作自定义矢量?只需将元素交换到末尾,然后从那里移除它?这不需要是一个特殊的班级。
- 如果您想保持元素的顺序,我已经为您提供了一个解决方案,这将是O(log n)复杂性。
- @尼古拉斯找到了一个解决方案(不知道为什么他想要一个新的收集它),但问是否有一个O(1)或O(log n)的解决方案。我们知道有一个不变的时间解(他自己发现的),所以O(log n)只能表示一个维持顺序的解。
- 语言不可知论:stackoverflow.com/questions/311703/…
你有解决办法,看起来很好。在C++中编写它的惯用方法不是创建另一个类(并且请不要从EDCOX1(0)中继承),而是写一个函数:
1 2 3 4 5 6
| 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();
} |
用途:
这提供了与std::swap相同的例外担保。
现在,如果你想返回这个对象,并且你可以访问C++ 11编译器,你可以这样做。困难的部分是在所有情况下提供基本例外担保:
1 2 3 4 5 6 7 8
| 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()。
- @丹尼尔:是的,修好了,谢谢。
- 我还需要归还我移走的东西,但你的权利。我会那样做的。谢谢。
- 内容将是删除元素后的向量内容,但顺序尚未保持。
- @现金奶牛:物品的顺序不是问题,除非我错误地理解了这个问题。
- @ AlexandreC。没错。
- 这将调用n的元素析构函数两次。如何避免这种情况?
是的,有一个解决方案,一个平衡良好的二叉树。
每侧需要一个节点来存储节点的数量。从这里,找到第n个元素是O(logn)。
删除第n个元素也是O(logn),因为您必须遍历回树以更正所有计数。任何再平衡最多也是O(log n)。
如果没有叶节点比另一个节点深2个节点,则认为树平衡良好。查找AVL树,得到一个均衡算法。
如果标准库"打开"使用std::set和std::map所用的树作为公共接口,以在自定义树中使用,实际上是很好的。
- 但你不需要无礼
- 所以你不是投反对票的人?
- @你说得对,我误读了。我删除了我的评论并投了反对票。
- 你所描述的是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 trees更容易实现。
- 我把这当作一个算法复杂性的问题,而不是C++中的编码练习。当set和map都使用二叉树实现时,如果实现是标准化的并可用的,那就更好了:好吧,不管怎样,树的接口使您能够遍历、添加节点和重新平衡,而不必自己编写这个逻辑。
具有O(N)复杂性
vec.erase(vec.begin()+随机化x);RandomDx将介于0和vec.size()-1之间
如果您想要O(1)复杂性,您可以使用列表容器,或者用最后一个容器交换元素,然后删除它。(如其他人所述)
- 那是O(n)删除。
- 真的?为什么会这样?实际上,这只是一个指针重新分配,不是吗?
- @guitarFlow:因为索引n之后的每个元素都必须重新定位。
- 如何重新分配指针?它是一个数组,而不是指针数组。要从数组中间删除一个元素,必须将每个元素向下移动一个。
- 好吧,我不知道。我以为向量会以链表的方式组织起来。
- @guitarFlow:std::vector<>的元素必须有连续的存储空间,因此必须以数组的形式有效地实现它。(如果是一个链表,为什么会有一个std::list<>呢?)
- @Ildjarn嗯,说得对。;)
- 将要移除的元素与向量中的最后一个元素交换将是恒定的时间,但不会保持顺序。我假设您希望保持其余元素的顺序。