【问题标题】:Difference between selection sort with array and linked list?带数组和链表的选择排序之间的区别?
【发布时间】:2013-08-10 20:50:25
【问题描述】:

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

【问题讨论】:

  • 谷歌。是你的朋友。
  • 在链表上使用除mergesort() 之外的任何内容的开发人员会被解雇,因此他必须在最坏的情况下考虑这一点。除此之外,请参阅上面的评论。
  • @WhozCraig 使用链表的开发者被解雇了
  • @aaronman 几乎不可能反对这一点,所以我什至不会打扰 =P。
  • @Rollie 由于间接导致的缓存未命中将是唯一不明显的,链表在内存中不连续,这是灾难的根源

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


【解决方案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);
        }
}

在长度为Nstd::arraystd::list 上,这具有O(N^2) 复杂性:外部循环处理所有N 元素,而对std::min_element 的内部调用也具有线性复杂性,即给出整体二次缩放.

但是,由于基于比较的排序可以像 O(N log N) 一样便宜地完成,因此对于大型 N,这通常是不可接受的缩放。正如@EJP 所提到的,选择排序的一个可取之处是,虽然它进行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);
}

在长度为Nstd::arraystd::list 上,这具有O(N log N) 复杂性:递归深度为O(log N)(因为每次将间隔减半)和对@987654345 的调用@ 具有线性复杂性,这使得整体 O(N log N) 可缩放。

但是,几乎任何严肃的排序算法竞争者都不会通过比较次数来显着区分自己,而是通过访问和放置数据的相关开销来区分自己。这种优化只能通过比通用版本更多的知识来完成。

随机存取容器可以从混合算法中受益

使用混合算法可以更便宜地对具有随机访问迭代器的容器进行排序。标准库中的std::sort()std::stable_sort 函数提供了O(N log N) 最坏情况复杂度的混合算法。通常,它们以 IntroSort 的形式实现,它将递归随机枢轴快速排序与堆排序和插入排序混合在一起,具体取决于各种递归排序子范围的大小。

基于节点的容器可以受益于成员函数sort()

基于比较的排序算法大量使用复制或交换迭代器指向的基础数据。对于常规容器,交换底层数据是您能做的最好的事情。对于std::liststd::forward_list 等基于节点的容器,您更喜欢splice:仅重新排列节点指针并避免复制潜在的大量数据。但是,这需要了解迭代器之间的连接。

这就是std::liststd::forward_list 都具有成员函数 sort() 的原因:它们具有相同的O(N log N) 最坏情况复杂性,但利用节点-基于容器的字符。

【讨论】:

  • 选择排序进行 N^2/2 次比较,但它有一个重要的属性,即它只进行 N 次交换。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-01-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多