C語言中的遞歸函數寫法
遞歸定義
一個函數在它的函數體內直接或間接地調用自身稱爲函數的遞歸調用,而這種函數被稱爲遞歸函數。
直接調用:是指函數直接調用自己。
間接調用:是指函數在遞歸函數調用的下層再調用自己。
例:直接調用,Function1()->調用Function1()
間接調用,Function1()->調用Function2()->Function2()->調用Function1()
講解
遞歸函數簡單的理解就是在不停地調用自己,如果不懂請參照 “講解” 。
其實遞歸函數在使用時只需要明確一點:我寫這個函數是幹嘛用的,比如在用遞歸寫計算N!的C語言程序時,只需要明確我寫的函數是用來計算當前傳入參數N與它前面(N - 1)! 的乘積,寫起來就會很簡單。
而且要明確一點,那就是:遞歸調用的時候必須要有終止條件,也就是遞歸終點。否則程序就會一直遞歸,直到棧溢出。(不用糾結,深入學習後就會知道)
例子
一 、用遞歸算法計算 N 的階乘。
#include<stdio.h>
int F( int n ) //遞歸函數
{
long sum; //
if(n == 0 || n == 1) //遞歸終點。
return 1;
else
{
sum = n * F( n - 1 );
}
return sum; //函數返回sum的值。
}
int main()
{
int n;
scanf("%d", &n); //輸入n
printf("%d! = %d\n", n, F(n));
return 0;
}
這是一個很簡單的遞歸程序,但是包含遞歸的要素:
- 遞歸函數參數的設置與變化,如 sum = n * F(n - 1) 中的 n-1。在別的題中,我們要去找到那個遞推公式,然後合理設置參數,就能很快的寫出遞歸函數。
- 遞歸終點的設置,如 if(n == 0 || n == 1) return 1; 就是很標準的遞歸終點。在寫遞歸函數時,我們必須先想好函數的遞歸終點,然後才能寫遞歸函數。
- 函數的返回值,如本道題中的 return sum; 再別的題目中,要注意考慮到所有的情況,稍微複雜一點題目就很容易漏掉某種情況,然後沒有返回值。
同時補充一點函數的知識,在定義函數的時候,原則上函數的類型就是返回值的類型,但是如果類型不一樣,會強制類型轉換爲定義函數地類型的值然後返回。
二 、貪吃的猴子。
覺得上面的例子很簡單,那麼就讓我們看一道稍微難一點的遞歸題。
有一隻猴子,第一天摘了若干個桃子 ,當即吃了一半,但還覺得不過癮 ,就又多吃了一個。第2天早上又將剩下的桃子吃掉一半,還是覺得不過癮,就又多吃了兩個。以後每天早上都吃了前一天剩下的一半加天數個(例如,第5天吃了前一天剩下的一般加5個)。到第n天早上再想吃的時候,就只剩下一個桃子了。
輸入:
天數n
輸出:
第一天的桃子個數
提述:要先建立遞推公式。
首先,就像我之前說的,明確我們寫的遞歸函數是用來幹嘛的,然後寫出遞推公式,明確遞歸終點,再寫出遞歸函數。這道題就解決了。
那麼,我們寫的遞歸函數是用來幹嘛的?根據題意,猴子每天吃掉前一天的一半加上天數個桃子。
所以我們寫的遞歸函數就是計算第n天時剩餘的桃子個數。然後建立遞推公式:第 n 天剩餘的桃子個數 =2*( 第 n+1 天剩餘桃子數+ n )。
遞歸終點就是最後一天剩餘桃子數爲 1。然後我們就可以開始寫遞歸函數了。
#include<stdio.h>
int n; //定義全局變量n,記錄輸入天數
int Taozi( int day ) //遞歸函數。
{
int num;
if( day == n )
{
return 1; //遞歸終點。
}
else
{
num = 2 * ( Taozi( day + 1 ) + day ); //遞推公式
}
return num; //返回num的值
}
int main()
{
int num; //此處num是局部變量,與遞歸函數中的num 意義不同
scanf("%d", &n); //輸入全局變量n的值
num = Taozi( 1 ); //因爲是從第一天開始算,傳入的參數值爲 1。
if(num > 1)
printf("The monkey got %d peaches in first day.\n", num);
else
printf("The monkey got %d peach in first day.\n", num);
}
這題因爲遞推公式不同會有很多寫法,不一一列舉。 參考上面的提示,好好想一下這個過程,以便更好的理解遞歸的思想。接下來我會在列舉幾道遞歸的題,你們可以好好的琢磨一下這個過程。
三 、求數列的前n項和。
請使用遞歸算法求下列序列的前n項之和。
1 + 1/2 - 1/3 + 1/4 -1/5 ……
輸入:
n
輸出:
序列的前n項和(精確到小數點之後第6位)
像之前一樣,我們來分析一下這個問題:我們寫遞歸函數是用來幹嘛的?這題很顯然,是用來計算 1 / n 與 1 / (n - 1)的和,爲什麼是 n - 1 不是n + 1 ? 其實都是可以的,只是所得到的遞推公式不一樣,遞歸終點不一樣。而這題我們從後往前加,遞推公式很容易得到,遞歸終點也較簡單。所以遞歸終點就是 1 。
注意:奇數項和偶數項的符號不一樣,我們需要判斷一下。
#include <stdio.h>
#include <stdlib.h>
double plus(int x) // 這是一個返回值爲浮點數的函數,寫在後面的話要進行函數申明。
{
double sum = 0;
if(x == 1) // 遞歸終點
{
return 1;
}
else
{
if(x % 2 == 0) //當爲偶數項時,符號爲正
sum = 1.0 / x + plus(x - 1);
else //當爲奇數項時,符號爲負(首項除外)
sum = -1.0 / x + plus(x -1);
}
return sum;
}
int main()
{
double sum = 0;
int n = 0;
scanf("%d", &n);
sum = plus(n);
if(sum != 1)
printf("%06lf\n", sum);
else
printf("1\n");
return 0;
}
四 、子串反向
請編寫一個遞歸函數 reverse(char str[], int start, int end ) ,該函數的功能是將串 str 中下標從 start 開始到 end 結束的字符顛倒順序。假設 start 和 end 都在合理的取值範圍。
例如:
執行前:str[]=”0123456”;start=1 ;end=4
執行後:str[]=”0432156”
要求在該函數中沒有循環。
分析:這題和之前的有些不一樣,是對字符串操作的,那我們如何建立遞推公式呢?
我們想,要從start到end調換,那麼我們可以從兩頭向中間依次進行對調,那麼遞歸的終點就是當指針相交或相等的時候,返回。
這樣我們就可以實現字符串的對調。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void reverse(char *str, int start, int end)
{
int t;
for(t = 0; str[t] != '\0'; t++); //計算字符串的長度
//也可以 t = strlen(str);
char temp; //中間變量
if(end > t)
//當給的end超過字符串長度時,end直接變爲t-1,end是下標所以減一
end = t - 1;
if(end <= start)
{
return; //遞歸終點
}
else
{
temp = str[start];
str[start] = str[end];
str[end] = temp; //交換字符
reverse(str, start + 1, end - 1); //遞歸調用
}
return;
}
int main( )
{ char str[100];
int start, end;
gets(str);
scanf("%d %d", &start, &end);
reverse( str, start, end );
printf("%s\n", str);
return 0;
}
這道題雖然和上面的不一樣,但是所用的遞歸思想是一樣的,寫遞歸函數的方式也相同。只不過沒有了參數。
五 、迴文字符串。
有一種特殊形式的字符串,其正反序相同,被稱爲“迴文字符串”。例如LeveL就是一個迴文字符串。
輸入:
字符串
輸出:
Yes或者No
說明:
如輸出Yes,說明輸入的字符串是一個迴文字符串
輸出No,說明輸入的字符串不是一個迴文字符串
請使用遞歸算法實現。
這道題相比上面的那道題還要簡單一點,如果看了上面的題你有所收穫的話,那麼這道題一定不在話下。
那我們來看看吧,這道題是一道判斷題,判斷是否爲迴文串。操作和上面那題基本類似,就是比較首位往中間相對應的字符是否相同。但是遞歸終點我們可以用另一種表示方法,就是當我們所判斷的子字符串的長度爲 0 或 1 時,結束遞歸。具體我們看代碼。
#include<stdio.h>
#include<string.h>
int Huiwen( char *p , int n ) // n是代表字符串長度的參數
{
if(n == 1 || n == 0)
return 1;
else
{
if( *p == *(p + n - 1) ) //判斷對應位置的字符是否相同
{
return Huiwen( p + 1, n - 2 );
//這裏返回的是下一次調用的返回值,這樣只有當所有對應字符相同時才返回 1。
}
else
return 0;
}
}
int main()
{
char arr[10000] = {0};
int n;
gets( arr );
n = strlen( arr );
if( Huiwen( arr, n) )
{
printf("Yes\n");
}
else
{
printf("No\n");
}
return 0;
}
看完是不是覺得遞歸的寫法多種多樣?是的,不同的參數設置,不同的遞推公式,不同的遞歸終點都會讓寫出來的遞歸程序不一樣。但是裏面所含的遞歸思想是一樣的。明確那幾點,寫出一個遞歸程序其實很簡單。
但是不要看到題就想用遞歸做,所有的遞歸算法都可以用非遞歸算法寫出,多想想們也許會有不一樣的解題方法。
遞歸其實並不難,就那幾步而已!