计算之魂共读 | task03

154 阅读4分钟

这一节我们就讨论一些和排序算法相关的问题。通过这些问题,我们可以看出一些让计算机少做无用功的门道

要理解它们,关键是要掌握两个计算机科学的精髓——递归和分治。在考虑计算机问题时用计算思维,那些算法会很容易理解。

1.4.1 直观的排序算法

选择排序(Selection Sort)

先找出第一大的,再找出第二大的,以此类推。

方法:

假如有N个元素。

从头遍历到尾比较相邻的两个元素,将更大的元素置于后面。

从头遍历到倒数第二个元素...

即扫描N-1次,每次少扫描一个元素。

时间复杂度为

插入排序(Insert Sort)

想象:扫描一遍,插入相应的位置。

实际:找到插入的位置(优化:二分)+挪动元素

时间复杂度为

上述这两种排序算法的问题是做了很多次无谓的比较和数据的移动(选择排序中位置的互换也是一种数据移动)。我们不妨以选择排序为例,看看哪些操作是在做无用功。

首先是比较,如果已经比较过,,就没有必要比较x,z。

其次是无谓的位置互换,,但z会被先换到第一位。

1.4.2有效的排序算法的效率来自哪里

img

平均时间复杂度为

归并排序(Merge Sort)

假设序列a[1...N]前后两部分是排好序的,它们分别存储在B、C两个子序列中,每个子序列都有N/2个元素,需要使用归并把两个有序的子序列合并起来。

具体方法是利用三个指针p,q,r,分别指向各自序列当前元素的位置。当q或r到达末尾时,若另一序列还有剩余,直接放在a末尾即可。

利用递归完成B、C子序列的排序,结束条件是子序列只剩一个元素。递归次数是。每一次的计算量是N。

优化

在那个合并的过程中,获得当前最小的元素只需要让两个可能的最小元素进行一次比较,因为我们利用了X<Y Y<Z,则一定有X<Z这样的逻辑。

而在选择排序中,则需要让一个元素和几乎所有的元素比较,即便那些比较是显而易见不用进行的。这便是归并排序省时间的根本原因。

从这个例子可以看出,寻找更优化的算法的精髓就在于少做无用功。

缺点

它需要使用额外的存储空间保留中间结果。

因为当我们把BC这两个子序列合并为A序列时,需要额外的O(N)大小的存储空间。当然,在对BC子序列进行归并排序时不需要有这种担心,因为在整个计算过程中只需要保留一份完整的数据即可,A序列的空间可以拿来做存储。也就是说每一次归并,归并前的数据使用一组O(N)大小的存储空间,归并后的数据使用同样大小的另一组空间,两组存储空间来回替换使用。

堆排序(Heap Sort)

一种计算时间复杂度是O(NlogN ),同时又不占用额外空间(就地特性in place characteristic) 的排序算法

快速排序(Quick Sort)
特点

稳定性:两个相同的元素在排序前后相对位置维持原有的次序

不同于归并排序算法,快速排序和堆排序本身不具有稳定性,因此保留归并排序有时还是有意义的。

快速排序还有一个明显的缺点,那就是虽然它的平均时间复杂度是O(NlogN),但是在极端的情况下时间复杂度是O(N2)。

总结

计算机科学领域做事的两个原则:

首先,要尽可能地避免那些做了大量无用功的方法,比如选择排序和插入排序,一旦不小心采用了那样的方法,带来的危害有时是灾难性的;

其次,接近理论最佳值的算法可能有很多种,除了单纯考量计算时间外,可能还有很多考量的维度,因此有时不存在一种算法就比另一种绝对好的情况,只是在设定的边界条件下,某些算法比其他的更适合罢了。