归并排序的时间复杂度为O(nlogn),属于稳定排序(需要额外空间)。
其是一种采用分治法的典型的算法应用。
该排序算法的主体思想:
1. “分治”:不断将当前数组从中点处分割(直至末尾被分割的数组只有1个元素),不断将复杂的大问题拆解为易于处理的小问题;
2. “合并”:当拆分后的子数组长度为1时,开始由底向上进行合并操作(将当前子数组排序后合并),直至合并至原数组,此时完成了排序;
示例代码1(返回新数组):
1 /** 2 * 归并排序 3 * @param {number[]} arr - 待排序数组 4 * @return {number[]} 排序后的新数组 5 */ 6 function mergeSort(arr) { 7 const len = arr.length; 8 if (len < 2) return arr; 9 // --- “分” 10 const m = Math.floor(len / 2); 11 const left = arr.slice(0, m); 12 const right = arr.slice(m); 13 // --- “合” 14 return merge(mergeSort(left), mergeSort(right)); 15 } 16 17 /** 18 * 合并数组(排序) 19 * @param {number[]} A - 子数组(左) 20 * @param {number[]} B - 子数组(右) 21 * @return {number[]} 合并且排序后的数组 22 */ 23 function merge(A, B) { 24 const merged = []; 25 while (A.length > 0 && B.length > 0) { 26 if (A[0] <= B[0]) { 27 merged.push(A.shift()); 28 } 29 else { 30 merged.push(B.shift()); 31 } 32 } 33 34 if (A.length > 0) { 35 merged.push(...A); 36 } 37 else if (B.length > 0) { 38 merged.push(...B); 39 } 40 41 return merged; 42 }
示例代码2(修改原数组):
1 /** 2 * 归并排序 3 * @param {number[]} nums - 待排序的数组 4 * @param {number} l - 左端点索引 5 * @param {number} r - 右端点索引 6 */ 7 function mergeSort(nums, l, r) { 8 // 终止条件 9 if (l >= r) return; 10 // 分治 11 const m = ((r - l) >> 1) + l; 12 mergeSort(nums, l, m); 13 mergeSort(nums, m + 1, r); 14 // 合并 15 // 拷贝nums的[l, r]区间内的元素,对应到tmp的[0, r - l]; 16 const tmp = nums.slice(l, r + 1); 17 // i 指向tmp数组中左半部的起点 18 let i = 0; 19 // j 指向tmp数组中右半部的起点 20 let j = m - l + 1; 21 // 对nums[l, r]区间的元素进行赋值重排,参照tmp的数据备份 22 for (let k = l; k <= r; k++) { 23 // 如果当前区间内的左半部已走完 24 if (i === m - l + 1) { 25 nums[k] = tmp[j++]; 26 } 27 // 如果当前区间内的右半部已走完 或 左半部当前索引的元素小于等于对应右半部的 28 else if (j === r - l + 1 || tmp[i] <= tmp[j]) { 29 nums[k] = tmp[i++]; 30 } 31 // 右半部当前索引的元素小于对应左半部的 32 else { 33 nums[k] = tmp[j++]; 34 } 35 } 36 }
End