三談歸併排序(含尾遞歸)

一談,原始的歸併排序

function mergeSort(arr) {
    let {
        length
    } = arr
    if (length < 2) {
        return arr
    }
    let midIndex = Math.floor(length / 2)
    let leftArr = mergeSort(arr.slice(0, midIndex))
    let rightArr = mergeSort(arr.slice(midIndex))
    return merge(leftArr, rightArr)
}

function merge(leftArr, rightArr) {
    let i = 0,
        j = 0,
        arr = []
    while (true) {
        if (i === leftArr.length) {
            arr.push(...rightArr.slice(j))
            break
        }
        if (j === rightArr.length) {
            arr.push(...leftArr.slice(i))
            break
        }
        if (leftArr[i] < rightArr[j]) {
            arr.push(leftArr[i])
            i++
        }
        if (rightArr[j] <= leftArr[i]) {
            arr.push(rightArr[j])
            j++
        }
    }
    return arr
}

let arr = [1, 5, 2, 11, 7, 3, 1, 6, 17, 10]
let arrInOrder = mergeSort(arr)
console.log(arrInOrder) // [ 1, 1, 2, 3, 5, 6, 7, 10, 11, 17 ]

二談,優化後的歸併排序

優化算法的指導思想之一,找到某些能夠簡化處理的特殊狀況算法

合併時的特殊狀況

當 leftArr 的最後一個元素小於 rightArr 的第一個元素時,那麼順序就應該是 [leftArr, rightArr]數組

當 rightArr 的最後一個元素小於 leftArr 的第一個元素時,那麼順序就應該是 [rightArr, leftArr]函數

因此修改 mergeSort 函數以下優化

function mergeSort(arr) {
    let {
        length
    } = arr
    if (length < 2) {
        return arr
    }
    let midIndex = Math.floor(length / 2)
    let leftArr = mergeSort(arr.slice(0, midIndex))
    let rightArr = mergeSort(arr.slice(midIndex))
    if (leftArr[leftArr.length - 1] <= rightArr[0]) {
        return [...leftArr, ...rightArr]
    }
    if (rightArr[rightArr.length - 1] <= leftArr[0]) {
        return [...rightArr, ...leftArr]
    }
    return merge(leftArr, rightArr)
}
...

適時的利用插入排序

當數組的長度變小到必定程序時,採用插入排序code

遞歸優化-尾遞歸

先修改代碼以下排序

function mergeSort(arr, fromIndex, length) {
    if (length < 2) {
        return
    }
    mergeSort(arr, fromIndex, Math.floor(length / 2))
    mergeSort(arr, fromIndex + Math.floor(length / 2), length - Math.floor(length / 2))
    merge(arr, fromIndex, length)
}

function merge(arr, fromIndex, length) {
    let leftArr = arr.slice(fromIndex, fromIndex + Math.floor(length / 2))
    let rightArr = arr.slice(fromIndex + Math.floor(length / 2), fromIndex + length)
    let i = 0,
        j = 0,
        orderedArr = []
    while (true) {
        if (i === leftArr.length) {
            orderedArr.push(...rightArr.slice(j))
            break
        }
        if (j === rightArr.length) {
            orderedArr.push(...leftArr.slice(i))
            break
        }
        if (leftArr[i] < rightArr[j]) {
            orderedArr.push(leftArr[i])
            i++
        }
        if (rightArr[j] <= leftArr[i]) {
            orderedArr.push(rightArr[j])
            j++
        }
    }
    arr.splice(fromIndex, length, ...orderedArr)
}

let arr = [1, 5, 2, 11, 7, 3, 1, 6, 17, 10]
mergeSort(arr, 0, arr.length)
arr // [ 1, 1, 2, 3, 5, 6, 7, 10, 11, 17 ]

把傳過的參數都記錄下來,存在 argsArr 中遞歸

例如在計算 fromIndex 爲 0, length 爲 10 時,分爲三步io

  • 先要經過mergeSort計算 fromIndex 爲 0, length 爲 5
  • 再經過mergeSort計算 fromIndex 爲 5, length 爲 5
  • 最後merge (arr, 0, 10)

因爲尾遞歸調用,只能先計算 mergeSort(arr, 0, 5, argsArr)console

而把 [0, 10, 5, 5] 存起來,前兩個參數是merge的參數,後兩個是mergeSort的參數function

用過的參數就把它去掉,因此[0, 10, 5, 5] => [0, 10] =>

function mergeSort(arr, fromIndex, length, argsArr) {
    if (length < 2) {
        let args = argsArr.pop()
        while (args) {
            if (args.length === 4) {
                argsArr.push([args[0], args[1]])
                break
            }
            if (args.length === 2) {
                merge(arr, args[0], args[1])
                args = argsArr.pop()
            }
        }
        if (args) {
            return mergeSort(arr, args[2], args[3], argsArr)
        } else {
            return
        }
    }
    argsArr.push([fromIndex, length, fromIndex + Math.floor(length / 2), length - Math.floor(length / 2)])
    return mergeSort(arr, fromIndex, Math.floor(length / 2), argsArr)
}

function merge(arr, fromIndex, length) {
    let leftArr = arr.slice(fromIndex, fromIndex + Math.floor(length / 2))
    let rightArr = arr.slice(fromIndex + Math.floor(length / 2), fromIndex + length)
    let i = 0,
        j = 0,
        orderedArr = []
    while (true) {
        if (i === leftArr.length) {
            orderedArr.push(...rightArr.slice(j))
            break
        }
        if (j === rightArr.length) {
            orderedArr.push(...leftArr.slice(i))
            break
        }
        if (leftArr[i] < rightArr[j]) {
            orderedArr.push(leftArr[i])
            i++
        }
        if (rightArr[j] <= leftArr[i]) {
            orderedArr.push(rightArr[j])
            j++
        }
    }
    arr.splice(fromIndex, length, ...orderedArr)
}

let arr = [1, 5, 2, 11, 7, 3, 1, 6, 17, 10, 312, 312, 1, 1, 2323, 4, 56, 3, 14, 5543]
mergeSort(arr, 0, arr.length, [])
console.log(arr)

三談,迭代版歸併排序

其中 merge 函數不變,修改 mergeSort 函數

function mergeSort(arr) {
    for (let size = 1; size < arr.length; size = size * 2) {
        for (let i = 0; i + size < arr.length; i = i + size * 2) {
            merge(arr, i, size * 2)
        }
    }
}
function merge(arr, fromIndex, length) {
  ...
}
相關文章
相關標籤/搜索