我想知道使用带有findmax的数组和带有findmin的链表的选择排序背后的逻辑是什么。两者的最佳和最差情况?
答案 0 :(得分:1)
选择排序通常很糟糕。合并排序通常很好,但可以通过std::sort
为随机访问容器和基于节点的容器的成员函数sort()
进行改进。
考虑以下selection_sort
template<class ForwardIt, class Compare = std::less<typename std::iterator_traits<ForwardIt>::value_type>>
void selection_sort(ForwardIt first, ForwardIt last, Compare cmp = Compare())
{
for (auto it = first; it != last; ++it) {
auto const selection = std::min_element(it, last, cmp);
std::iter_swap(selection, it);
}
}
在长度为std::array
的{{1}}和std::list
上,这具有N
复杂性:外部循环处理所有O(N^2)
元素,内部调用N
也具有线性复杂度,这给出了整体二次比例。
但是,由于基于比较的排序可以像std::min_element
一样便宜地完成,因此对于大型O(N log N)
来说,通常是不可接受的缩放。正如@EJP所提到的,选择排序的一个兑换功能是虽然它进行N
比较,但它只进行O(N^2)
数据交换。但是,对于非常大的O(N)
,这种优于大多数N
排序算法的优势最终将被O(N log N)
比较成本所淹没。
考虑以下O(N^2)
merge_sort
在长度为template<class BiDirIt, class Compare = std::less<typename std::iterator_traits<BiDirIt>::value_type>>
void merge_sort(BiDirIt first, BiDirIt last, Compare cmp = Compare())
{
auto const N = std::distance(first, last);
if (N < 2) return;
auto middle = first + N / 2;
merge_sort(first, middle, cmp);
merge_sort(middle, last, cmp);
std::inplace_merge(first, middle, last, cmp);
}
的{{1}}和std::array
上,这具有std::list
复杂度:递归深度为N
(因为间隔被切入每次调用一半),对O(N log N)
的调用具有线性复杂度,从而提供整体O(log N)
缩放。
然而,几乎任何严重的排序算法竞争者都会将自己与显示的数量区别开来,而不是用于访问和放置数据的相关开销。这种优化只能通过比通用版本更多的知识来完成。
使用混合算法可以更加便宜地对具有随机访问迭代器的容器进行排序。标准库中的std::inplace_merge
和O(N log N)
函数提供了std::sort()
最坏情况复杂度的混合算法。通常它们实现为IntroSort,它将递归随机数据快速排序与堆排序和插入排序混合在一起,具体取决于各种递归排序子范围的大小。
std::stable_sort
基于比较的排序算法大量使用迭代器指向的复制或交换基础数据。对于常规容器,交换基础数据是您可以做的最好的。对于基于节点的容器,例如O(N log N)
或sort()
,您更愿意std::list
:仅重新排列节点指针并避免复制潜在的大量数据。但是,这需要了解迭代器之间的连接。
这就是std::forward_list
和splice
都有成员函数 std::list
的原因:它们具有相同的std::forward_list
最坏情况复杂度,但利用容器的基于节点的字符。