分治策略和递归

目录

一、分治策略和递归

1.分治概念

2.递归的概念

3.分治策略的特征:

3.1分治法步骤:

4.示例

4.1  求解n的阶乘(不考虑int溢出)

4.2 Fibonacci数列

 5.练习

注意事项


一、分治策略和递归

1.分治概念

       任何可以用计算机求解的问题所需的计算时间都与其规模有关。问题的规模越小,解题所需的计算时间往往也越少,从而也较容易处理。例如,对于n个元素的排序问题,当n =1时,不需任何计算;n=2时,只要做一次比较即可排好序;n=3时只要进行两次比较即可;..…而当n较大时,问题就不那么容易处理了。要想直接解决一个较大的问题,有时是相当困难的。

       分治法的设计思想是,将一个难以直接解决的大问题,分割成一些规模较小的相同问题,以便各个击破,分而治之。如果原问题可分割成k个子问题,1<k≤n,且这些子问题都可解,并可利用这些子问题的解求出原问题的解,那么这种分治法就是可行的。由分治法产生的子问题往往是原问题的较小规模模式,这就为使用递归技术提供了方便。在这种情况下,反复应用分治手段,可以使子问题与原问题类型一致而其规模却不断缩小,最终使子问题缩小到很容易求出其解。由此自然导致递归算法。

       分治与递归像一对孪生兄弟,经常同时应用在算法设计之中,并由此产生许多高效算法。

2.递归的概念

       递归:若一个函数直接或间接地调用自己,则称这个函数是递归的函数。(简单地描述为“自己调用自己”)。

3.分治策略的特征:

分治法所能解决的问题一般具有以下四个特征:

  • 该问题的规模缩小到一定的程度就可以容易地解决。
  • 该问题可以分解为若干个规模较小的相同问题。
  • 使用小规模的解;可以合并成,该问题原规模的解。
  • 该问题所分解出的各个子规模是相互独立的。

3.1分治法步骤:

在分治策略中递归地求解一个问题,在每层递归中应用如下三个步骤:

  • 分解︰将问题划分成一些子问题,子问题的形式与原问题一样,只是规模更小。
  • 解决︰递归地求解子问题。如果子问题的规模足够小,则停止递归,直接求解。
  • 合并:将小规模的解组合成原规模问题的解。

4.示例

4.1  求解n的阶乘(不考虑int溢出)

  • 阶乘可递归定义为:

  •  递归程序:
int fac(int n)
{
    if (n <= 1)
        return 1;
    else return fac(n - 1) * n;
}

  •  图解递归过程(代码的调动过程)

  •  图解递归过程(栈帧的动态调用过程)

  • 总结:

函数被调用,不管是自己调用自己,还是被其他函数调用,都将会给被调用函数分配栈帧。不存在无穷递归。即递归函数必须要有一个是递归结束的出口(要有递归中止的条件语句)。问题的规模不要太大,递归过深,引起栈溢出。

4.2 Fibonacci数列

  • 分析:无穷数列1,1,2,3,5,8,13,21,34,55...称为Fibonacci数列。
  • 递归定义:
  • 递归程序:
    int fib(int n)
    {
        if (n == 1 || n == 2)
            return 1;
        else return fib(n - 1) + fib(n - 2);
    }

 非递归程序:

int fib(int n)
{
    int a = 1, b = 1, c = 1;
    for (int i = 3; i <= n; i++)
    {
        c = a + b;
        b = a;
        a = c;
    }
    return c;
}
  • 时间复杂度分析:O(n)(非递归)    O(2^n)   S(n)-(递归)
  • 递归程序的优化

 

int fibon(int n, int a, int b)
{
    if (n == 1 || n == 2)
        return a;
    else return fibon(n - 1, a + b, a);
}
int fibon(int n)
{
    int a = 1, b = 1;
    return fibon(n, a, b);
}

 5.练习

输入一个整数(无符号整型),用递归算法将整数倒序输出。

分析:现在用递归过程的递推步骤中用求余运算将整数的各个位分离,并打印出来。

1.非递归代码,使用迭代

void PrintInt(size_t n)
{
    while (n != 0)
    {
        printf("%u", n % 10);
        n = n / 10;
    }
}

2.递归代码

void backward(size_t n)
{
    if (n > 0)
    {
        printf("%u", n % 10);
        backward(n / 10);
    }
}

结论:余数总是取当前整数中最右一位,所以先输出余数后递归可实现倒序输出。如果先递归后输出余数。则是在回归的过程中输出,实现的就是正序输出。

注意事项

1.限制条件:在设计一个递归过程时,必须至少有一个可以终止此递归的条件,用来结束递归调用过程。否则递归函数将陷入执行无限循环的高度危险之中。

2.内存使用:应用程序的栈空间是有限。函数在每次调用它自身时,都会开辟栈帧空间以保存其形参值,局部变量值,还有现场保护信息。如果这个进程无限持续下去,最终会导致栈溢出的错误。

3.效率:几乎在任何情况下都可以用循环替代递归。使用循环相对于使用递归调用可以大幅提高性能。

4.间接递归:如果两个函数相互调用,可能会使性能变差,甚至产生无限递归。此类设计所产生的问题与单个递归过程所产生的问题相同,但更难检测和调试。

5.测试︰在编写递归过程时,应非常细心地进行测试,以确保它总是能满足某些限制条件而终止递归过程。您还应该确保不会因为过多的递归调用而耗尽内存。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值