【C语言】常见递归题,看完递归不再怕

本文主要归纳了一些常见的递归题,若有不对,望请斧正

什么是递归

程序调用自身的编程技巧称为递归( recursion)。

递归做为一种算法在程序设计语言中广泛应用。 一个过程或函数在其定义或说明中有直接或间接 调用自身的 。一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解。 递归策略 :只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。 递归的主要思考方式在于:把大事化小

简单来说递归就是函数自己调用自己,为了便于理解我们看一下最简单的递归

int main()
{
    printf("hehe");
    main();
    return 0;
}

ok,按理来说,我们这个屏幕上会一直死循环打印hehe,但当我们实现代码的时候,我们会发现屏幕上打印一会后,就弹出报错,如下图

此报错中主要是Stack overflow(栈溢出的问题),俗话说得好,从错误中发现问题。这是因为我们的每一次函数调用都是在栈区上开辟空间,一直死循环地申请空间,导致栈区被耗干

所以我们得出:在函数的递归中,必须要有我们的限制条件。

递归的二个必要条件:
1.存在限制条件,当满足这个限制条件的时候,递归便不再继续。 2.每次递归调用之后越来越接近这个限制条件

ok,当我们简单了解了什么是递归的时候,接下来就是我们的一些递归题目

按照顺序打印数的每一位。


#include <stdio.h>

void print(int n)
{
     if (n > 9)        //终止条件
     {
         print(n/10);        
     }
    else
     printf("%d ", n % 10);
}

int main()
{
     int num = 1234;
     print(num);
     return 0;
}

解题思路:题目要我们打印一个数的每一位,如1234,这简单,我们给1234%10不就得到4了么,下一步就是要得到我们的3,我们可以得到4后将1234/10得到123,再给123%10.......大体思路我们了解了,递归就是2大关键:算法与终止条件。现在我们算法解决了,我们再来思考终止条件:当我们把1234变成了1的时候,我们还需要将1/10么?答案是不需要的,所以你的终止条件就是n>9

求字符串的长度

方法一(递归)

int whatlength(char* p)
{
    if (*p != '\0')
        return 1 + whatlength(p + 1);  //这里不能是*p+1,因为如果你p是地址,*p是解引用传过去不是你的地址了
    else
        return 0;    
}

int main()
{
    char arr[] = "abcdef";
    int ret=whatlength(arr);
    printf("%d", ret);
    return 0;
}

方法二(计数)

int whatlength(char* p)
{
    int count = 0;
    while (*p != '\0')
    {
        count++;
        p++;
    }
    return count;
}

int main()
{
    char arr[] = "abcdef";
    int ret=whatlength(arr);
    printf("%d", ret);
    return 0;
}

解题思路:这里我们主要将我们的递归,我们是传过去一个数组名,是首元素的地址,已知我们的字符串数组是个连续的空间,而且末尾存的是\0。利用这些便可实现我们的递归。

为什么不将p+1改成*p+1:*p+1是解引用操作符,改的是你首元素里存的值
为什么不将p+1改成p++:后置++是先使用后++,你传下一个的还是p本身,导致死循环
为什么不将p+1改成++p:前置++可以达到题目要求,但会改变本次函数里形参的值,不好观察

求n的阶乘。(不考虑溢出)


int factoria(int n)
{
    if (n > 1)
    {
        return n * factoria(n - 1);
    }
    else
    {
        return 1;
    }
}

解题思路:我们随便来一个数n=10。我们再建立一个求n阶乘的函数factoria()。当我们n=10进去的时候。是不是要先算我们的10*后面的一大堆,即10*factoria(10-1).这就是我们的算法。

求第n个斐波那契数。(不考虑溢出)

什么是斐波那契数列

int fib(int n)
{
    if (n <= 2)
    {
        return 1;
    }
    else
    {
        return GetNum(n - 1) + GetNum(n - 2);
    }
}

这题只要知道了斐波那契数是怎么取得的就就行

值得注意的是:在使用 fib 这个函数的时候如果我们要计算第50个斐波那契数字的时候特别耗费时间。 使用 factorial 函数求10000的阶乘(不考虑结果的正确性),程序会崩溃。 (上面2个函数)。这是因为我们每次求下一次数的时候,会发生很多的重复运算,导致栈溢出

系统分配给程序的栈空间是有限的,但是如果出现了死循环,或者(死递归),这样有可能导致一 直开辟栈空间,最终产生栈空间耗尽的情况,这样的现象我们称为栈溢出。
如何解决上面问题
1. 将递归改写成非递归。 2. 使用static对象替代 nonstatic 局部对象。在递归函数设计中,可以使用 static 对象替代 nonstatic 局部对象(即栈对象),这不仅可以减少每次递归调用和返回时产生和释放 nonstatic 对象的开销,而且 static 对象还可以保 存递归调用的中间状态,并且可为 各个调用层所访问

实现n的k次方,使用递归实现。

double Pow(int n, int k)
{
    if (k > 0)
    {
        return n * Pow(n, k - 1);
    }
    else if (0 == k)
    {
        return 1.0;
    }
    else
    {
        return 1.0 / Pow(n, -k);
    }
}

解题思路:重点在于我们的k次方上,当我们的k--到0的时候递归结束。而当我们的k为负数的时候,其实就是原来的正数被1.0除

计算一个数的每位之和(递归实现)

int GetNum(int n)
{
    if (n > 9)
    {
        return GetNum(n / 10) + n % 10;
    }
    else
    {
        return n;
    }
}

你在看了前面的时候你会发现这题就是打印数的每一位的变种

字符串逆序(递归实现)

int get_strlen(char* p)
{
    int count = 0;
    while (*p != '\0')
    {
        count++;
        p++;
    }
    return count;
}

void reverse_string(char* p)
{    
    int sz = get_strlen(p);
    char temp = *p;            //1    
    *p = *(p + sz - 1);        //2
    *(p + sz - 1) = '\0';      //3
    if (get_strlen(p + 1) >= 2)
    {
        reverse_string(p + 1); //4
    }
    *(p + sz - 1) = temp;      //5
}

值得注意的是,函数先声明再定义。如果你的get_strlen在reverse_string后面,编译器会给出c28278的警告。切记函数先声明再定义(使用),编译器扫描函数顺序是从上往下的。

解题思路:实现字符串的逆序其实不用递归很简单,无非就是左右2个指针依次靠近来写。在写递归的时候我们应该先从非递归怎么写找灵感,也是最2端的字符交换

如图,我们先把a取出来放到局部变量temp里,再把g放到我们a的位置上。而我们要实现递归的话,我们就不能把a从temp放到g上去,倘若如此你下一次递归的时候,就要再去算bcdefa了,因为你递归是靠判断后面字符的长度来的,你的末尾始终不会变,为了解决这问题,我们可以先把g的位置放上\0

前3步,就相当固定了2端的位置。第4步就是交换其他的。而交换的终止条件就是你的字符串长度>=2(只剩下1个不用交换)。第5步就是把temp的值放到该放的位置上

更多题目练习

这些都是我们的最基础的递归题目,下面这些题目其实都是我们基础题目的变种,感兴趣用递归可以做一做

变种水仙花

小乐乐走台阶

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值