尝试计算函数的时间和存储复杂度(C)

时间:2019-02-18 10:22:51

标签: c time-complexity complexity-theory space-complexity

编辑:我想出了如何正确计算时间复杂度,但仍然无法计算出存储复杂度。

编辑:把所有东西都弄清楚了。

我尝试解决复杂性问题,但失败了。

答案应该是:时间复杂度-n(m + n),存储复杂度-m + n。

请帮助我了解我在哪里错了,并提出一种更好地理解/解决这类问题的方法。

功能如下:

void f(int n, int m){
     if (n <= 1) {
         int *arr=malloc(m*sizeof(int));
         for (int i=0; i<m; i++) arr[i] = 0;
         free(arr);
         return;
     }
     f(n-1, m+1);
     f(n%2, m+1);
}

从我看到的“ free(arr)”中释放了malloc分配的内存,这使得malloc在时间复杂度方面不受欢迎。 编辑:有人向我解释说,即使我们使用“免费”,仍然会考虑malloc(明智地使用空间cpmlexity)。

我看到第一个函数调用使函数本身调用了n次,并且在发生这种情况时m增加了1-n次,因此,第一次函数调用的时间复杂度为n(m + 1),存储复杂度n-,因为有n个函数递归调用。编辑:最终找到答案。

第二个函数调用调用函数log(n)次,m增加log(n)次,这使该调用的时间复杂度为:log(n)(m + 1)。 存储复杂度:log(n)。

所以总时间复杂度为n(m + 1),总存储复杂度为n。

2 个答案:

答案 0 :(得分:0)

这实际上是一个棘手的问题! 第二个函数调用f(n%2, m+1)仅调用递归f,因为它计算n到2的提醒,该提醒可以为1或0!在这两种情况下,都将返回f函数,而无需进行任何进一步的递归调用。 因此它不是log n。

函数f在f(n-1, m+1)中被调用了n次,而紧接着在f(n%2, m+1)中被调用了,它将再次被调用一次。如果仅考虑n个因子,则为O(2n)。

现在考虑m因子,我们将注意到if内的循环重复m次,并且在每个递归调用中m均增加1(并且在从递归调用返回时实际上减小)! (m + n ... m + 1)的和为O(mn + n(n + 1)/ 2)。简化之后。

因此,考虑这两个因素,时间复杂度为O(2n + mn + n(n + 1)/ 2),实际上简化后等于 O(nm + n ^ 2) )

关于存储复杂度:第一个调用(m + 1)的m会增加n,这将持续n次,但第二个调用不会继续,因此存储复杂性将为 O(n + m)

答案 1 :(得分:0)

void f(int n, int m){
     if (n <= 1) {
         int *arr=malloc(m*sizeof(int));
         for (int i=0; i<m; i++) arr[i] = 0;
         free(arr);
         return;
     }
     f(n-1, m+1);
     f(n%2, m+1);
}

我们重构它:

void f1(int m) {
    int *arr = malloc(m*sizeof(int));
    for (int i = 0; i < m; i++) {
        arr[i] = 0;
    }
    free(arr);
}

void f(int n, int m){
     if (n <= 1) {
         f1(m);
         return;
     }
     f(n-1, m+1);
     f(n%2, m+1);
}

对于f1来说,它非常简单-空间复杂度为sizeof(int) * m-我们需要分配很多空间-时间复杂度仅为m-我们正在遍历所有m元素在数组arr中。

n%2只能是10,因此我们可以用f(n%2, m+1);代替f1(m+1)

void f(int n, int m){

     if (n <= 1) {
         f1(m); // (1)
         return;
     }

     f(n-1, m+1); // (2)

     f1(m + 1); // (3)
}

现在。如果为n > 1,则我们调用f(n-1, ...直到n <= 1。对于每个n > 1,我们以相反的时间顺序调用f1(m + 1)(因为它在递归调用之后)。当我们到达n <= 1时,将f1(m)次调用m = m(initial) + n(initial) - 1。 等等,例如n=5的示例,然后:

  • f(5, m)的初始调用,因此n = 5
  • n = 5,所以我们称f(4, m+1) //(2)
  • n = 4,所以我们叫f(3, m+2) //(2)
  • n = 3,所以我们叫f(2, m+3) //(2)
  • n = 2,所以我们叫f(1, m+4) //(2)
  • n = 1,所以我们调用f1(m+4)并返回//(1)
  • n = 2,在(2)之后,所以我们叫f1(m+4) //(3)
  • n = 3,在(2)之后,所以我们叫f1(m+3) //(3)
  • n = 4,在(2)之后,所以我们叫f1(m+2) //(3)
  • n = 5,在(2)之后,所以我们叫f1(m+1) //(3)

我们可以看到f1(m+4)被两次调用,并且我们从f1(m + i)i=1的相反顺序调用i=4

我们可以“展开”功能:

void f(int n, int m){
     f1(m + n - 1);
     for (int i = n - 1; i > 0; --i) {
         f1(m + i);
     }
}

由于mn都接近无穷大,因此+1-1毫无意义。

空间复杂度是f1(max(m + i, m + n - 1))的空间复杂度,因为f1每次都会释放内存。因此,(m + n - 1) * sizeof(int)(m + n) * sizeof(int),也就是m + n

时间复杂度取决于我们调用f1函数的次数。我们看到我们称呼为:

f1(m + n - 1)
f1(m + n - 1)
f1(m + n - 2)
...
f1(m + 2)
f1(m + 1)

所以时间复杂度是

(m + n - 1) + ((m + n - 1) + (m + n - 2) + ... + (m + 1))
(m + n - 1) + (n - 1) * m + ((n - 1) + (n - 2) + ... 1)
(m + n - 1) + (n - 1) * m + ((n - 1) * (n - 1 + 1) / 2)
(m + n - 1) + (n - 1) * m + ((n - 1) * (n - 1 + 1) / 2)
// the `*2`, `/2`, `+1` and `-1` mean nothing close to infinity
 m + n      + n       * m + n        *  n
m + n + m * n + n * n
m * (n + 1) + n * (n + 1)
(m + n) * (n + 1)
(m + n) * n