遞歸的內存堆棧分析
一直對遞歸理解不深,原因是遞歸的過程很抽象,分析不清內存堆棧的返回過程。偶然google到一篇博文遞歸(不得不說,技術問題還是要多google),對遞歸過程的內存堆棧分析豁然開朗,下面先列出分析過程:
// A C++ program to demonstrate working of recursion
#include<bits/stdc++.h>
using namespace std;
void printFun(int test)
{
if (test < 1)
return;
else
{
cout << test << " ";
printFun(test-1); // statement 2
cout << test << " ";
return;
}
}
int main()
{
int test = 3;
printFun(test);
}
下面這個圖準確的列出了整個遞歸的過程,以后遇到單次遞歸問題,完全可以用此方法分析(對于多次遞歸情況,嘗試畫了一下歸并排序里的兩次遞歸,實在沒有辦法整潔的排版,作罷。。)
言歸正傳,下面分析歸并排序。
歸并排序
歸并排序采用的是分治的思想,首先是“分”,將一個數組反復二分為兩個小數組,直到每個數組只有一個元素;其次是“治”,從最小數組開始,兩兩按大小順序合并,直到并為原始數組大小,下面是圖解:
觀察下“治”的過程,可以看出,“治”實際上是將已經有序的數組合并為更大的有序數組。那么怎樣將已經有序的數組合并為更大的有序數組?很簡單,創建一個臨時數組C,比較A[0],B[0],將較小值放到C[0],再比較A[1]與B[0](或者A[0],B[1]),將較小值放到C[1],直到對A,B都遍歷一遍。可以看出數組A,B都只需遍歷一遍,所以對兩個有序數組的排序的時間復雜度為O(n)。
而“分”就是將原始數組逐次二分,直到每個數組只剩一個元素,一個元素的數組自然是有序的,所以就可以開始“治”的過程了。
時間復雜度分析:分的過程需要三步:log8 = 3,而每一步都需要遍歷一次8個元素,所以8個元素共需要運行 8log8)次指令,那么對于 n 個元素,時間復雜度為 O(nlogn)。
代碼中運用了兩次遞歸,十分抽象難懂,畫了一整頁堆棧調用圖,才弄明白(太凌亂就不貼了),大家可以試試。
// 融合兩個有序數組,這里實際上是將數組 arr 分為兩個數組
function mergeArray(arr, first, mid, last, temp) {
let i = first;
let m = mid;
let j = mid+1;
let n = last;
let k = 0;
while(i<=m && j<=n) {
if(arr[i] < arr[j]) {
temp[k++] = arr[i++];
} else {
temp[k++] = arr[j++];
}
}
while(i<=m) {
temp[k++] = arr[i++];
}
while(j<=n) {
temp[k++] = arr[j++];
}
for(let l=0; l<k; l++) {
arr[first+l] = temp[l];
}
return arr;
}
// 遞歸實現歸并排序
function mergeSort(arr, first, last, temp) {
if(first<last) {
let mid = Math.floor((first+last)/2);
mergeSort(arr, first, mid, temp); // 左子數組有序
mergeSort(arr, mid+1, last, temp); // 右子數組有序
arr = mergeArray(arr, first, mid, last, temp);
}
return arr;
}
// example
let arr = [10, 3, 1, 5, 11, 2, 0, 6, 3];
let temp = new Array();
let SortedArr = mergeSort(arr, 0 ,arr.length-1, temp);
alert(SortedArr);