微信公众号:bigsai
前言在排序中,我们可能大部分更熟悉冒泡排序、快排之类。对归并排序可能比较陌生。然而事实上归并排序也是一种稳定的排序,时间复杂度为O(nlogn).
归并排序是基于分治进行归并的,有二路归并和多路归并.我们这里只讲二路归并并且日常用的基本是二路归并。并且归并排序的实现方式有递归形式和非递归形式。要注意其中的区分(思想上没有大的区别,只是划分上会有区分后面会对比)。
并且归并排序很重要的一个应用是求序列中的逆序数个数。当然逆序数也可以用树状数组完成,这里就不介绍了。
归并排序(merge sort)归并和快排都是基于分治算法的。分治算法其实应用挺多的,很多分治会用到递归,也有很多递归实现的算法是分治,但事实上分治和递归是两把事。分治就是分而治之。因为面对排序,如果不采用合理策略。每多一个数就会对整个整体带来巨大的影响。而分治就是将整个问题可以分解成相似的子问题。子问题的解决要远远高效于整个问题的解决,并且子问题的合并并不占用太大资源。
至于归并的思想是这样的:
第一次:整串先进行划分成1个一个单独,第一次是一一(12 34 56---)归并成若干对,分成若干2个区间.归并完(xx xx xx xx----)这样局部有序的序列。
第二次就是两两归并成若干四个(1234 5678 ----)每个小局部是有序的。
就这样一直到最后这个串串只剩一个,然而这个耗费的总次数logn。每次操作的时间复杂的又是O(n)。所以总共的时间复杂度为O(nlogn).
对于分治过程你可能了解了,但是这个两两merge的过程其实是很重要的。首先我们直到的两个序列都是有序的。其实思想也很简单,假设两个串串为 3 5 7 8和2 6 9 10进行归并操作。我们需要借助一个额外的数组team[8]将两个串串有序存进去就行。而流程是这样的:
在这里插入图片描述非递归的归并
正常归并的代码实现都是借助递归的。但是也有不借助递归的。大部分课本或者考试如果让你列归并的序列,那么默认就是非递归的,比如一个序列9,2,6,3,8,1,7,4,10,5序列的划分也是这样的。
第二次结束:{2,3,6,9}{1,4,7,8}{5,10}
第三次结束:{1,2,3,4,6,7,8,9}{5,10}
第四次结束:{1,2,3,4,5,6,7,8,9,10}
递归的归并
在代码实现上的归并可能大部分都是递归的归并。并且递归和分治整在一起真的是很容易理解。递归可以将问题分解成子问题,而这恰恰是分治所需要的手段。而递归的一来一回过程的来(分治)回(归并),一切都刚刚好。
而递归的思想和上面非递归肯定不同的,你可以想想非递归:我要考虑当前几个进行归并,每个开始的头坐标该怎么表示,还要考虑是否越界等等问题哈,写起来略麻烦。
而非递归它的过程就是局部—>整体的过程,而递归是整体—>局部—>整体的过程。
而递归实现的归并的思想:
int mid=(left+right)/2;//找到中间节点
if(left<right)//如果不是一个节点就往下递归分治
{
mergesort(array, left, mid);//左区间(包过mid)进行归并排序
mergesort(array, mid+1, right);//右区间进行归并排序
merge(array, left,mid, right);//左右已经有序了,进行合并
}
}
同样是9,2,6,3,8,1,7,4,10,5这么一串序列,它的递归实现的顺序是这样的(可能部分有点问题,但是还是有助于理解的):