從零開始學演算法(四)歸併排序
從零開始學演算法(四)歸併排序
歸併排序
程式碼是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!