归并排序的特点是: 先拆分, 再排序。
而使用柱状递归树图可以让你非常清晰地感受到归并排序的这个特点。
1 柱状递归树图
先上一张归并排序的效果图快速预览下
递归的本质就是树!
这张图中每一个柱状图都表示一次递归后的结果, 我们可以非常直观地看到递归的模样!
希望看完本文后, 再提起归并排序时, 首先出现在你脑海里的,会是一个柱状递归树的动画 : )
下面先说归并排序的递归实现方式: 先递归二分, 再排序合并。
2 递归实现
let data = [13, 11, 7, 4, 9, 8, 15, 6, 5, 3, 14, 2, 10, 1, 16, 12]function mergeSortSplit(array) {
if ('还剩下1个数字时') return
let mid = '先求array中间值'
mergeSortSplit('array左半部分')
mergeSortSplit('array右半部分')
// 先忽略排序合并
// mergeLeftAndRight('array左半部分', 'array右半部分')
}3 排序合并
排序合并 顾名思义就是先排序 后合并。
这一步也是归并排序的重头戏,实现归并排序时我们也会花更多的精力在这一步。
我们先来看看最简单的情况:两个长度一的数组怎么合并为一个有序数组。
比较小的元素先移到上层数组中:
最后完成排序合并,可以看到新合并的数组是一个有序数组:
接下来看看复杂点的情况,两个长度为4的数组合并为一个数组:
通过下面这个简单的动画演示,我们可以一窥究竟。
详细的代码实现我们在下次更新再进行讲解。这里先大概说下逻辑,我们可以把左右两个数组想象成左右两个指针。两个指针一开始都是指向数组的初始位置0,然后两个指针所指向的元素开始比较大小,较小的那个数会移到上层数组中,并且该数字所在的指针向右移动一格,依次类推。
现在我们在上面的递归函数里加入排序合并这步操作。
function mergeSortSplit(array) {
if ('还剩下1个数字时') return
let mid = '先求array中间值'
mergeSortSplit('array左半部分')
mergeSortSplit('array右半部分')
// 排序&合并
mergeLeftAndRight('array左半部分', 'array右半部分')
}于是就变成了下面这样,是不是和你想象中的一样呢?这里只放左半部分的动画。
递归二分 + 排序合并,这就是归并排序算法完整的递归实现。
最终排序效果图:
4 非递归实现
归并排序还可以使用非递归的方式实现,也就是自底向上, 先遍历拆分, 再排序合并。
非递归的实现方式,采用自底向上的思路。
对数组进行
1个一组进行拆分-排序合并;
2个一组进行拆分-排序合并;
4个一组进行拆分-排序合并;
8个一组的方式进行拆分......
最终的排序结果:
5 后续
本文的动画使用的是d3.js,如果对本文的动画代码感兴趣,请关注公众号"字节武装"。