归并排序
时间复杂度O(nlogn)
操作思路:
归并排序应用了分治思想,分为分和治两部分。先是分,把一个数组平均分为很多小份,分到最小时数组里只有一个元素,此时数组是有序的。之后是治,将分开的两个有序数组合并回去。
代码
递归实现
代码解释
MergeSort是分的函数,参数为待排序数组和两个数组下标,这两个下标是排序的具体区间。对于整个未排序数组来说,两个下标是数组的最小下标和最大下标。对于分开的小数组来说,两个下标是小数组元素在大数组中的具体位置。当左下标等于右下标时,数组中只有一个元素,相当于有序数组,直接返回即可,这是整个数组被分割为最小的小数组时的情况。然后是多次递归将数组分割,最后执行合并操作。
ExdernalSort是治的函数,需要传入整个数组和三个下标。这个函数将L到mid和mid+1到r两个数组合并起来。用newnums这个数组合并两个小数组,然后把原数组更新为newnums,和并的具体操作就是遍历newnums,不断把两个数组的较小值或较大值放入newnums,这样就完成了合并。
为什么要借助新数组合并而不原地合并?原地合并大致有两种思路,以递增排序为例。一是将较小元素放在最前面,但这样会导致较小元素前面的元素都要向后移动一位来为这个元素腾出空间。二是直接交换较小元素和它排序后所在位置的元素,但这样有可能破坏两个小数组有序的性质。
递归实现的归并排序可以抽象为一颗深度为logn的二叉树,多层递归需要栈空间,空间复杂度为O(n+logn),可以优化为迭代实现
迭代实现
代码解释
ExternalSort没有变化。MergeSortr这个函数里,s是合并的间隔,递归实现是将数组一直对半分割直到每个子数组的长度为1,那么迭代实现s就从1开始一直乘2,直到s大于这个数组的长度。合并时两两合并,所以在一般情况下,是将i到i+s-1和i+s到i+2*s-1这两个区间合并。i+2*s-1<=r,移项就可以获得while循环的条件i<=r-2*s+1。当i+2*s-1>r,也就是从i开始到r的距离不足2*s了就不能这样合并,当i到r的距离大于s小于2*s,即i<r-s+1时将i到i+s-1和i+s到r这两个区间合并就可以了。当i到r的距离小于s时,只剩下了一个子数组,无法合并。
快速排序
代码
图一为霍尔法,图二为快慢指针法,图三为三数取中法是普通快排的优化,可以防止数据是递增或递减使时间复杂度到O(n^2)