递归的概念
递归函数
用函数自身给出定义的函数
递归算法
一个算法包含对自身的调用
(直接或间接调用)
整数划分
问题:
将正整数n表示成一系列正整数之和:
n=n1+n2+…+nk,其中n1≥n2≥…≥nk≥1,k≥1
正整数n的这种表示称为正整数n的划分
正整数n的不同的划分个数称为正整数n的划分数p(n)
目标:求正整数n的不同划分个数p(n)
思路:
我们假设函数f(n,m)
n为要整数划分的数,m为划分中出现的最大加数
且加数都是大于等于1,可以是一个加数即自己本身
p(6)=11
6
5+1
4+2,4+1+1
3+3,3+2+1,3+1+1+1
2+2+2,2+2+1+1,2+1+1+1+1
1+1+1+1+1+1
根据n和m的关系,考虑以下几种情况:
(1)当n=1时,不论m的值为多少(m>0),只有一种划分即{1};
(2) 当m=1时,不论n的值为多少,只有一种划分即n个1,{1,1,1,...,1};
(3) 当n=m时,根据划分中是否包含n,可以分为两种情况:
(a). 划分中【包含n】的情况,只有一个即{n};
(b). 划分中【不包含n】的情况,这时划分中最【】大的数字也一定比n小】,即n的所有(n-1)划分。
因此 f(n,n) =1 + f(n,n-1);
例如:f(6,6)=1+f(6,5)
(4) 当n<m时,由于划分中不可能出现负数,因此就相当于f(n,n);例如f(3,5)=f(3,3)
(5) 但n>m时,根据划分中是否包含最大值m,可以分为两种情况:
(a). 划分中包含m的情况,即{m, {x1,x2,...xi}}, 其中{x1,x2,... xi} 的和为n-m,可能再次出现m,因此是(n-m)的m划分,因此这种划分 ,个数为f(n-m, m);
(b). 划分中不包含m的情况,则划分中所有值都比m小,即【n的(m-1)】划分,个数为f(n,m-1);
因此 f(n, m) = f(n-m, m)+f(n,m-1);
综合以上情况,我们可以看出,上面的结论具有递归定义特征,其中(1)和(2)属于回归条件,(3)和(4)属于特殊情况,将会转换为情况(5)。而情况(5)为通用情况,属于递推的方法,其本质主要是通过减小m以达到回归条件,从而解决问题。其递推表达式如下:
f(n, m)= 1; (n=1 or m=1)
f(n, n); (n<m)
1+ f(n, m-1); (n=m)
f(n-m,m)+f(n,m-1); (n>m)
最后p(n)==f(n,n)
#include <stdio.h>
int split(int n,int m)
{
if (n==1||m==1)
return 1;
else if (n<m)
return split(n,n);
else if (n==m)
return 1+split(n,n-1);
else
return split(n,m-1)+split(n-m,m);
}
int main()
{
int n;
printf("输入划分数:");
scanf("%d",&n);
printf("整数划分为:%d\n",split(n,n));
return 0;
}
汉诺(Hanoi)塔问题
问题:任何时候,在小圆盘上都不能放大圆盘,且在三根柱子之间一次只能移动一个圆盘。
思路:
假设总共需要移动n个盘子
1.将A柱上的n-1个盘子借助C柱移向B柱
2.将A柱上仅剩的最后一个盘子移向C柱
3.将B柱上的n-1个盘子借助A柱移向C柱
Hn是n个盘子从a移动到c的次数
-
n=1 a的盘移动到c;
即直接移动不用借助其他柱子 所以不写move
-
n=2 将a柱上面的小盘移到b,将a最下面的大盘移到c,再将b上的移到c ,移了3次,H2=3
-
n>2 将a柱上面的n-1个小盘移到b(借助c),将a最下面的大盘移到c,再将b上的n-1个盘移到c(借助a) , 总共移动
Hn-1 + 1 + Hn-1
所以Hn=2Hn-1 + 1
; -
将n个圆盘从a柱子上借助c柱子移动到b柱子上
move(n-1,a,c,b) move(n-1,b,a,c)
代码实现
#include <stdio.h>
int f(int n )
{
if (n==1)
return 1;
if (n==2)
return 3;
else
return 2*f(n-1)+1;
}
int main()
{
int n;
scanf("%d",&n);
printf("%d",f(n));
return 0;
}
#include<stdio.h>
//将n个圆盘从x柱子上借助y柱子移动到z柱子上
void move(int n, char x, char y, char z)
{
if(n==1) //圆盘只有一个时,只需将其从x塔移到z塔
printf("圆盘编号%d :从 %c 移动到 %c\n", n, x, z);
else
{
move(n-1, x, z, y); //递归,把x塔上编号1~n-1的圆盘移到y上,以z为辅助塔
printf("圆盘编号%d :从 %c 移动到 %c\n", n, x, z);
move(n-1, y, x, z); //递归,把y塔上编号1~n-1的圆盘移到z上,以a为辅助塔
}
}
int main()
{
int n; //n代表圆盘的个数
/*A,B,C分别代表三个柱子*/
char ch1 = 'A';
char ch2 = 'B';
char ch3 = 'C';
printf("请输入圆盘的个数:");
scanf("%d", &n);
move(n, ch1, ch2, ch3);
return 0;
}
递归式
当一个算法包含对自身的递归调用时,其
运行时间
通常可以用递归式
来表示
递归式解法
代换法(substitution method)
递归树方法(recursion-tree method)
主方法(master method
代换法
步骤
- 猜测解的形式
- 用数学归纳法证明之
- 只适用于解的形式很容易猜的情形
- 如何猜测则需要经验
不常用 (例子可不看)
递归树- 每一个节点代表递归函数调用集合中一个 子问题的代价,将所有层的代价相加得到 总代价,递归式作为孩子结点(
T(xn)部分
)O(xn)部分作为父结点
。
例子:
T(n)=4T(n/2)+n 的递归树
- 该树长度为 log2n
因为原本的问题规模为 n ,到了树的最底层,为 1 了,也就是说,每次往下一层,规模变为 1/2,假设它变成 i 个节点,则 n / 2i = 1 ,即 n = 2i ,所以 i = log2n
- 图中所有节点之和为:
n+2n+4n+8n+16n+…(2i)n = (1+2+4+…+2i)n 根据等比数列求和公式可得: 1*(1-2i)/(1-2) = 2i-1 因为i=log2n 所以2^(log2n) - 1
所以图中所有节点之和为:n(n-1)=n2-n 所以时间复杂度为O(n2)
主方法
主定理1
主定理2
直觉上来说两个函数的较大者
决定了递归式的解,如果两个函数相当,则乘上一个对数因子logn
。
需要注意的是主方法并没有覆盖所有可能性,所有的大于和小于都是`多项式意义上的大于和小于`,对于有些递归式夹在三种情况的间隙中,是无法用主方法来求解的。下面解释一下什么是多项式意义上的小于和大于:
f(x)多项式大于g(x):存在实数e>0,使得`f(x)>g(x)*n^e`
f(x)多项式小于g(x):存在实数e>0,使得`f(x)<g(x)*n^e`
举个例子,有递归式T(n) = 2T(n/2)+nlgn, = n,nlgn/n = lgn,此时不存在e>0,使得nlgn>nn^e,所以就不能用主方法求解。
主方法的应用:
求递推式的例子
假设mergesort总体时间是T(n) 所以对半mergesort是T(n/2) merge是O(n) ,当c无穷大时,O(n)==cn,且因为序列不断二分进行mergesort所以序列长度n=2^k