演算法之歸併排序
排序是演算法中最基本的題目,其中很重要的一個排序演算法就是歸併排序,也叫合併排序。歸併排序是演算法裡典型的策略中分治策略的最基本的一個實現,也是很多演算法類書籍講解演算法的時候最喜歡作為引導的一個題目。歸併排序總是被拿來作為演算法書的開篇主要是由於下面的幾個特點:
面試中總被問起的演算法排序題目; 非常典型的分治策略的演算法題目; 非常適合演算法複雜度分析的練手題目; 其它。
今天我們就來看看歸併排序是怎麼回事,以及在JavaScript中的實現。
引導
先定義一下我們的題目,題目是:
給定陣列,設計演算法給陣列進行排序; 輸入: 任意亂序的陣列; 輸出: 從小到大順序的陣列。
解決思路
分治策略的核心就是分和治,簡單的說就是,大的問題逐個分解,然後解決分解後的小問題。最後合併解決完的小問題,即大的問題即被解決。 就歸併排序來講,就是把輸入的陣列(大問題)逐個分解為小的問題(小陣列),然後解決小問題(對小問題進行排序),最後把合併解決完後的小問題(合併排序後的陣列),這樣就得到了我們需要的排序後的問題。 下面,我們用一個陣列來做為例子: 第一步:陣列 [5, 4, 1, 8, 7, 2, 6, 3] 第二步:陣列分解為兩個陣列 [5, 4, 1, 8]和[7, 2, 6, 3] 第三步:遞迴呼叫自身逐個分解併合並排序 第四步:陣列[1, 4, 5, 8]和[2, 3, 6, 7] 第五步:合併後得到[1, 2, 3, 4, 5, 6, 7, 8]
虛擬碼
程式碼部分也分兩步來,第一步解決排序,第二步解決合併,如下:
第一部分:排序子問題:
// 輸入: n長度的亂序陣列A
// 輸出: n長度的順序陣列A
// ===================
函式mergeSort,輸入A
如果陣列長度為1或者0,直接返回;否則,繼續:
B = 遞迴呼叫mergeSort函式,排序陣列A的前n/2;
C = 遞迴呼叫mergeSort函式,排序陣列A的後n/2;
呼叫合併merge函式,返回merge(B, C);
複製程式碼
第二部分:合併子問題
// 輸入: 兩個順序的陣列B合C(n/2長度)
// 輸出: 順序的陣列D
i = j = 0;
for k=0 to n-1
if B[i] > C[j]
D[k] = C[j], j++
else
D[k] = B[i], i++
複製程式碼
第二部分這裡需要考慮的是如果陣列長度不一樣的話,C或者D的陣列某一個陣列的元素取完後的情況的處理。讀者可以自己思考一下。
歸併排序之js實現
下面我們用js來實現歸併排序,如下:
/*
* 歸併排序
*/
function mergeSort(arr) {
var n = arr.length, index = Math.floor(n/2), leftArr, rightArr;
if (arr.length === 0 || arr.length === 1) {
return arr;
}
leftArr = mergeSort(arr.slice(0, index));
rightArr = mergeSort(arr.slice(index, n));
return merge(leftArr, rightArr);
}
/*
* 合併過程
*/
function merge (arrLeft, arrRight) {
var k = i = j = 0, ll = arrLeft.length, lr = arrRight.length, nl = ll + lr, res = [];
while (i < ll && j < lr) {
if (arrLeft[i] > arrRight[j]){
res[k++] = arrRight[j++];
} else {
res[k++] = arrLeft[i++];
}
}
if (i >= ll) {
while (k < nl) {
res[k++] = arrRight[j++];
}
} else if (j >= lr) {
while (k < nl) {
res[k++] = arrLeft[i++];
}
}
return res;
}
複製程式碼
演算法複雜度
這裡簡單介紹下歸併排序的演算法複雜度。演算法複雜度通常用大O的表示法,並且忽略常數和低階多項式,只保留最高階表示式。例如,我們的歸併排序的複雜度為O(n*logn)。 複雜度分析,不知道O(n*logn)的來源的,大家可以用樹形分析法,簡單的講陣列每一次遞迴需要執行的操作列出來,然後畫成樹形結構,你就會得到n*logn的多項式了。這樣保留最高向即是O(n*logn)。
原文同樣載於這裡,歡迎star。