由mergeSort引发的一些思考

35次阅读

共计 3069 个字符,预计需要花费 8 分钟才能阅读完成。

重新梳理一下归并排序以及一些相关的东西。

对于归并排序大家如果需要回忆下是个什么东西的话,可以点击这个链接,里面有各种排序的动画演示以及讲解,比我再用文字赘述一遍要好得多,功能相当强大。

先给出归并排序的 js 代码实现:

function mergeSort(arr, l, r) {if (l === r) {return;}
    let mid = Math.floor((r + l) / 2);
    mergeSort(arr, l, mid);
    mergeSort(arr, mid + 1, r);
    merge(arr, l, mid, r);
}

function merge(arr, l, mid, r) {
    let leftIndex = l;
    let rightIndex = mid + 1;
    let helpArr = [];
    while(leftIndex <= mid && rightIndex <= r) {let leftItem = arr[leftIndex];
        let rightItem = arr[rightIndex];
        if (leftItem < rightItem) {helpArr.push(leftItem);
            leftIndex++;
        } else {helpArr.push(rightItem);
            rightIndex++;
        }
    }

    // 这俩循环只会进去一个,因为经过上面的比较,要么左边部分走完了,要么右边部分走完了
    while(leftIndex <= mid) {helpArr.push(arr[leftIndex]);
        leftIndex++;
    }

    while(rightIndex <= r) {helpArr.push(arr[rightIndex]);
        rightIndex++;
    }

    for (let index = 0; index < helpArr.length; index++) {arr[l] = helpArr[index];
        l++;
    }
}

如何估计归并排序的时间复杂度呢?

由于上面采用了递归写法,我们使用 master 公式对递归进行时间复杂度估算,以下是公式详情。
<p>T(n) = a*T(n/b) + O(n^d)<br/>
(1)、log(b, a) > d => 复杂度为 O(n^log(b, a))<br/>
(2)、log(b, a) = d => 复杂度为 O(n^d*logn)<br/>
(3)、log(b, a) < d => 复杂度为 O(n^d)</p>

a 代表递归的次数,由于在 mergeSort 中调用了两次 mergeSort,所以归并排序中 a = 2。<br/>
b 代表样本量被划分几份,由于我们对样本量是一分为二将数组分为 left 和 right 部分,所以归并排序中 b = 2。<br/>
O(n^d) 代表其他操作的时间复杂度,所以在归并排序中主要是 merge 这个函数,相当于是执行了一次数组遍历,则为 O(n)。<br/>
a = 2,b = 2,d = 1 根据 master 公式,复杂度为 nlogn。

我们知道冒泡排序、选择排序、插入排序的时间复杂度都是 O(n^2),当样本量比较大的时候,n^2 比之 nlogn 差了可不是一星半点。这是为什么呢?因为在其他三种排序中,会浪费元素之间的比较,比如冒泡排序冒泡比较一轮只定位了一个元素,下一轮冒泡又只定位一个元素,会浪费元素之间的相互比较;而归并排序通过分治,由小到大进行比较合并的过程中,上一次比较合并的元素不会再次发生比较,有序的区域成规模增长,这样就不会浪费比较,节省了时间。

由归并排序引入数组小和问题和数组逆序对问题。

根据小和的题目要求,我们思考一下可以发现,在归并排序过程中,left 和 right 部分进行比较合并的时候,其实就可以找到左边部分比右边部分小的数,意思就是说我们可以很方便的在 merge 这个函数执行过程中来计算数组的小和且会快很多,因为合并的时候左右两遍都是有序的,如果一个数比右边的第一个数字小,我们可以得知这个数字肯定比右边全部的数字都小。

举个例子,比如 left = [1,2,3],right = [4,5,6],1 小于 4,说明右边三个数都比 1 大,假如说小和等于 sum,那么 sum 就要加 1 * 3。

代码实现一下小和:

function smallSum(arr) {if (!arr || arr.length < 2) {return 0;}
    return mergeSort(arr, 0, arr.length - 1);
}

function mergeSort(arr, l, r) {if (l === r) {return 0;}
    let mid = Math.floor((l + r)/2);
    return mergeSort(arr, l, mid)
            + mergeSort(arr, mid + 1, r)
            + merge(arr, l, mid, r)
}

function merge(arr, l, mid, r) {
    let leftIndex = l;
    let rightIndex = mid + 1;
    let helpArr = [];
    let sum = 0;
    while(leftIndex <= mid && rightIndex <= r) {let leftItem = arr[leftIndex];
        let rightItem = arr[rightIndex];
        if (leftItem < rightItem) {
            /** 相对于归并排序增加的部分 **/
            let tempSum = (r - rightIndex + 1) * leftItem
            sum += tempSum;
            /***************************/
            helpArr.push(leftItem);
            leftIndex++;
        } else {helpArr.push(rightItem);
            rightIndex++;
        }
    }

    /** 这部分和归并排序 merge 函数一样 **/

    return sum;
}

对于小和都知道如何使用归并排序进行求解之后,逆序对其实和小和是一样的,只是反过来了而已,以下直接贴出代码。

function inversePairs(arr) {if (!arr || arr.length < 2) {return [];
    }
    return mergeSort(arr, 0, arr.length - 1);
}

function mergeSort(arr, l, r) {if (l === r) {return [];
    }
    let mid = Math.floor((l + r)/2);
    return [...mergeSort(arr, l, mid),
        ...mergeSort(arr, mid + 1, r),
        ...merge(arr, l, mid, r)
    ];
}

function merge(arr, l, mid, r) {
    let leftIndex = l;
    let rightIndex = mid + 1;
    let helpArr = [];
    let res = [];
    while(leftIndex <= mid && rightIndex <= r) {let leftItem = arr[leftIndex];
        let rightItem = arr[rightIndex];
        if (leftItem < rightItem) {helpArr.push(leftItem);
            leftIndex++;
        } else {
            /** 相对于归并排序增加的部分 **/
            res.push([leftItem, rightItem]);
            /***************************/
            helpArr.push(rightItem);
            rightIndex++;
        }
    }

    /** 这部分和归并排序 merge 函数一样 **/

    return res;
}

以上是对归并排序这部分内容进行的一些回顾和总结,希望能加深自己对它的理解,能在其他更多的地方将其运用上;如果有不正确的地方,大家可以踊跃指出,我将及时改正。

正文完
 0