【问题标题】:How would I calculate the worst case time complexity of this recursive function?我将如何计算这个递归函数的最坏情况时间复杂度?
【发布时间】:2016-03-04 19:17:02
【问题描述】:

我认为这根本没有效率。我正在尝试创建一个更快的实现(最好是二进制搜索或使用 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.

【问题讨论】:

  • 我在那篇文章中看到的唯一大 o 计算是 2^n,但那是使用 Sets。
  • 这不可避免地会是指数级的时间和缓慢的。如果你找到解决这个问题的有效方法,你将赢得一百万美元,在计算机科学界永远享有盛誉,你将摧毁互联网。
  • @LouisWasserman 哈哈哈!也许有一天。
  • @LouisWasserman 实际上动态规划在伪多项式时间内解决了这个问题。也就是说,仍然存在指数级缓慢的情况,但大多数情况下它会很快返回。事实上,对于不超过集合大小乘以从最小和到最大和的范围的整数。

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


【解决方案1】:

我的问题是我怎么知道这个程序的效率,以及方法调用是一个指标吗?

推导出算法的渐近运行时间的唯一方法是亲自动手手动。话虽如此,在算法运行时尝试跟踪方法调用并不是推导算法运行时间的可靠合理方式。

要开始尝试确定您的算法运行的速度有多快或多慢,我们可以从分析每行的运行时间推导出递归开始:

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 &gt; 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 &gt; 0 | c = 5000,因此T(n) = Ω(2^n)


结论Θ(2^n)


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

【讨论】:

  • 很好的答案!我希望最终改进这个算法,但感谢你帮助我了解它目前的位置。
  • @csheridan 很高兴我能帮上忙,祝你好运!
  • 我对您在分析的第 9 行计算的 n * T(n - 1) 有点迷茫。
  • 还有,变量“c”是什么?
  • @csheridan 因此for 循环将循环(最坏情况)n 次,因此执行for 循环调用的实际行将运行n+1 次(考虑到在决定我们完成循环之前进行最终检查)。所以知道了这一点,for 循环中的所有内容都将被称为n 次。对于此 for 循环中的 else if 块,我们假设最坏的情况是它始终为真,这意味着您的函数(我们称为 T(n))被调用 n * T(n-1) 次。
【解决方案2】:

如果您想通过实验找到趋势,对方法的调用次数是一个不错的指标。您需要使用不同的输入多次运行该函数、收集数据、绘制图表并找到最佳拟合曲线...这将假定对 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 的大小

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-07-14
    • 1970-01-01
    • 1970-01-01
    • 2022-01-04
    • 1970-01-01
    相关资源
    最近更新 更多