深入理解递归

递归和循环的区别

  递归和循环在设计思路上是有区别的,例如计算 1 - n 的累加,利用 for 循环来做。

#include <stdio.h>
#include <stdlib.h>

int main()
{
    int n = 5;
    int i = 1;
    int nSum = 0;

    for (; i <= n; i++)
    {
        nSum = nSum + i;
    }

    system("pause");
    return 0;
}

  for 循环是一个线性迭代的思路,数值一个一个相加,1+2+3+4+5 ,求出 5 的累加。而递归的思路则不是一条线做迭代,思路如下,求 5 的累加 Sum(5),只需知道 4 的累加(Sum(4))是多少,然后结果加 5;这个时候问题就回到了 4 的累加 (Sum(4))是多少,只需知道 3 的累加 (Sum(3) )是多少,然后结果加 4;依次 Sum(3) 等于 2 的累加 (Sum(2)) 加 3 ;Sum(2) 等于 1 的累加 (Sum(1) )+ 2 。

  这时,需要注意一点,递归的设计,必须在越推越深,越推越深……到最内层时,必须要有个常识性结果,这里就是 Sum(1) = 1,否则,在编写递归程序时,会出问题的。

  根据上面推理过程,并且因为已知 Sum(1) = 1,所以 Sum(2) = 3,Sum(3) = 6,Sum(4) = 10,Sum(5) = 15,最后结果就等于 15。

在这里插入图片描述
  由上图可以看出,递归的执行线路是一个类 U 型。在不计函数本身的开销上,行动线路就已经比循环多一倍了。所以递归的效率是低于循环的,用递归解决这一类问题很显然是不划算的。

  根据上面的推理过程,用递归实现 5 的累加。首先写出口, n 等于 1 时返回 1,n 等于其他时,n 的累加等于 n-1 项的累加 (GetSum(n-1))加 n。


#include <stdio.h>
#include <stdlib.h>

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

int main()
{
  int n = 5;
  int i = 1;
  int nSum = 0;

  nSum = GetSum(n);

  system("pause");
  return 0;
}

深入理解递归的执行过程

  通过调试观察递归的执行过程,按 F10 进行单步调试,此时在 main 函数里面, n 的值等于 5。按 F11 进入 GetSum 函数的内部。

在这里插入图片描述
  因为 n 等于 5,不等于 1 ,直接跳到 return GetSum(n-1) + n 这里,因为要求出 GetSum(n-1) + n 的结果,就先要求出 GetSum(n-1) 的返回值。

在这里插入图片描述

  继续按 f11 进入函数 GetSum 的内部 ,虽然和前一步函数名是一样的,但是传入的参数却是不同的,此时 n 等于 4,通过栈窗口可以看到,此时函数在 GetSum(int 0x00000004) 这个位置,而前一个函数在 GetSum(int 0x00000005) 这个位置。

在这里插入图片描述

  依次按 F11 ,可以看到依次进入,GetSum(int 0x00000003),GetSum(int 0x00000002),GetSum(int 0x00000001) 的函数内部。

  当执行到,n 等于 1,函数所处 GetSum(int 0x00000001) 时。

在这里插入图片描述
  因为 n == 1,所以 return 1,根据函数栈可以看出,当退出 GetSum(int 0x00000001) 之后,进入GetSum(int 0x00000002),当退出 GetSum(int 0x00000002) 之后,进入GetSum(int 0x00000003)……依次退出函数栈,如下图,直到回到 main 函数为止。类似于盗梦空间的片段,普通生活(main 函数),如果结束了,人就死了(程序结束)。从第一层梦(第一次函数调用)进入第二层梦(函数被调用两次,由第一层函数调用),再进入第三层梦(函数被调用三次,由第二层函数调用)……依次可进入更深层次梦境。当满足一定条件时,退出梦境,也依次退出,由第三层梦退回第二层梦,第二层梦退回第一层梦,第一层梦退回到现实生活。

循环适合解决线性问题,递归适合解决非线性问题

  由分析和执行过程可以看出,用递归解决累加问题,既耗费时间又耗费脑力,并且效率不高,也就是俗话说的费力不讨好。但是为什么老师还有一些教课书上都在说递归的好呢?这里的好,必须是要分场和的,在这之前,先介绍两种问题,分别是线性问题和分线性问题。

  线性问题:有且只有一个前驱,有且只有一个后继,例如:累加问题,编程中的数组,火车的车厢。

  非线性问题:人们常说,条条大路通罗马,就是一个非线性问题。如下图,A 为出发地点,A 可以经过 C 到达罗马,A 也可以经过 B 再经过 D 到达罗马。因为每个节点可以有多条路径,多种选择,这种结构就是非线性。

在这里插入图片描述
  从分析和调试过程可以得出结论:递归不适合解决线性问题,循环适合解决线性问题。下面用大家都知道的斐波拉契数列,来再次理解递归,并证明递归不适合线性问题。

  斐波拉契数列规定了数列的前两项是 1,从第 3 项开始,第 n 项等于 n-1 项加 n-2 项。所以,第 3 项是 2 ,第 4 项是 3,第 5 项是 5,第 6 项是 8,第7 项是 13。

  在拉波拉契数列中 8 的前面一定是 5 ,后面一定是 13,所以斐波拉契数列是一个线性问题,适合使用循环解决问题。

  用循环编写程序,打印斐波拉契数列 46 项:

#include <stdio.h>
#include <stdlib.h>

