歸并排序?qū)嶋H是使用了分治再合并的思想:
分治:
它每輪會(huì)把數(shù)組分割成2分部分,如果分割的部分還很多數(shù),可以按照這個(gè)方法繼續(xù)分割,直到分割成簡(jiǎn)單序列(比如分割到剩最后一個(gè)數(shù)了,一個(gè)數(shù)的序列自然就是最簡(jiǎn)單的有序序列)。-
合并:
這時(shí)候,對(duì)分割的最小部分開始,進(jìn)行兩兩合并成有序序列,合并的實(shí)現(xiàn)方法是:- 創(chuàng)建一個(gè)左指針,指向一個(gè)分割的有序序列(比如是數(shù)組arr1, 長(zhǎng)度為n)的初始位置0;
- 創(chuàng)建一個(gè)右指針,指向另一個(gè)分割的有序序列(數(shù)組arr2,長(zhǎng)度為m)的初始位置0;
- 再創(chuàng)建一個(gè)m+n的空數(shù)組(res),用來(lái)存儲(chǔ)此輪的有序結(jié)果;
- 比較兩個(gè)指針的數(shù),哪個(gè)小就放到res中,然后該指針右移;
- 重復(fù)4步驟操作,直到有一個(gè)有序序列已經(jīng)全被放到到res中,另一個(gè)有序序列剩下的所有值就可以直接拼接在res后面了,這樣一輪操作就結(jié)束了
- 然后,再對(duì)再一對(duì)分割部分重復(fù)做1-5步驟,直至所有分割部分都合并完,結(jié)束
下面通過(guò)例子來(lái)說(shuō)明它的思想:
初始的無(wú)序數(shù)組:[ 8, 5, 4, 9, 6, 2, 1 ]
分割:
- 第1步,對(duì)半分,分成兩組 [8, 5, 4, 9],[6, 2, 1],此時(shí)這兩還不是有序數(shù)組
- 第2步,各自繼續(xù)對(duì)半分,[8, 5, 4, 9]分成[8, 5]和[4, 9]; [6, 2, 1]被分割成[6, 2]和[1],發(fā)現(xiàn)除了[1]其它都發(fā)現(xiàn)還不是有序數(shù)組
- 第3步,各自繼續(xù)對(duì)半分,[8, 5]分成[8]和[5],[4, 9]分成[4]和[9], [6, 2]分成[6]和[2],這下好了,所有的都是有序序列了[8],[5],[4],[9],[6],[2],[1]
合并: - 對(duì)各層最小部分進(jìn)行合并[8],[5]合并成了有序序列[5, 8];[4],[9]合并成了有序序列[4, 9],[6]和[2]合并成了[2, 6],[1]這一步?jīng)]有對(duì)象合并閑置
- 再向上一層合并,[5, 8]和[4, 9]合并成了[4, 5, 8, 9];[2, 6]和[1]合并成了[1, 2, 6]
- 再向上一層合并[4, 5, 8, 9] 和 [1, 2, 6] 合并成了 [1, 2, 4, 5, 6, 8, 9],此時(shí),已經(jīng)是個(gè)完整的有序序列了,結(jié)束
復(fù)雜度分析
- 時(shí)間復(fù)雜度:
- 分割過(guò)程每次步驟是n/2,所以時(shí)間復(fù)雜度為O(logn)
- 合并過(guò)程是遍歷一個(gè)循環(huán),O(n)
所以總的是O(nlogn)
- 空間復(fù)雜度:
要重新定義一個(gè)數(shù)組存放有結(jié)果,所以是O(n) - 穩(wěn)定性:歸并排序并不會(huì)改變相同元素的相對(duì)位置,所以是一個(gè)穩(wěn)定的算法
代碼實(shí)現(xiàn)
/**
* @description 遞歸實(shí)現(xiàn)歸并排序
* @param arr: 初始的無(wú)序隊(duì)列
* @return res: 被排好序的隊(duì)列
*/
function mergeSort(arr) {
// 如果分割得只每剩一個(gè)值,自然已經(jīng)是一個(gè)有序區(qū)間,直接返回
if(arr.length <= 1) return arr;
// 否則分割
let mid = 0;
mid = Math.floor((arr.length)/2)
console.log('mid:', mid);
// 左子序列
let left = arr.slice(0, mid)
// 遞歸獲取左子序列的分割合并,得到最后一層的有序左子序列
left = mergeSort(left)
// 右子序列
let right = arr.slice(mid)
// 遞歸獲取右子序列的分割合并,得到最后一層的有序右子序列
right = mergeSort(right)
return merge(left, right)
}
/**
* @description 合并算法
* @param left: 要被合并的有序數(shù)組1
* @param right: 要被合并的有序數(shù)組2
* @return arr: 被排序好的數(shù)組
*/
function merge(left, right) {
// 合并后存放的數(shù)組
let res = []
while(left.length && right.length) {
// 如果左子序列第1個(gè)數(shù)數(shù)比較小,彈出此數(shù)放到結(jié)果隊(duì)列中
if(left[0] <= right[0]) {
res.push(left.shift())
// 否則,如果右子序列第1個(gè)數(shù)數(shù)比較小,彈出此數(shù)放到結(jié)果隊(duì)列中
} else {
res.push(right.shift())
}
}
// 當(dāng)一個(gè)子序列排序結(jié)束時(shí),另一個(gè)子序列就不需要再遍歷比較了,直接拼接到結(jié)果后
return res.concat(left, right)
}
// 查看所有結(jié)果
arr = [ 8, 5, 4, 9, 6, 2, 1 ]
let res = mergeSort(arr) // [1, 2, 4, 5, 6, 8, 9]
console.log("res:", res)
參考:
圖靈社區(qū):https://www.ituring.com.cn/book/miniarticle/62897
小象Web開發(fā):https://baijiahao.baidu.com/s?id=1675262114341494342&wfr=spider&for=pc
排序算法系列文章傳送門(未完,持續(xù)更新中):
排序算法-1(javascript) 冒泡、選擇、插入、希爾排序的實(shí)現(xiàn)
排序算法-2(javascript) 快速排序的實(shí)現(xiàn)
排序算法-3(javascript) 堆排序的實(shí)現(xiàn)
排序算法-4(javascript) 歸并排序的實(shí)現(xiàn)