數(shù)據(jù)結(jié)構(gòu)與算法之美筆記——遞歸

摘要:

遞歸是一種經(jīng)常遇到的算法,主要用于一個(gè)問(wèn)題可拆解為多個(gè)相似解決思路的小問(wèn)題進(jìn)行解決,遞歸的優(yōu)勢(shì)就是代碼簡(jiǎn)潔高效,但是遞歸也會(huì)存在堆棧溢出、發(fā)生重復(fù)計(jì)算、空間復(fù)雜度較高等問(wèn)題。

遞歸、

遞歸在編程技巧中十分常見(jiàn),當(dāng)一個(gè)大問(wèn)題可拆解為多個(gè)類似解決思路的小問(wèn)題時(shí)便可以使用遞歸,但是書(shū)寫(xiě)遞歸代碼很多人會(huì)覺(jué)得復(fù)雜,其實(shí)遞歸代碼的書(shū)寫(xiě)也有自己的技巧。

分析遞歸的過(guò)程其實(shí)主要是在分析其「遞推公式」和「結(jié)束條件」,舉個(gè)生活中的例子,比如排隊(duì)時(shí)你想知道自己是排在第幾個(gè),可以一個(gè)個(gè)自己數(shù),但也可以問(wèn)排前一位的人是排第幾個(gè),加一位就是自己當(dāng)前排隊(duì)的位置,那如果前一位也不知道自己具體排在第幾位呢,同樣他也可以用同樣的方式詢問(wèn)排前面的一位,那有誰(shuí)可以完全明確自己的排隊(duì)位置呢,排在第 1 位的肯定能明確自己的位置是第 1,因?yàn)樗懊嬉呀?jīng)沒(méi)有人了。

這個(gè)實(shí)際中的例子將一個(gè)確定自己排隊(duì)位置的問(wèn)題分解為了詢問(wèn)自己前一位的位置加一的方式解決,前一位也通過(guò)類似的解決方式來(lái)確定自己的位置,直到排在第一位的人明確自己的位置是第一位。按照分析可以得到遞推公式和結(jié)束條件如下:

遞推公式:f(n)=f(n-1)+1
結(jié)束條件:f(1)=1

轉(zhuǎn)換為代碼如下:

public int f(n) {
  if(n == 1) {return 1;}
  return f(n - 1) + 1;
}

這里可以看出遞歸可以使代碼簡(jiǎn)潔高效,這也是遞歸的優(yōu)勢(shì),但并不是說(shuō)所有的問(wèn)題都適合使用遞歸進(jìn)行解決,一般能夠滿足以下三個(gè)條件的問(wèn)題比較適合使用遞歸解決。

  • 一個(gè)問(wèn)題可以拆分為諸多小問(wèn)題解決

比如排隊(duì)例子確定「自己排在第幾個(gè)位置」這個(gè)問(wèn)題就可以分解為「詢問(wèn)自己前一位的人的排隊(duì)位置」。

  • 一個(gè)問(wèn)題拆分的小問(wèn)題與其本身具有相似的解決思路

比如排隊(duì)例子中的子問(wèn)題「詢問(wèn)在自己前一位的人的排隊(duì)位置」與「明確自己排隊(duì)所在位置」有同樣的解決思路,就是「再詢問(wèn)排隊(duì)在自己之前的人所在排隊(duì)位置」。

  • 能夠找到遞歸的結(jié)束條件

在排隊(duì)例子中的遞歸結(jié)束條件就是「排隊(duì)在第一位人肯定能明確自己所排位置為第一位」。

遞歸的劣勢(shì)

堆棧溢出

當(dāng)調(diào)用方法時(shí),在調(diào)用方法之前被聲明的臨時(shí)變量會(huì)被存儲(chǔ)在棧中,等待臨時(shí)方法被調(diào)用結(jié)束之后再將臨時(shí)變量取出恢復(fù)現(xiàn)場(chǎng),如果遞歸過(guò)深,大量的臨時(shí)變量被存儲(chǔ)棧中,必然會(huì)造成堆棧的溢出,這個(gè)問(wèn)題可以通過(guò)限制遞歸深度來(lái)解決。

重復(fù)計(jì)算問(wèn)題

在排隊(duì)例子中沒(méi)有體現(xiàn)重復(fù)計(jì)算問(wèn)題,我們可以換個(gè)例子,例如斐波那契數(shù)列,斐波那契數(shù)列符合使用遞歸的條件,遞推公式及結(jié)束條件如下:

遞推公式:f(n)=f(n-1)+f(n-2) (n\geq2)
結(jié)束條件:f(1)=1 f(0)=0

如果計(jì)算第3位的數(shù)時(shí)就會(huì)如下圖:


從圖中可以看出對(duì) f(1) 存在重復(fù)計(jì)算,如果要解決這個(gè)問(wèn)題可以使用一個(gè)數(shù)據(jù)結(jié)構(gòu)將計(jì)算結(jié)果存儲(chǔ)起來(lái),每次計(jì)算前查看是否有相應(yīng)的計(jì)算結(jié)果,如果有計(jì)算結(jié)果就取出使用,如果沒(méi)有就進(jìn)行計(jì)算并把相應(yīng)計(jì)算結(jié)果存儲(chǔ)起來(lái)。

空間復(fù)雜度高

在排隊(duì)例子中分析代碼空間復(fù)雜度為 O(1),但是因?yàn)檫f歸在調(diào)用方法時(shí)會(huì)造成存儲(chǔ)空間的累積,所以排隊(duì)例子代碼的空間復(fù)雜度應(yīng)該為 O(n)

遞歸代碼雖然簡(jiǎn)潔但有相應(yīng)的劣勢(shì),除了以上描述的解決方案之外其實(shí)還可以將遞歸代碼轉(zhuǎn)化為循環(huán)進(jìn)行解決,因?yàn)闅w根結(jié)底遞歸也是對(duì)問(wèn)題的循環(huán)解決,比如排隊(duì)問(wèn)題的代碼可以修改為如下的循環(huán):

public int f(n) {
  int position = 1;
  for(int i = 2; i <= n; i++) {
    position++;
  }
  return position;
}

總結(jié)

遞歸適宜于大問(wèn)題可以分割為有相似解決方案的小問(wèn)題的場(chǎng)景,寫(xiě)出遞歸代碼的關(guān)鍵在于找到遞歸公式和遞歸結(jié)束條件,使用遞歸可以使代碼表達(dá)更加簡(jiǎn)潔,但也會(huì)存在堆棧溢出,重復(fù)計(jì)算及空間復(fù)雜度高等問(wèn)題,堆棧溢出與空間復(fù)雜度高可以通過(guò)限制遞歸深度解決,重復(fù)計(jì)算可以通過(guò)存儲(chǔ)計(jì)算值避免。


文章中如有問(wèn)題歡迎留言指正
數(shù)據(jù)結(jié)構(gòu)與算法之美筆記系列將會(huì)做為我對(duì)王爭(zhēng)老師此專欄的學(xué)習(xí)筆記,如想了解更多王爭(zhēng)老師專欄的詳情請(qǐng)到極客時(shí)間自行搜索。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容