归并排序
这种算法采用了分而治之的思想:
分割:把未排序的列表划分为 n 个子列表,每个包含一个元素(只有一个元素的列表被认为是有序的)。
合并:不停地合并子列表生成新的已排序列表,直到最后合并为一个已排序的列表。
两个数组合并时,只需要比较二个数组的第一个数,谁小就先取谁,取了后就在对应数列中删除这个数。然后再进行比较,如果有数列为空,那直接将另一个数列的数据依次取出即可。
function merge(left,right){ const temArr = []; // 不停的比较剩下未排序的两个数组 while(left.length && right.length){ if(left[0]<=right[0]) temArr.push(left.shift()); else temArr.push(right.shift()); } // 左边或右边可能还有剩余的元素,剩余的元素肯定比之前的元素大,合并即可 return temArr.concat(left,right); }
具体的实现方式有两种
1. 递归法(Top-down)
先将数组拆分到不可分割为止,然后再进行组合、排序,直到资料全部排序完成。
function mergeSort_TopBottom(arr){ //如果数组元素只有1个或0个,则认为是有序数组 if(arr.length <=1) return arr; //将数组对半劈开,直到只有1个元素为止 const middle = Math.floor(arr.length/2); const leftArr = mergeSort(arr.slice(0,middle)); const rightArr = mergeSort(arr.slice(middle)); return merge(leftArr,rightArr); }
使用递归可以很清楚的描述算法过程,缺点是函数频繁地自我调用。长度为n的数组最终会调用mergeSort_TopBottom()函数 2n-1次,这意味着一个长度超过1500的数组会在Firefox上发生栈溢出错误。因此实际开发中应该使用迭代来实现同样的功能
2. 迭代法(Bottom-up)
![](https://img2022.cnblogs.com/blog/1155527/202204/1155527-20220420100315083-888591757.gif)
function mergeSort_BottomUp(datas) { const len = datas.length; /* 按2的倍数对整个数组标段 */ for (let size = 1; size < len; size = 2 * size) { console.log(`------------size:${size}------------------------------`) //循环每个标段 for (let start = 0; start < len; start = start + 2 * size) { console.log(`----start:${start}-------`) let left = datas.slice(start,start+size),right = datas.slice(start+size,start+size+size); const sortedDatas = merge(left,right); console.log(`分段排序后的子数组:${sortedDatas}`); //用分段中的值替换原数组中对应下标的值 for (let k=0;k<sortedDatas.length;k++){ console.log(`原数组下标:${k+start},值:${datas[k+start]},替换为:${sortedDatas[k]}`) datas[k+start] = sortedDatas[k]; } } } }
在处理分过程中递归调用两个分的操作,所花费的时间为2*T(n/2)
,合的操作时间复杂度则为O(n)
,因此可以得到以下公式:
总的执行时间 = 2 × 输入长度为n/2
的sort
函数的执行时间 + merge
函数的执行时间O(n)
当只有一个元素时,T(1) = O(1)
如果对T(n) = 2 * T(n/2) + O(n)
进行左右 / n的操作,得到 T(n) / n = (n / 2) * T(n/2) + O(1)
现在令 S(n) = T(n)/n
,则S(1) = O(1)
,然后利用表达式带入得到S(n) = S(n/2) + O(1)
所以可以得到:S(n) = S(n/2) + O(1) = S(n/4) + O(2) = S(n/8) + O(3) = S(n/2^k) + O(k) = S(1) + O(logn) = O(logn)
综上可得,T(n) = n * log(n) = nlogn
关于归并排序的稳定性,在进行合并过程,在1个或2个元素时,1个元素不会交换,2个元素如果大小相等也不会交换,由此可见归并排序是稳定的排序算法