递归思想求arr中最大值,递归函数先调用处理几部分,然后整理和归并信息返回
提示:递归思想,非玄学,必定要学会的递归思想,这是极其重要的算法范畴
递归思想:递归函数
递归函数f(arr,L,R):
最简单的二分递归:把arr(N长度)的L–R范围分为2部分,每一个部分数据规模是N/2
(1)先去递归调用左边部分:f(arr,L,mid),得到一个结果x1
(2)再去递归调用右边部分:f(arr,mid+1,R),得到一个结果x2
(3)最后,整理和归并x1和x2,得到f(arr,L,R)的结果,返回
(4)当L=R时,就剩一个元素,返回仅有一个元素时应该返回的结果,这就是base case:基本状况。
一、举例:
求无序arr中的最大值
示例:3 2 1 5 4 2 6
返回最大值:6
二、解题
暴力索引
直接o(n)索引找,也是可以的
递归寻找
当然,递归找找试试:
按照上面的递归思想,分2部分!
f(arr,L,R)代表,请寻找并返回arr中L–R上的最大值!
(1)mid=0+6/2=3,递归掉用f(0–3),f(4–6)
(2)在f(0–3)这里,又将其分为2部分:mid=0+3/2=1,递归掉用f(0–1),f(2–3)
与此同时,f(4–6)又可以分为2部分:mid=4+6/2=5,递归调用f(4,5),f(6,6)
(3)显然,此时f(6,6)满足了基本条件L=R,故返回一个最大值,自然也就是它本身:f(6,6)=6;
(4)继续划分:
f(0–1)可以划分为f(0,0)=3,f(1,1)=2;
f(2–3)可以继续划分为f(2,2)=1,f(3,3)=5;
f(4,5)可以继续划分为f(4,4)=4,f(5,5)=2;
目前他们全部都是到了叶节点了
也都拿到了各自最大值,开始返回结果:
(5)f(0–1)=max(f(0,0)=3,f(1,1)=2)=3
f(2–3)=max(f(2,2)=1,f(3,3)=5)=5
f(4,5)=max(f(4,4)=4,f(5,5)=2)=4
继续返回整合:
f(0–3)=max(f(0–1),f(2–3))=3
f(4–6)=max(f(4,5),f(6,6))=6
最终:
f(0,6)=max(f(0–3),f(4–6))=6
这就是递归之后的结果
手撕代码:
//复习:递归寻找最大值
public static int maxVal(int[] arr, int L, int R){
//base case
if (L == R) return arr[L];//就本身
//L<R:左右递归调用2次
int mid = L + ((R - L) >> 1);//中点
int leftMax = maxVal(arr, L, mid);
int rightMax = maxVal(arr, mid + 1, R);
//整理归并返回
return Math.max(leftMax, rightMax);
}
public static void test2(){
int[] arr = {3,2,1,4,5,6};
int ans = maxVal(arr, 0, arr.length - 1);
System.out.println(ans);
}
public static void main(String[] args) {
test2();
}
结果:6
递归思想的时间复杂度
根据上面的例子:
不妨设,一个递归函数,里面要调用几次?a=2次
每一次调用递归,其数据规模为N/b,这里b=2,因为每次都是均分的
除了两次递归调用意外的操作,max整理归并返回的复杂度为o(n^d),这里d自然是0,因为max操作就o(1)
则,我们得到递归函数的时间复杂度计算公式T(n):
T(n)=aT(N/b)+o(n^d)
最终:
Master复杂度公式:
死记硬背!!!
(1)log(b,a) > d,则T(n)=o( n^(log(b,a) )
(2)log(b,a) = d,则T(n)=o( n^d * log(n) )
(3)log(b,a) < d,则T(n)=o( n^d )
我们用一个直接点的,看图:
总之,log(b,a)越大,d越小,复杂度越小,
log(b,a)越小,d越大,复杂度越大,
log(b,a)是中间值,则复杂度中间带log(n)
所以master公式与log(b,a)成反比,与d成正比
比如:此题:
a=2,【这是递归调用的次数】
b=2,【每次递归调用问题的规模是N/2,b=2】
d=0,max归并整理操作只需要o(1),所以o(n^d)=o(n0)=o(1)
我们求一下:
log(b,a)=log(2,2)=1
显然,log(b,a)=1>d=0
d小,复杂度就小
T(n)= o( n^(log(b,a) ) = o(n1)=o(n)
和暴力索引一样,但是我们这里就是为了将递归思想的。
其实,常规情况下,无论abd怎么取值,无非就三种情况:
上面的情况1已经有了a=2,b=2,d=0
上面的情况2:那就不妨设log(b,a)=d,也就是说,a=2,b=2,d=1
d=1意味着递归最后还有一个o(n)的遍历骚操作【因为o(n^d)=o(n)】
则T(n)= o( n^d * log(n) ) = o( n * log(n) )
上面的情况3 ,log(b,a)<d,也就是说,a=2,b=2,d=2
d=2意味着递归最后还一个o(n^2)的骚操作,那挺狗的,复杂度就很高了
T(n)=o( n^d ) = o(n**2)
因此呢,大致就记住这三种情况,很少有例外,有的话,我们再说!
总结
提示:重要经验:
1)递归思想并非玄学,其实就是分块去求结果,最后归并整理信息返回
2)Master公式要牢记,并理解常用的3中时间复杂度