1. 程式人生 > >掌握歸併排序前首先理解遞迴行為

掌握歸併排序前首先理解遞迴行為

剖析遞迴行為

遞迴行為

“大事化小” 講的就是一個遞迴的思想,把一個規模大的問題通過劃分成小問題去解決,小問題繼續劃分

遞迴的聯想理解

遞迴像是你在金字塔的頂端,要收集最底的寶藏,你每下一層看沒有寶藏,找到了去往下一層的門,繼續去下一層,層和層之間的樓梯都是一樣的,看上去走的行程是一樣的,實際上它們是不同的樓梯,等你走到底拿了寶藏,你還要一層一層再爬回去。

遞迴的基本思想

“有來有回” 以及常聽到的 “自己呼叫自己”

當劃分為多個子問題處理時,重要的一點是子問題解決了,才可以繼續解決基於子問題的問題

舉個例子

一個數組左邊界是L,右邊界是R,(L和R都是位置),我們想要尋找這個陣列的最大值,我們現在將陣列一分為二,分別找出陣列左部分的最大值,和陣列右部分的最大值,最後返回二者之中的最大值。
5,3,1,8,6,2
L,1,2,3,4,R

var maxArr = [5,3,1,8,6,2];
function getMax(arr,L,R){
	if(L == R){
	return arr[L];
	}
	var mid = (L+R)/2;
	var maxLeft = getMax(arr,L,mid);
	var maxRight = getMax(arr,mid+1,R);
	return max = Math.max(maxLeft,maxRight);
}
getMax(maxArr,0,maxArr.length-1);
console.log(max);

在這裡插入圖片描述

可以看到提示程式碼錯誤:
“Maximum call stack size exceeded” 超過最大呼叫棧大小

原來系統用的是棧結構來解決這個問題,不過我們先改寫程式碼使它通過隨後詳細講解。

我們將var mid = (L+R)/2;改為var mid = L + ((R - L) >> 1);
在這裡插入圖片描述
打印出陣列最大值8

因為L,R表示下標時,L不會溢位,R不會溢位,L+R可能溢位,mid = (L+R)/2算出的mid可能是錯誤的,這樣是不安全的,但mid = (L+(R-L)/2)<R,R不溢位,所以絕對不會有溢位問題,最後用右移運算抖機靈。

深入剖析遞迴行為

遞迴就是系統幫你壓棧
遞迴函式getMax(arr,L,R)開始,系統準備一個棧,首先L和R相等嗎,相等則說明陣列只有一個元素則直接返回,否則繼續。首先L=0,R=5,產生一個變數mid = 2,隨後變數maxLeft需要子過程來產生,系統做的工作是把getMax(arr,0,5)以及程式碼跑到第幾行,mid = 2等資訊全部壓入棧中,然後呼叫子過程,先當與這個函式被歸擋,先跑子過程,繼續跑getMax(arr,0,2),一直繼續直到觸發L == R,即執行 return arr[L]返回。然後進行彈棧操作,讀取彈棧後的棧頂的資訊,完全還原現場當時的資訊,即發生到哪個子過程,程式碼跑到第幾行,再繼續執行之後的子過程,最終串起來子過程和父過程的通訊。

任何遞迴行為都可以改成非遞迴
將系統壓棧改成自己壓棧,就變成了非遞迴,即迭代

遞迴行為的複雜度

Master公式
T(N) = aT(N/b) + O(Nd)
T(N)表示樣本量為N的時間複雜度
T(N/b)表示子樣本的樣本量
a表示子過程發生的次數
O(Nd)表示除過子樣本發生之後剩下的操作的時間複雜度

以之前的例子為例,在一個數組中尋找最大值,陣列一切為2,整個陣列樣本量為N,左邊一半,樣本量為N/2,右邊一般,樣本量為N/2,左邊跑完跑右邊,樣本量為N/2的過程發生了2次,跑完子過程之後,比對最大的數值然後返回,是常數量的複雜度。
即:T(N) = 2T(N/2)+O(1)

Master公式的使用

T(N) = a*T(N/b) + O(Nd)

  1. log(b,a) > d -> 複雜度為O(Nlog(b,a))
  2. log(b,a) = d -> 複雜度為O(Nd * logN)
  3. log(b,a) < d -> 複雜度為O(Nd)

劃分子問題的規模一樣才可以用master公式

上面的例子,a = 2, b = 2,d = 0 則O(N) = O(N)

一些補充

我們在分析一個問題寫演算法的時候,只關心一步,即父問題轉換為子問題那一步,我們只需要知道它們大層面的關係,子問題繼續轉換子問題以及如何轉換,轉換多少次,不需要去關心。
劃分是一個大範圍對等的劃分,例如:如果是奇數個數陣列,劃分為兩部分,資料不對等也屬於正確。