一、分治
分治( Divide-and-Conquer )及分而治之,就是把一个较为复杂的问题分成多个规模较小但结构和原问题相同的或相似的子问题,然后在分别解决这些子问题,最后再将这些子问题合并即可得到原问题的解。在计算机中分治是一种很重要的算法思路,如:快速排序、归并排序……都是建立在分治的思路上。
根据上面定义分治可以分为三个步骤:
- 分解:将原问题分解为若干个和原题结构相同或相似的子问题。
- 解决:递归所有问题,如果问题小到可以直接解决则直接解决它,若不能直接解决则继续分解。
- 合并:将所有子问题的解进行合并以求出原问题的解。
注:分治分解出的的问题应该是相互独立的、没有交叉的,如果分解的问题有相交的地方,那么改题则不能使用分治解决。一般来说分治作为一种算法思想既可以使用递归的手段来实现,也可以通过非递归的方式来实现,不过一般来说使用递归实现分治较为简单(从广义上将,分治分解的问题个数>0
即可,但是严格来讲一般把子问题个数为1
的的情况成为减治)
二、递归
递归(递去归来):就是在函数(或方法)运行的过程中自己调用自己的现象。 在《算法笔记》上提到的递归的定义:“要理解递归,你首先要理解递归,直达你能理解递归”,这应该算是对递归有趣且十分直观的解释。递归就在于反复调用自己函数(或方法),但每次把问题规模缩小直到得到边界数据的结果,然后在返回的路上一步步求出最后的解。由此看来递归非常适分治的思想。
递归两概念&三要素
两概念:①:递归边界②递归式(递归调用)
三要素
- 明确你这个函数(或方法)想要⼲什么,
- 寻找递归结束条件,否则肯进入递归死循环。
- 提取重复的逻辑,缩小问题规模。
递归缺点
递归相对的不同的循环运行效率略低,在递归调用的过程当中系统为每一层的返回点、局部量等开辟了栈来存储。递归次数过多容易造成栈溢出等。
例1:n的阶乘
分析:n! = n * (n-1),(n-1)! = (n-1) * (n-2)! ,(n-2)! = (n-2) * (n-3)!,(n-3)! = (n-3) * (n-4)!,2! = 2 * 1,1! = 0! = 1
由分析得出递归边界应该是当n==1或n==0
当到达边界时返回值应为1
同时递归式应为F(n)=n*F(n-1)
,编写C代码如下:
#include<stdio.h>
int Factorial(int n)
{
if(n <= 1) //递归边界
return 1;
else
return Factorial(n - 1) * n ;//递归式
}
main()
{
int n;
scanf("%d",&n);
printf("%d",Factorial(n));
}
例2:斐波那契数
斐波那契数列,又称黄金分割数列、因数学家列昂纳多·斐波那契以兔子繁殖为例子而引入,故又称为“兔子数列”,它指的是这样一个数列:1,1,2,3,5,8,13……
从这组数可以很明显看出这样一个规律:从第三个数开始,后边一个数一定是在其之前两个数的和。及F(1)=1,F(2)=1,F(n)=F(n-1)+F(n-2)
由上述分析得出递归边界n<=2
返回值为1
,递归式为F(n-1)+F(n-2)
,C代码如下
#include<stdio.h>
int Fibonacci(int n)
{
if(n <= 2) //递归边界
return 1;
return Fibonacci(n-2)+Fibonacci(n-1); //递归式
}
main()
{
int n;
scanf("%d",&n);
printf("%d",Fibonacci(n));
}
奇特的递归:求 求1+2+…+n
题目描述
求 1+2+…+n1+2+…+n,要求不能使用乘除法、forfor、whilewhile、ifif、elseelse、switchswitch、casecase 等关键字及条件判断语句 (A?B:C)。
数据范围:1 <= n <= 1000
思路:利用短路与运算实现递归边界的跳出
#include<stdio.h>
int getSum(int n) {
(n>0) && ( n += getSum(n-1) );
//短路与:首先判断左半部分,若左半部分为真则在执行判断右半部分
//若左半部分为假,则不执行右半部分
return n;
}
main()
{
int n;
scanf("%d",&n);
printf("%d",getSum(n));
}
网友评论