如何计算此递归函数的最坏情况时间复杂度?

时间:2016-03-04 19:17:03

标签: java algorithm recursion big-o subset-sum

我认为这根本不高效。我正在尝试创建一个更快的实现(最好是二进制搜索或者使用Sets),但是现在这就是我所处的位置。 我不确定它是否相关,但我创建了一个计数变量,只是为了查看调用该方法的次数。它达到了577次。

这是“子集和”算法,我必须显示添加到目标总和的所有子集,在本例中为3165。 没有具体说明这个问题是算法,但我意识到它确实是同一个概念。

我的问题是我怎么知道这个程序的效率如何,方法调用指标是什么?

public class SubsetSumAlgorithm {

    static int count = 0;

    public static void main(String[] args) {

        int[] array = {26, 39, 104, 195, 403, 504, 793, 995, 1156, 1673};

        System.out.println("COLLECTIONS IN ARRAY THAT ADD TO 3165: ");
        findCollections(array, 0, 0, 3165, "");
        System.out.println("Method called " + count + " times.");//method calls
    }

    public static void findCollections(int[] array, int index, int currentPosition, int sum, String collection) {

        count++; //<---COUNTING THE METHOD CALLS HERE

        if (array.length < index || currentPosition > sum) {
            return;
        }
        //if sum is found, add to subset
        for (int i = index; i < array.length; i++) {
            if (currentPosition + array[i] == sum) {
                System.out.println(collection + " " + array[i]);
            }
            //otherwise, call the method again
            else if (currentPosition + array[i] < sum) {//recursive call
                findCollections(array, i + 1, currentPosition + array[i], sum, collection + " " + array[i]);
            }
        }
    }
}

这是输出:

COLLECTIONS IN ARRAY THAT ADD TO 3165: 
26 195 793 995 1156
195 504 793 1673
Method called 577 times.

2 个答案:

答案 0 :(得分:2)

  

我的问题是如何知道这个程序的效率如何,方法调用指标是什么?

获得算法的渐近运行时间的唯一方法是实际弄脏并手动手动。有了这样说,尝试在算法运行时跟踪方法调用并不是可靠的合理的方式来导出算法的运行时。

要开始尝试计算算法的运行速度或速度,我们可以先从分析每行的运行时间中得出重现:

1    public static void findCollections(int[] array, int index, int currentPosition, int sum, String collection) {
2        if(array.length < index || currentPosition > sum)
3            return;
4        for(int i = index; i < array.length; i++) {
5            if(currentPosition + array[i] == sum) {
6                System.out.println(collection + " " + array[i]);
7            }
8            else if(currentPosition + array[i] < sum) {
9                findCollections(array, i + 1, currentPosition + array[i], sum, collection + " " + array[i]);
10           }
11       }
12   }

...

1    -
2    1
3    1
4    n+1
5    n
6    n
7    -
8    n
9    n*T(n-1)
10   -
11   -
12   -

我们分析了每一行,我们可以推导出复发:

    T(n) = n*T(n-1) + 4n + 3
 => T(n) = n*T(n-1) + 4n + Θ(1)
 => T(n) = n*T(n-1) + 4n

由于我们无法使用主定理,并且尝试递归树会非常混乱并且非常快速地混淆这种特定的重现,我们可以使用替换方法。我们将初始猜测设置为2^n,因为它看起来像它可能是指数的:


上限O(2^n)

这意味着

T(n) ≤ c * 2^n

如果这个命题成立,这意味着我们也知道

T(n-1) ≤ c * 2^(n-1)

因此,我们现在可以写下我们的复发并试图证明我们的猜测:

    c * 2^n ≥ n * (c * 2^(n-1)) + 4n
 => c * 2^n ≥ c * n * 2^(n-1) + 4n

适用于所有n > 0 | c = 1,因此T(n) = O(2^n)


下界Ω(2^n)

这意味着

T(n) ≥ c * 2^n

如果这个命题成立,这意味着我们也知道

T(n-1) ≥ c * 2^(n-1)

因此,我们现在可以写下我们的复发并试图证明我们的猜测:

    c * 2^n ≤ n * (c * 2^(n-1)) + 4n
 => c * 2^n ≤ c * n * 2^(n-1) + 4n

适用于所有n > 0 | c = 5000,因此T(n) = Ω(2^n)


结论Θ(2^n)

由于我们已经证明该算法同时属于O(2^n) Ω(2^n),因此根据定义,它也是Θ(2^n)。所以基本上,你的算法的时间复杂度是Θ(2^n),因为它是指数的,所以非常慢

答案 1 :(得分:1)

如果您想通过实验找到趋势,那么对Method的调用次数是一个不错的指标。您需要使用不同的输入多次运行该函数,收集数据,绘制图形并找到最佳拟合曲线......这将假设对findCollections的调用与执行所需的时间直接相关。 然而,在这里找到最坏的情况并不太难。您只需要考虑哪些输入变量会导致执行时间最长。

在这种情况下,如果sum变量大于整个集合的总和,您将看到最差的执行(最多的递归)。如果这个条件为真,那么下面的方法将等同于你的方法:

public static void findCollections(int[] array, int index, int currentPosition, int sum, String collection) {
    for(int i = index; i < array.length; i++) {
        findCollections(array, i + 1, currentPosition + array[i], sum, collection + " " + array[i]);
    }
}

这里很容易看出,这将循环遍历所有可能的array子集。这使您的解决方案O(2^n),其中n的大小为array