1. 程式人生 > >從零開始學演算法(四)歸併排序

從零開始學演算法(四)歸併排序

從零開始學演算法(四)歸併排序

歸併排序

程式碼是Javascript語言寫的(幾乎是虛擬碼)

演算法介紹

歸併排序(Merge Sort)把兩個排好序的序列合併成一個排好序的序列

演算法原理

我們在拿到一個無序的序列時,其實用了“分治”的思想,先“分”後“治”,先將整個序列通過劃分為子序列,再將子序列不斷劃分為子序列,直到只有單個元素為止,再通過兩兩合併使其有序,再將合併後的陣列繼續通過比較大小拷貝的方法繼續合併,最終實現整體有序。

演算法簡單記憶說明

在這裡插入圖片描述
“分” 階段採用遞迴的方法,將整個陣列不斷劃分為最小的陣列個體

程式碼實現:

function mergeSort(arr,l,r){
	if (l == r) {
			return;
		}
		var mid = l + ((r - l) >> 1);
		mergeSort(arr, l, mid);
		mergeSort(arr, mid + 1, r);
	}

與我們上一篇文章的遞迴思想舉的例子完全一樣,mid為序列的中間數的位置,通過遞迴讓它們不斷一分為二。

“治” 階段將劃分好的子序列兩兩歸併排序合併在一起,合併後的序列繼續兩兩合併,最終全部整合形成完整的有序序列

“治”的過程的實現時通是通過準備一個拷貝陣列,比較兩個待合併的序列,分別給兩個索引,誰小拿下來填誰,誰的索引向後移動一位,直到有一邊的序列全部被填入拷貝陣列,則把剩下的序列的剩下的數填入拷貝陣列,最後將拷貝後的陣列拷貝回原陣列。
待合併兩陣列

8 3
i j

help輔助拷貝陣列

3<8,將3填入help陣列,j向後移,但是陣列耗盡了,所以將8拷入help陣列中得到

3 8

同理2和9也通過比較拷貝得到

2 9

繼續合併合併後的陣列
小的填入而後索引後移直到耗盡一方,然後將剩下的序列的剩下的元素填入陣列中。

最後大合併

演算法複雜度和穩定性

歸併排序的時間複雜度是O(N*logN)
歸併排序採用遞迴的思想來做
master公式 T(N) = a*T(N/b) + O(Nd)

從父問題與子問題的大層面關係來看,整個陣列樣本量為N,左邊一半,樣本量為N/2,右邊一般,樣本量為N/2,左邊跑完跑右邊,樣本量為N/2的過程發生了2次,得到兩個排好序的子樣本,剩下要做的是在外排的過程中劃過N個數,因為兩個下標依此在動,最後整體拷貝回陣列,剩下的操作為N
則a=2,b=2,d=1
log(b,a) = d -> 複雜度為O(Nd * logN)
得到歸併排序的時間複雜度是O(N*logN)

歸併排序是穩定排序演算法

演算法穩定性的定義: 假設在數列中存在a[i]=a[j],若在排序之前,a[i]在a[j]前面;並且排序之後,a[i]仍然在a[j]前面。則這個排序演算法是穩定的。
舉例
1,3,2,5,2,4
排序的過程中1,2,3與2,4,5排序後為1,2,2,3,4,5
所以為穩定排序

程式碼實現

function mergeSort(arr,l,r){ //遞迴
	if (l == r) {
			return;
		}
		var mid = l + ((r - l) >> 1);
		mergeSort(arr, l, mid);
		mergeSort(arr, mid + 1, r);
		merge(arr, l, mid, r);
	}
function merge(arr,l,m,r){
	var help = new Array(r-l+1);//創立輔助陣列用來拷貝數字
	var i = 0;
	var p1 = l;//左邊部分首位
	var p2 = m+1;//右邊部分首位
	while(p1 <= m&&p2 <= r){
		help[i++] = arr[p1]<arr[p2]?arr[p1++]:arr[p2++];
		//完成比較合併,p1小填p1,p2小填p2,填入陣列的那個部分++向後移動一位,輔助陣列也要向後移動一位
	} //必定有且只有一個部分越界
	while(p1<=m){
		help[i++] = arr[p1++]; //p2越界,把p1剩下的填了
	}
	while(p2<=r){
		help[i++] = arr[p2++]; //p1越界,把p2剩下的填了
	}
	for(i = 0;i<help.length;i++){
		arr[l+i] = help[i]; //拷貝回原陣列
	}

}
function startMerge(arr){
	if(arr == null||arr.length<2){
		return arr;	
	}
	mergeSort(arr,0,arr.length-1);
}

//對數器
function sortNumber(a,b){
return a - b
}

function rightMethod(arr) {
    arr.sort(sortNumber);
}

function generateRandomArray(maxSize, maxValue) {
    var arr = new Array(Math.floor((maxSize + 1) * Math.random()));
    for (var i = 0; i < arr.length; i++) {
        arr[i] = Math.floor((maxValue + 1) * Math.random())-Math.floor(maxValue * Math.random());
    }
    return arr;
}

function isEqual(arr1, arr2) {
    if ((arr1 == null && arr2 != null ) || (arr1 != null && arr2 == null)) {
        return false;
    }
    if (arr1 == null && arr2 == null) {
        return true;
    }
    if (arr1.length != arr2.length) {
        return false;
    }
    for (let i = 0; i < arr1.length; i++) {
        if (arr1[i] != arr2[i]) {
            return false
        }
    }
    return true;
}

function copyArray(arr) {
    if (arr == null) {
        return null;
    }
    return [].concat(arr);
}

function Test() {
    var testTimes = 10000;
    var maxmaxSize = 10;
    var maxValue = 100;
    var succeed = true;
    for (var i = 0; i < testTimes; i++) {
        var arr1 = generateRandomArray(maxmaxSize,maxValue);
        var arr2 = copyArray(arr1);
        var arr3 = copyArray(arr1);
        startMerge(arr1);
        rightMethod(arr2);
        console.log(arr1);
        if (!isEqual(arr1, arr2)) {
            succeed = false;
            console.log(arr3);
            break;
        }
    }
    console.log(succeed ? "Good job!" : "Damn it!");
}
Test();

在這裡插入圖片描述
通過對數器的驗證!Good job!