int main()
{
    // 斐波拉契数列
    // 1 1 2 3 5 8 13
    unsigned int f1 = 1;
    unsigned int f2 = 1;
    unsigned int f3 = 0;
    int i = 0;

    printf("第1项\t1\r\n");
    printf("第2项\t1\r\n");

    while (i < 44)
    {
        f3 = f1 + f2;
        printf("第%d项\t%d\r\n", i + 3, f3);
        f1 = f2;
        f2 = f3;
        i++;
    }
    system("pause");
    return 0;
}

   编译执行,秒出结果

  用递归解决,打印斐波拉契数列 46 项。为了便于分析,假设只打印斐波拉契数列第 5 项的值,第 5 项等于第 4 项加第 3 项,第 4 项等于第 3 项加第 2 项,第 3 项等于第 2 项加第 1 项,又因为第 2 项和第 1 项都是 1 ,所以可得出第五项的结果。

在这里插入图片描述

  由此,可写出程序:首先写出口, n 等于 1 或 n 等于 2 时返回 1,n 等于其他时,第 n 项等于第 n-1 项加 n-2 项。


#include <stdio.h>
#include <stdlib.h>

int fib(int n)
{
    if (n == 1 || n == 2)
    {
        return 1;
    }

    return fib(n - 1) + fib(n - 2);
}

int main()
{
    // 斐波拉契数列
    // 1 1 2 3 5 8 13
    unsigned int f1 = 1;
    unsigned int f2 = 1;
    unsigned int f3 = 0;
    int i = 3;

    printf("第1项\t1\r\n");
    printf("第2项\t1\r\n");

    while (i <= 46)
    {
        f3 = fib(i);
        printf("第%d项\t%d\r\n", i, f3);
        f1 = f2;
        f2 = f3;
        i++;
    }

    system("pause");
    return 0;
}

  编译执行,程序运行起来非常的慢,运行到 40 项就需要花费很长的时间。

递归实现走迷宫

  递归非常适合解决非线性问题,例如走迷宫,走迷宫在十字路口时,可以有 4 个选择,这是一个非线性问题。用递归实现走下图迷宫。迷宫设置规则 1 位墙, 0 为路,2 行 2 列是迷宫的起始位置,$ 为迷宫的出口

 11111111111111111111111
 10101001111101111111111
 10101010000000000100001
 10101010111101110101101
 10000000111100000101111
 101001100000011101011$1
 10110110110110110101101
 10000000100110110000001
 11111111111111111111111

  首先用程序打印这个迷宫。思路是:定义迷宫,求出迷宫的行数,然后逐行打印。

#include <stdio.h>
#include <stdlib.h>

void ShowMaze(char szMaze[][24], int nCount)
{
  int i = 0;
  while (i < nCount)
  {
    printf("%s\r\n", szMaze[i]);
    i++;
  }
}

int main()
{
  char szMaze[][24] = {
    "11111111111111111111111",
    "10101001111101111111111",
    "10101010000000000100001",
    "10101010111101110101101",
    "10000000111100000101111",
    "101001100000011101011$1",
    "10110110110110110101101",
    "10000000100110110000001",
    "11111111111111111111111"
  };

  int nRaw = sizeof(szMaze) / sizeof(szMaze[0]);
  ShowMaze(szMaze, nRaw);
  system("pause");
  return 0;
}

  算法思想:逆时针探路,分别走的路线是 x-1,y 和 x,y+1 和 x+1,y 和 x,y-1,如果等于 $ 或者 0 就在往前走一步,然后调用函数本身,传的值是往前走一步的坐标,当前坐标被置为白脸。如果往前走一步的值,不等于 $ 或者 1 ,则返回调用的函数,当前的坐标被置为黑脸。直到坐标本身等于 $ 走出迷宫为止,程序如下:

#include <stdio.h>
#include <stdlib.h>

void ShowMaze(char szMaze[][24], int nCount)
{
  int i = 0;
  while (i < nCount)
  {
    printf("%s\r\n", szMaze[i]);
    i++;
  }
}

void Maze(char szMaze[][24], int x, int y, int nRaw)
{
  system("cls");
  ShowMaze(szMaze, nRaw);
  system("pause");
  
  if (szMaze[x][y] == '$')
  {
    system("cls");
    ShowMaze(szMaze, nRaw);
    printf("恭喜过关\r\n");
    system("pause");
    exit(0);
  }
  
  szMaze[x][y] = '\1';

  if (szMaze[x - 1][y] == '$' || szMaze[x - 1][y] == '0')
  {
    Maze(szMaze, x - 1, y, nRaw);
  }
  if (szMaze[x][y + 1] == '$' || szMaze[x][y + 1] == '0')
  {
    Maze(szMaze, x, y + 1, nRaw);
  }
  if (szMaze[x + 1][y] == '$' || szMaze[x + 1][y] == '0')
  {
    Maze(szMaze, x + 1, y, nRaw);
  }
  if (szMaze[x][y - 1] == '$' || szMaze[x][y - 1] == '0')
  {
    Maze(szMaze, x, y - 1, nRaw);
  }
  
  szMaze[x][y] = '\2';
  
  system("cls");
  ShowMaze(szMaze, nRaw);
  system("pause");
  return;
}

int main()
{
  char szMaze[][24] =
  {
    "11111111111111111111111",
    "10101001111101111111111",
    "10101010000000000100001",
    "10101010111101110101101",
    "10000000111100000101111",
    "101001100000011101011$1",
    "10110110110110110101101",
    "10000000100110110000001",
    "11111111111111111111111"
  };
  
  int nRaw = sizeof(szMaze) / sizeof(szMaze[0]);
  ShowMaze(szMaze, nRaw);
  
  Maze(szMaze, 1, 1, nRaw);
  printf("本迷宫无解\r\n");
  system("pause");
  return 0;
}

  白脸构成的线路,是完整的走迷宫路线

在这里插入图片描述

想了解更多 C/C++ 或 网络安全知识,请关注公众号:大人物小城梦

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值