由mergeSort引發的一些思考

重新梳理一下歸併排序以及一些相關的東西。

對於歸併排序大家如果需要回憶下是個什麼東西的話,可以點擊這個鏈接,裏面有各種排序的動畫演示以及講解,比我再用文字贅述一遍要好得多,功能相當強大。

先給出歸併排序的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;
}

以上是對歸併排序這部分內容進行的一些回顧和總結,希望能加深自己對它的理解,能在其他更多的地方將其運用上;如果有不正確的地方,大家可以踊躍指出,我將及時改正。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章