[JS排序算法] - 4 - 合并排序

归并排序(英语:Merge sort,或mergesort),
是创建在归并操作上的一种有效的排序算法,效率为O(nlog  n)。
1945年由约翰·冯·诺伊曼首次提出。
该算法是采用分治法(Divide  and  Conquer)的一个非常典型的应用,
且各层分治递归可以同时进行。

解释

以上是 维基百科 关于 归并排序 的描述。其实就是 合并排序合并排序 也称 归并排序 。其合并排序的 核心点 就在于:

  • 将原数组拆分成若干个子数组,然后通过比较再进行合并

合并 合并 ,有 才有 , 有 才有 。所以 合并排序 首先要做的流程就是先将原数组 拆分 。原数组 拆分 后,再对其进行 合并 。那 拆分的粒度 是多少呢?或者说 拆成几个 元素?
因为原数组的状态是 无序 的,拆的过程中 无法判断大小顺序 。所以拆的时候,要把 原数组 按照 单个元素 进行 拆分 。拆分之后的每个 子数组中 只有 一个元素 ,此时的每个子数组都可以理解为是 有序状态
所以 这时候,问题就 转化 为了:
如何 合并 两个 有序数组 ,使之 合并 后的 新数组 状态仍 保持有序

首先实现如何合并两个有序数组

比如说,现在有一个 两个有序 数组组成的 无序数组 arr:

const arr = [1, 3, 5, 2, 4, 6];

对于它的前3项[1, 3, 5] 和后3项([2, 4, 6])。都是有序的状态。我们现在要将其合并为一个新数组,合并后的新数组也要保持有序状态。

首先对其进行拆分,因为它是由两个无序数组组成的。所以就根据两个有序数组的交界点对其进行拆分。也就是下标为3的位置

/**
 * 合并两个有序数组
 * @param {要合并的数组} arr
 * @param {左半边开始的位置} p
 * @param {左半边结束的位置 右半边开始的位置} q // 要拆分的位置
 * @param {右半边结束的位置} r
 */
function merge(arr, p, q, r){
    // 首先根据两个有序数组的交界点进行拆分
    let arr1 = arr.slice(p, q);
    let arr2 = arr.slice(q, r);
    
    // 添加哨兵: 各自向数组尾部添加最大值
    // 作用: 
    // 1 占位 2 减少下方for循环中的判断条件
    arr1.push(Number.MAX_SAFE_INTEGER)
    arr2.push(Number.MAX_SAFE_INTEGER)
    
    // 循环不变式 对原数组进行回写覆盖
    for(let k = p, i = 0, j = 0; k < r; k++){
        // p是下标0 初始时k=p=0 且k在不断递增
        // i指针默认0 指向arr1中将要写入到原数组中的位置
        // j指针默认0 指向arr2中将要写入到原数组中的位置
        // 分别比较两个有序数组 arr1 和 arr2 中的每个元素
        // 谁小 谁先回写到原数组中
        // 注意 每回写一次arr1或者arr2中的元素 要将i或j进行+1
        arr[k] = arr1[i] < arr2[j] ? arr1[i++] : arr2[j++]
    }
}

merge(arr, 0, 3, 5);
// 1,2,3,4,5,6

测试一下即可发现,完成了将两个有序数组合并为一个有序数组。
合并排序的核心实现了,那么接下来代码就比较简单了

JS实现合并排序
/**
 * @param  {无序数组}  arr
 * @param  {头始指针}  p
 * @param  {尾部指针}  r
 */
function  merge_sort(arr, p, r) {
    if (r - p < 2) return; // 数组中小于两个元素就不需要再拆分了

    // 向上取整 计算出要拆分的位置
    const q =  Math.ceil((p + r) / 2);

    // 递归拆分:
    // 左
    merge_sort(arr, p, q);
    // 右
    merge_sort(arr, q, r);

    // 最后合并:
    merge(arr, p, q, r);
}

// 同上
function merge(arr, p, q, r){
    let arr1 = arr.slice(p, q);
    let arr2 = arr.slice(q, r);
    
    arr1.push(Number.MAX_SAFE_INTEGER)
    arr2.push(Number.MAX_SAFE_INTEGER)
    
    for(let k = p, i = 0, j = 0; k < r; k++){
        arr[k] = arr1[i] < arr2[j] ? arr1[i++] : arr2[j++]
    }
}
测试一下
const _arr = [2, 6, 12, 67, 845, 87, 13, 678, 321, 12, 3, 1, 7, 8, 9];

merge_sort(_arr);

console.log(_arr);

/*
    [
        1, 2, 3, 6, 7, 8,
        9, 12, 12, 13, 67, 87,
        321, 678, 845
    ]
*/

你可能感兴趣的:(javascript,前端,排序学习)