如何區分優質程序猿?遞歸的修煉之路!

遞歸是程序運行時的一種現象,也是解決某些特定問題時較迭代算法來說更自然更優雅的代碼組織方式。作爲程序員工作了多年後,我發現除了用髮量來區分一名程序猿是否優秀以外,能不能理解好遞歸、能不能用遞歸來解決問題也是區分好程序員和差程序員的試金石。很多同學通過學習掌握了某些語言的語法,也能寫一些代碼,但是一遇遞歸就頭大。下面就來給大家總結總結。

談到遞歸,我們的C語言老師可能會說遞歸就是「函數自己調用自己」。比如下面的代碼段:

void foo()
{    
	// 自己調用自己就是遞歸    
	foo();
}

我們知道這個函數要是運行起來,除了讓你的計算機報出一個「堆棧溢出」的錯誤外,其他什麼作用也沒有。在某種程度上說,我們的大腦就是一個計算機。當我們嘗試去理解「自己調用自己」這句話時,大腦也會陷入一個無限的遞歸過程裏,然後「轟」的一聲「堆棧溢出」了,所以也就無法去理解了。當然老師還會告訴你遞歸除了「自己調用自己」外,還有很重要的一部分就是在滿足條件的時候函數會返回,這樣就避免出現無限遞歸的過程了。所以一個完整的遞歸函數組成如下:

void foo()
{    
	// 返回部分,遞歸的退出條件    
	if (condition)    
	{        
		return;    
	}        
	// 遞歸部分    
	foo();
}

接下來老師一般會舉例如何用遞歸計算斐波那契數列數列,一般計算該數列的代碼段如下:

unsigned int fibo(unsigned int n)
{    
	// 返回部分    
	if (n == 0)        
		return 0;    	
	if (n == 1) 
	{        
		return 1;    
	}        
	// 遞歸部分    
	return fibo(n - 1) + fibo(n - 2);
}

代碼只有短短几行,卻可以很優雅的解決這樣的問題。但是你真的能理解嗎?要理解遞歸,我們要退一步,瞭解另一個概念——「分而治之」。稍微瞭解程序設計的人,對這個概念應該不陌生。通俗的說,假如我們有這樣一個問題A,如果能把A分解成一系列比A更容易解決的子問題(A0,A1,A2……An),通過解決子問題(A0,A1,A2……An)來最終達成解決問題A,這個就是「分而治之」。從本質上來說遞歸就是「分而治之」概念的一個應用。以遞歸計算斐波納切數列來舉例,要計算斐波納切數列的第n項該怎麼辦?通過數列的定義我們知道第n項的值等於第n-1項加上第n-2項,所以我們可以把計算第n項這個問題分解成計算n-1項和n-2項兩個子問題。我們知道計算n-1和n-2項要比計算n項更容易點(因爲n-1和n-2都比n要來得小)。那麼n-1項由如何計算呢?根據定義n-1項等於(n-1)-1項和(n-1)-2項的值,好了,我們在這裏碰到了遞歸,好,我們先就此打住。因爲n在一直減少,最終會減到1,再減到0,而第0項和第1項的值我們不用計算就知道的,這就是遞歸終結的時候了。

嘮叨了這麼多,我們可以得出使用遞歸必須要滿足的兩個條件:

要有遞歸公式。
要有終止條件。

遞歸和循環的關係

遞歸和循環存在很多關係。理論上講,所有的循環都可以轉化成遞歸,但是利用遞歸可以解決的問題,使用循環不一定能解決。比如編寫樹和圖的程序就必須用遞歸,雖然循環也可以實現,但那樣做絕對是程序員的噩夢(爲什麼程序猿頭髮少?)。

循環又稱迭代。遞歸算法與迭代算法設計思路的主要區別在於:函數或算法是否具備收斂性!當且僅當一個算法存在預期的收斂效果時,採用遞歸算法纔是可行的。否則就不能使用遞歸算法。所謂收斂性就是指要有終止條件,不能無休止地遞歸下去。

遞歸的優缺點

遞歸的優點是簡化程序設計,結構簡潔清晰,容易編程,可讀性強,容易理解。在很多情況下使用遞歸是必要的,它往往能把複雜問題分解爲更簡單的步驟,而且能夠反映問題的本質。我們一開始可能發現遞歸理解起來也不容易,這是因爲我們的「認知」太少了!

但是遞歸的缺點也很明顯: 速度慢,運行效率低,對存儲空間的佔用比循環多。 嚴格講,循環幾乎不浪費任何存儲空間,而遞歸浪費的空間實在是太大了,而且速度慢。

典型應用

應用一:漢諾塔問題

問題描述:相傳在古印度聖廟中,有一種被稱爲漢諾塔(Hanoi)的遊戲。該遊戲是在一塊銅板裝置上,有三根杆(編號A、B、C),在A杆自下而上、由大到小按順序放置64個金盤。遊戲的目標:把A杆上的金盤全部移到C杆上,並仍保持原有順序疊好。操作規則:每次只能移動一個盤子,並且在移動過程中三根杆上都始終保持大盤在下,小盤在上,操作過程中盤子可以置於A、B、C任一杆上。

#include <stdio.h>

int count;
void hanoi(int n,char a,char b,char c)
{    
	if(n==1)    
	{        
		printf("第%d次移動:%c->%c\n",++count,a,c);    
	}    
	else    
	{        
		hanoi(n-1,a,c,b);        
		printf("第%d次移動:%c->%c\n",++count,a,c);
		hanoi(n-1,b,a,c);    
	}
}

int main(void)
{    
	int n;    
	char a='A',b='B',c='C';    
	printf("請輸入漢諾塔層數\n");    
	scanf("%d",&n);    
	hanoi(n,a,b,c);    
	return 0;
}

應用二:遞歸方法十進制轉化二進制

#include <stdio.h>

void shi_er(unsigned long n)
{    
    int r;    
    r=n%2;    
    if(n>=2)    
    {        
        shi_er(n/2);    
    }    
    putchar(r==0?'0':'1');
}

int main(void)
{    
    shi_er(10);    
    return 0;
}

應用三:階乘

#include <stdio.h>

int factrial(int a)
{   
    int product=1;    
    if (a == 1)    
    {        
        return product;    
    }    
    else    
    {        
        product=a*factrial(a-1);    
    }
}

int main(int argc, char const *argv[])
{    
    printf("%d\n",factrial(3));    
    return 0;
}

更多精彩視頻、文章、嵌入式學習資料,微信關注公衆號 【學益得智能硬件】

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章