选择排序与数组和链表之间的区别?

时间:2013-08-08 23:28:43

标签: c++ arrays algorithm list selection-sort

我想知道使用带有findmax的数组和带有findmin的链表的选择排序背后的逻辑是什么。两者的最佳和最差情况?

1 个答案:

答案 0 :(得分:1)

TL; DR

选择排序通常很糟糕。合并排序通常很好,但可以通过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_mergeO(N log N)函数提供了std::sort()最坏情况复杂度的混合算法。通常它们实现为IntroSort,它将递归随机数据快速排序与堆排序和插入排序混合在一起,具体取决于各种递归排序子范围的大小。

基于节点的容器可以从成员函数std::stable_sort

中受益

基于比较的排序算法大量使用迭代器指向的复制或交换基础数据。对于常规容器,交换基础数据是您可以做的最好的。对于基于节点的容器,例如O(N log N)sort(),您更愿意std::list:仅重新排列节点指针并避免复制潜在的大量数据。但是,这需要了解迭代器之间的连接。

这就是std::forward_listsplice都有成员函数 std::list的原因:它们具有相同的std::forward_list最坏情况复杂度,但利用容器的基于节点的字符。