迭代和函數的遞歸的學習

對於模塊化程序的初步瞭解

一個真正的程序裏面白喊自定義函數,也包含對應的頭文件,還有函數定義界面。於是創建了個簡單的加法函數。
函數的聲明

#ifndef __ADD_H__
#define __ADD_H__

//函數的聲明
int Add(int x, int y);

#endif

我將這個聲明放在目錄爲add.h的文件下,其中的

#ifndef __ADD_H__
#define __ADD_H__

#endif

作用是爲了防止函數連續被調用時整個函數的定義段被連續搬運,導致主程序的代碼量過大。函數的調用原理是,講函數定義目錄下的代碼經過頭文件搬運到需要用的地方,然後執行。
函數的定義

int Add(int x, int y)
{
    return (x + y);
}

放在目錄爲add.c的目錄下,將對應功能的函數放在具有提示意義的目錄下面,能夠與他人更好的配合,具有可讀性。
主函數

#include <stdio.h>
#include "add.h"
int main()
{
    int a = 15;
    int b = 25;
    printf("sum=%d\n",Add(a, b));

    return 0;
}

引用自定義函數的頭文件時,用的是雙引號 "" 。

最簡單的遞歸

#include <stdio.h>
int main()
{
    printf("haha\n");
    main();
    return 0;
}//遞歸常見的錯誤,棧溢出,stack overflow

這是一個沒有限制條件的遞歸,程序運行一會兒就會自己停止,並且彈出警告窗口,原因是,程序運行的時候,會將運行時產生的局部變量存在名爲堆棧的一個內存區域,這個沒有限制的遞歸,不斷的調用自身,不斷的打印“haha”,就會將堆棧佔滿。
而這個程序也讓人能理解,遞歸,就是函數對於自身的調用,用現在的網絡流行語,俗稱套娃。

設計一個遞歸函數 分別打印出1234裏的1 2 3 4

void print(int n)
{
    if (n > 9)
    {
        print(n / 10);
    }
    printf("%d ", n % 10);

}
#include <stdio.h>
int main()
{
    int num = 0;
    scanf("%d", &num);
    print(num);
    return 0;
}

第一次要使用遞歸進行實現某一個功能其實是 很沒有頭緒的,儘管聽老師講完了 ,程序也跟着打了出來,也看着調試一步步的調了,眼看着代碼一行一行的走,尤其是函數裏面的調用,到了最後一層,不滿足調用條件的時候,程序運行窗口就一個一個的把字符打印出來了,我前期是比較沒有理解,程序執行到這個地方是怎麼一層一層的返回去的,就是忘記了程序執行到了哪裏,老師畫圖講解之後,纔算是明白,一層一層的進來,也是要一層一層的出去。當最後一層不再滿足條件執行完畢彈出之後,彈出到上一層之後,繼續執行下一條語句,以此類推。

不創建臨時變量,求字符串長度

先是寫一個能實現求字符串從長度的函數,不考慮遞歸
最簡單的當然是直接使用庫函數計算字符串長度

#include <string.h>
int main()
{
        strlen();
}

接下來用自定義函數

int my_strlen(char* str)
{
    int count = 0;
    while (*str != '\0')
    {
        count++;
        str = str + 1;
    }
    return count;
}

整個字符串是無法直接被函數調用的,只能講字符串的地址作爲指針變量,指向字符串的第一個字符,調用進入函數之中,每次用完之後+1,直到看到‘\0’字符,字符串的長度計算停止。count就作爲一個計數,記錄字符串的長度。可是我們需要的是一個不用臨時變量的函數實現這一功能。

#include <stdio.h>
int my_strlen(char* str)
{
    if (*str != '\0')
        return 1+my_strlen(str + 1);
    else
        return 0;
}//未使用臨時變量進行計數,實現了求字符串長度
int main()
{
    char arr[] = "bit";
    int len = my_strlen(arr);//傳送過去的是數組第一個元素的地址,並不是整個數組
    printf("len = %d\n", len);
    return 0;
}

遞歸還是一樣,用畫圖的方法解釋起來更容易理解,將程序執行的過程可視化,增強理解,有一定的限制條件,而且每一層調用函數都在使得限制條件更加接近不滿足if語句,最終能夠停止遞歸過程。俗稱停止套娃。

遞歸和迭代 求階乘

當理解了前面兩個遞歸的例子之後,我也獨立寫出了這個求階乘的遞歸代碼,大概思路爲,想要求出n!,先要求出(n-1)!,想要求出(n-1)!,先要求出(n-2)!,快進到想要求出2!,就先求出1!,所以還是對自身的不斷調用,代碼如下:

#include <stdio.h>
int Fac1(int n)
{
    int num = 1;
    int i = 0;
    for (i = 1; i <= n; i++)
    {
        num = i * num;
    }
    return num;
}
int Fac2(int n)
{
    int ret = 1;
    if (n != 0)
         ret = n * Fac2(n - 1);
    else
        return ret;
}
int main()
{
    int n = 0;
    scanf("%d", &n);
    printf("%d!=%d\n",n, Fac1(n));
    printf("%d!=%d\n",n, Fac2(n));
    return 0;
}

其中也用了個for循環寫了另一個函數實現目的。

求斐波那契數列的第n個

斐波那契數列,1 1 2 3 5 8...簡單的說,就是想知道第n個,就得先知道第n-1個和第n-2個,要知道第n-1個和第n-2個,就得知道第n-2和n-3個,第n-3和n-4個...以此類推,可是遞歸求第n個,需要運算的次數,就是2^n+2^(n-1)+...+2^2+2次運算,當求到第40個以上的時候,程序已經有了很明顯的等待,需要等待才能出結果,計算的次數其實已經很大,n每+1,運算次數就會多出兩倍,運算時間也會多出兩倍,代碼如下:

#include <stdio.h>
int Fib1(int n)
{
    if (n <= 2)
        return 1;
    else
        return  Fib1(n - 1) + Fib1(n - 2);
}
int Fib2(int n)
{
    int a = 1, b = 1, c=0;
    int i = 0;
    if (n > 2)
    {
        for (i = 3; i <= n; i++)
        {
            c = a + b;
            a = b;
            b = c;
        }
        return c;
    }
    else
        return 1;
}
int main()
{
    int n = 0;
    scanf("%d", &n);
    int f2 = Fib2(n);
    printf("第%d個斐波那契數列:%d\n", n, f2);
    int f1 = Fib1(n);
    printf("第%d個斐波那契數列:%d\n", n, f1);
    return 0;
}

所以以上代碼中還有一個,不用遞歸,直接用迭代實現的函數,一運行就能看到,兩個函數實現目標的時間差,所以當,遞歸之中函數調用的次數過大時,運算量巨大,就很有必要使用其他手段實現目標。

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