void foo(float[] array, int start, int end){
if((end-start) <=1) return;
int x = (end-start) / 5;
int y = 2*x;
int z = 4*x;
foo(array,start,start+y);
for(index = y; index < z; index++){
array[index]++;
}
foo(array, start+z, end);
}
我得到的是循环初始化执行N + 1次,而内部代码执行N次,这意味着时间复杂度将是O((N + 1)*(N))= O(n ^ 2 )。但是在这种情况下如何计算N?我该如何处理递归调用?如何将此信息转换为Big O表示法?
请不要说这是一个重复的问题。我理解其他例子也有类似的问题,但这个例子对我来说是抽象的,学习如何解决它会在很多其他情况下帮助我。
答案 0 :(得分:1)
假设foo
的输入是:
foo(array, 0, 1000)
因此,首先,x
变为200,这是整个数据的五分之一。然后y
和z
成为第二个quintiles。请注意,它们仍然是end - start
(1000)的一小部分,而不是常量。
for
:
for(index = y; index < z; index++)
从y
到z
。 y
为400(2 * 1000/5),z
为800(4 * 1000/5)。所以for
循环的次数是800 - 400 = 400 =(4 - 2)* 1000 / 5.也许那个为你分析这个的人叫它N
,但这很荒谬。你在循环中做的是一个简单的增量,它需要恒定的时间(O(1)
)并且那里没有N
!
所以让我们自己说出一些东西。我会拨打N
以上的1000。基本上,for
循环2N / 5次。
递归怎么样?第一次递归在start
上递归到start + x
(即数据的前2/5),第二次递归在start + z
上递归到end
(即最后一次递归) 5个数据)。
假设你的函数总计花费f(N)
时间来计算它正在做的事情,它可以分解为:
f(N) = f(2N/5) + 2N/5 + f(N/5)
现在您只需要分析这个递归函数并尝试理解订单是什么。
虽然有很多方法可以理解这一点,例如绘制一个显示f
扩展方式的树以及使用什么参数,但您也可以尝试找到更简单的函数上限和下限。如果它们是相同的,那么你很幸运:
2N/5 + 2*f(N/5) < f(n) < 2*f(2N/5) + 2N/5
按the master theorem,两个限制都属于案例3,两个限制都变为θ(N)
,因此您的foo
函数实际上是θ(N)
。
另一种看待它的方法如下:
如您所见,数组的每个元素只触及一次。因此函数的时间复杂度为θ(N)
。
答案 1 :(得分:0)
在我们看一下整个函数的实际Big-O之前,让我们快速更正您的代码片段。
// O(4 + N + 2*O(?)) = O(N + O(?))
void foo(float[] array, int start, int end){
if((end-start) <=1) return; // O(1)
int x = (end-start) / 5; // O(1)
int y = 2*x; // O(1)
int z = 4*x; // O(1)
foo(array,start,start+y); // O(?)
for(index = y; index < z; index++){ // O(N)
array[index]++; // O(1)
}
foo(array, start+z, end); // O(?)
}
现在,在基本情况(end - start <= 1
)中,foo立即返回,使其成为O(1)
。在第二个到基本的情况下,这意味着此函数是O(N)
(只是for
循环)。在第三种到基本情况中,这意味着该函数是O(N + N + N) = O(3N) = O(N)
。它以这种方式向上发展。
重要的是要记住,我们忽略了一些(可能很大的)常数因子,但整个算法仍然属于O(N)
的范畴。
不可否认,这种方法缺乏严谨性,但只需最少的工作就可以得到很好的估计。