对已经排序了 n 个第一个元素的向量进行排序?
Posted
技术标签:
【中文标题】对已经排序了 n 个第一个元素的向量进行排序?【英文标题】:Sort a vector in which the n first elements have been already sorted? 【发布时间】:2014-03-02 18:33:39 【问题描述】:考虑N
元素中的std::vector
v
,并认为n
的第一个元素已经用n < N
排序,而(N-n)/N
非常小:
有没有比使用完整的std::sort(std::begin(v), std::end(v))
更快地使用 STL 算法对这个向量进行排序的聪明方法?
编辑:澄清:(N-n)个未排序的元素应插入已排序的第一个元素内的正确位置。
EDIT2:额外问题:以及如何找到 n ? (对应于第一个未排序的元素)
【问题讨论】:
EDIT2 的答案是std::is_sorted_until
就地执行是否是一项要求?好像是隐含的,你却没有明说。
如果N-m
很小,那么可以使用插入排序。是的,它被认为是一种糟糕的排序算法,但事实是当输入几乎排序时它是有效的。或者,如果您可以节省一些额外的内存,您可以将未排序的元素移动到不同的向量中,对其进行排序并进行合并排序,在这种情况下,您可能希望从末尾而不是开头进行排序。
std::sort 有什么问题?您不必为起始元素提供迭代器。您不妨提供第 200 个元素的迭代器(如vec.begin() + 199
)
C++ 标准库没有一些可用的自适应排序算法? (例如 timsort)否则您可以使用它们对整个数组进行排序,算法将自动避免在第一部分浪费时间。
【参考方案1】:
仅对其他范围进行排序,然后使用std::merge。
【讨论】:
merge 需要分离输入和输出,这里使用的正确算法是inplace_merge
。
我知道,但是 OP 没有告诉他是否想把它放在适当的位置。
虽然您的帖子不是仅链接答案的经典案例,因为它至少包含一个函数的名称,但这仍然只是略高于阈值。请至少提供一个简约的使用示例,最好与 OPs 问题相关。我知道很多人对此表示赞同,但如果链接被删除,他们也会赞同吗?【参考方案2】:
void foo( std::vector<int> & tab, int n )
std::sort( begin(tab)+n, end(tab));
std::inplace_merge(begin(tab), begin(tab)+n, end(tab));
用于编辑 2
auto it = std::adjacent_find(begin(tab), end(tab), std::greater<int>() );
if (it!=end(tab))
it++;
std::sort( it, end(tab));
std::inplace_merge(begin(tab), it, end(tab));
【讨论】:
我添加了一个问题(见 EDIT 2) @Vincent 你应该打开一个新问题,无论如何,这是你这次的答案 你可以用std::is_sorted_until
代替std::adjacent_find
同样,标准inplace_merge
正式地是“假”、“模拟”的就地合并。该标准允许它在异地工作并允许它执行完整的排序。坦率地说,我不记得曾经见过一个就地且不是完整类型的诚实实现。该算法太复杂,无法打扰。如果你知道,请告诉我。【参考方案3】:
最佳解决方案是独立排序尾部,然后执行就地合并,如此处所述
http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.22.5750
算法相当复杂,通常被认为“不值得努力”。
当然,对于 C++,您可以使用现成的 std::inplace_merge
。但是,该算法的名称具有很大的误导性。首先,不能保证std::inplace_merge
确实就地工作。而且当它实际上就位时,不能保证它不会作为一个成熟的排序来实现。在实践中,它归结为尝试它并查看它是否足以满足您的目的。
但是,如果您真的想让它就地并且正式地比完整排序更高效,那么您将不得不手动实现它。 STL 可能对一些实用算法有所帮助,但它不提供任何“仅调用标准函数”类型的可靠解决方案。
【讨论】:
从标准中给出的复杂性范围来看,似乎符合标准的inplace_merge
甚至不允许使用最佳算法,就执行的比较而言,这比天真的方法要昂贵得多。但是当然,天真的方法需要分配一个临时缓冲区。【参考方案4】:
对N - n
最后一个元素使用插入排序:
template <typename IT>
void mysort(IT begin, IT end)
for (IT it = std::is_sorted_until(begin, end); it != end; ++it)
IT insertPos = std::lower_bound(begin, it, *it);
IT endRotate = it;
std::rotate(insertPos, it, ++endRotate);
【讨论】:
【参考方案5】:Timsort 排序算法是 Pythonista Tim Peters 开发的一种混合算法。它充分利用了数组内任何地方已排序的子段,包括开头。虽然如果您确定特别是前 n 个元素已经排序,您可能会找到更快的算法,但该算法应该对所涉及的整体问题类别有用。***将其描述为:
该算法找到已排序的数据子集,并使用该知识对剩余部分进行更有效的排序。
用蒂姆·彼得斯自己的话说,
它对许多人都有超自然的表现 各种偏序数组(少于 lg(N!) 所需的比较,以及 少至 N-1),但与 Python 先前高度调整的样本排序一样快 在随机数组上混合。
详细信息在in this undated text document by Tim Peters 中进行了描述。这些示例是用 Python 编写的,但即使对于不熟悉它的语法的人来说,Python 也应该是可读的。
【讨论】:
【参考方案6】:使用 std::partition_point(或 is_sorted_until)来查找 n。然后如果 n-m 很小,做一个插入排序(线性搜索+std::rotate)。
【讨论】:
预计会详细说明 SO。请至少提供单行使用示例,以便将其传递给 OPs 代码。【参考方案7】:我假设您的问题有两个目的:
提高运行时间(使用巧妙的方法) 不费吹灰之力(仅限于 STL)考虑到这些目标,我强烈建议您不要进行这种特定优化,除非您确信付出的努力是值得的。 据我记得,std::sort() 实现了快速排序算法,该算法在预排序输入上几乎与确定输入是否/多少排序一样快。
您可以尝试将数据结构更改为排序/优先队列,而不是干预 std::sort。
【讨论】:
快速排序在预排序输入上并不快。对于某些枢轴选择(在现代实现中不太可能),它可能非常慢,但快速排序的最佳性能是O(n log n)
。
你是对的。事实上,我搞砸了:排序数据可能是快速排序的最坏情况(取决于不太可能选择的枢轴)。但是,如果数据总是排序但对于最后几个附加元素,则没有需要检查第一个元素。您可以简单地让数据结构在添加最后一个元素时对其进行排序。在此我假设向量是连续建立的,因为这就是这种情况通常发生的方式。以上是关于对已经排序了 n 个第一个元素的向量进行排序?的主要内容,如果未能解决你的问题,请参考以下文章