Java 包含递归调用的循环函数的运行时
我正在解决以下问题: 查找以下代码的时间复杂度: 我的思考过程如下: 查看对Java 包含递归调用的循环函数的运行时,java,time-complexity,Java,Time Complexity,我正在解决以下问题: 查找以下代码的时间复杂度: 我的思考过程如下: 查看对排列的每次调用中所做的工作: System.out.println(前缀)将花费O(n)时间 String rem=str.substring(0,i)+str.substring(i+1)也需要O(n)时间。这是因为合并两个字符串需要O(m+n)时间,其中m和n是字符串长度。因为在这种情况下,长度加起来总是n,所以总共是O(n) 最后,对permutation(还不是递归)和str.charAt(i)语句的实际调用将
排列的每次调用中所做的工作:
System.out.println(前缀)代码>将花费O(n)时间
String rem=str.substring(0,i)+str.substring(i+1)代码>也需要O(n)时间。这是因为合并两个字符串需要O(m+n)时间,其中m和n是字符串长度。因为在这种情况下,长度加起来总是n,所以总共是O(n)
最后,对permutation
(还不是递归)和str.charAt(i)
语句的实际调用将在常量时间so(1)中完成
然而,我感到困惑的是,这是在一个循环中完成的。在我的书中,这个解决方案被解释为“调用树中的每个节点都对应于O(n)功”。每个节点不是都需要O(n+n^2)=0(n^2)工作吗?因为循环的每个迭代都需要O(n)工作,并且会被调用n次
因此,在考虑了几次之后,我决定更笼统地重新表述我的问题,你将如何以标准化的方式确定这种性质的函数的时间复杂性(一种可以应用于大多数(如果不是所有)这种性质的问题的方法)?虽然只是通过情况进行推理是可行的,但也容易产生误解(正如我所证明的),那么有没有更一般的方法来解决这个问题呢
好吧,在反复思考这个问题之后,我意识到我的想法是正确的,但只是关于实际解决方案本身是如何构建的。我发现你可以从两个方面考虑这种情况,记住以下几点
因此,首先,在处理这个场景时,我们首先查看对置换的每个调用中的工作:
System.out.println(前缀)代码>-->O(n)
String rem=str.substring(0,i)+str.substring(i+1)代码>-->O(n)
置换(rem,前缀+str.charAt(i))代码>-->O(1)[这还没有考虑递归!!!]
现在我们知道了整个调用树中每个节点内部完成的工作量。现在我们考虑递归。对此有两种不同的思考方式
1) 递归与循环相结合
由于循环和递归调用都会导致调用permutation
,因此我们只需担心对permutation
的调用总数,而不必区分循环调用和常规递归调用。因此,我们以一个字符串为例,假设为“abc”,并检查调用树的叶节点:
“abc”-->1个节点
“a”、“b”、“c”-->3个节点
“ab”、“ac”、“ba”、“bc”、“ca”、“cb”-->6个节点
因此,我们看到有6个叶节点。我们可以立即看到这等于3!其中3是输入字符串“abc”的长度。因此,叶节点的数量将是n代码>其中n
对应于输入字符串长度。调用树的深度始终为n
,因为递归在n=0时停止。因此,调用树中的节点总数将是n*n代码>。因为每个节点对应于O(n)
功,所以我们完成的总功是O(nxnnxn!)=O((n+2)!)
[我们可以这样做,因为我们可以删除常量!]
2) 与循环分离的递归
现在,这就是我的困惑和这个问题的根源。在每个节点中完成的工作应该是O(n^2)
而不是O(n)
,这似乎是合乎逻辑的,因为for
循环在节点内部,对吗?在本例中,我认为for循环和递归是独立的实体。但是,这种思维方式并不真正正确,因为for
循环不只是每次被调用时运行n
次,而是运行n
次,然后运行n-1
次,然后运行n-2
,等等。总之,它运行n代码>每次启动的次数。因此,我们有n代码>运行,每次执行O(n)
工作意味着启动后总共运行O(n x n!)
次。但是,现在我们只考虑了每次调用for
循环的次数。它实际启动了多少次?这将是n
次,因为我们可以看到递归在n=0
时结束。因此,nxnnxn代码>再一次
现在,如果你发现了我推理中的错误(特别是第二种思维方式),请毫不犹豫地指出它!我想确保我正确地思考了这个问题
好吧,在反复思考这个问题之后,我意识到我的想法是正确的,但只是关于实际解决方案本身是如何构建的。我发现你可以从两个方面考虑这种情况,记住以下几点
因此,首先,在处理这个场景时,我们首先查看对置换的每个调用中的工作:
System.out.println(前缀)代码>-->O(n)
String rem=str.substring(0,i)+str.substring(i+1)代码>-->O(n)
置换(rem,前缀+str.charAt(i))代码>-->O(1)[这还没有考虑递归!!!]
现在我们知道了整个调用树中每个节点内部完成的工作量。现在我们考虑递归。对此有两种不同的思考方式
1) 递归与循环相结合
由于循环和递归调用都会导致调用permutation
,因此我们只需担心对permutation
的调用总数,而不必区分循环调用和常规递归调用。因此,我们以一个字符串为例,假设为“abc”,并检查调用树的叶节点:
“abc”