Java 这个简单的递归代码中发生了什么?
我似乎不明白为什么下面的代码会打印出来Java 这个简单的递归代码中发生了什么?,java,recursion,Java,Recursion,我似乎不明白为什么下面的代码会打印出来 00 1 10 1 依我看,它应该打印两个二进制数字的排列 请不要修改我的代码。我想解释一下为什么它现在这样工作 public static String addZ(int n) { String str =""; if(n==0) return ""; str += "0" + addZ(n-1)+"\n"; str += "1" + addZ(n-1); return str; } 如果这与家
00
1
10
1
依我看,它应该打印两个二进制数字的排列
请不要修改我的代码。我想解释一下为什么它现在这样工作
public static String addZ(int n)
{
String str ="";
if(n==0)
return "";
str += "0" + addZ(n-1)+"\n";
str += "1" + addZ(n-1);
return str;
}
如果这与家庭作业有关,你应该给它贴上这样的标签 代码使用类似于
0{prev}\n1{prev}
的格式生成文本,其中{prev}
是递归调用的结果。请注意,此换行符成为递归结果的一部分,因此将“中断”其他调用的结果。让我告诉你我的意思
n==0
是基本大小写,硬编码为返回空字符串(“”
)
n==1
是第一种可以递归的情况。由于{prev}
是空字符串,因此返回0\n1
。这个印成
0
1
00
1
10
1
000
1
10
1
100
1
10
1
接下来是n==2
{prev}
是0\n1
,因此生成00\n1\n10\n1
。正如您所注意到的,这打印为
0
1
00
1
10
1
000
1
10
1
100
1
10
1
n==3
是我将展示的最后一步{prev}
是00\n1\n10\n1
,因此它生成000\n1\n10\n10\n100\n10\n1
,打印为
0
1
00
1
10
1
000
1
10
1
100
1
10
1
作为视觉辅助,我将使用不同的大括号在打印输出中包装不同的递归返回。大括号括住n==0
结果,方括号括住n==1
结果,括号括住n==2
结果
0(0[0{}
1{}]
1[0{}
1{}])
1(0[0{}
1{}]
1[0{}
1{}])
编辑
OP说这不是家庭作业,所以我改变了答案,提供了一个示例Java程序,可以打印从0到2的所有数字
public class Main {
public static void main( String[] args ) {
String binaryNumbers = buildBinaryNumbersString(4);
System.out.println(binaryNumbers);
}
private static String buildBinaryNumbersString( int bits ) {
return recursivelyBuildBinaryNumbersString(bits, "");
}
private static String recursivelyBuildBinaryNumbersString( int bits, String prefix ) {
String result;
if (bits <= 0) {
result = prefix;
} else {
result =
recursivelyBuildBinaryNumbersString(bits - 1, prefix + "0") +
"\n" +
recursivelyBuildBinaryNumbersString(bits - 1, prefix + "1");
}
return result;
}
}
此代码与您的代码非常相似,但不同之处在于它的工作原理。您的代码可以被认为是试图构建一个值下面的所有二进制字符串,然后尝试扩展这些值以包含下一个更高的位(尽管代码没有成功地做到这一点)。相反,通过在下一位的值0
或1
之前加上“迄今为止的数字”,该代码被写入多个递归路径中以供重用。因此,对于相同的位值,对递归BuildBinaryNumbersString()
的调用将发生很大变化,具体取决于传递的前缀。考虑所有这些<代码>前缀< /代码>值,当<代码>位始终是代码> 1 < /代码>:
prefix: result
000: 0000\n0001
001: 0010\n0011
010: 0100\n0101
011: 0110\n0111
100: 1000\n1001
101: 1010\n1011
110: 1100\n1101
111: 1110\n1111
看看这八个前缀和它们的八个输出组合如何生成四位的所有十六种排列
值得注意的是,这种技术用于通过递归调用传递迄今为止合成的结果。这种技术与使用递归调用返回调用链的结果一样有用。值得一提的是,这种技术对于尾部递归至关重要。对于支持尾部递归(而非Java)的语言,如果函数的返回值是对同一函数的递归调用,则可以编写递归函数,以便该函数执行的最后一个操作是递归。粗略地说,这意味着调用函数的堆栈项不再需要,因为被调用函数的堆栈项足以生成调用函数的返回。这允许调用函数的堆栈项被被调用函数的堆栈项覆盖,从而消除堆栈溢出的危险。我不知道每一个递归函数是否可以用一个等价的递归函数来代替,它使用尾部调用,但即使这样,我也发现了通过调用链向下传递状态是非常有用的想法。 < P>如果你想了解递归函数是如何工作的,最好把它看成一棵树。对于功能:
public static String addZ(int n)
{
String str ="";
if(n==0)
return "";
str += "0" + addZ(n-1)+"\n";
str += "1" + addZ(n-1);
return str;
}
现在我们可以看到,这两个str+=
实际上只是一个调用,用于向字符串追加以下内容:“0”+addZ(n-1)+“\n”+“1”+addZ(n-1)
现在,如果我们向Console添加一些日志记录,我们会得到如下树:
str += "0" + addZ([n=2] n-1)+"n";
str += "0" + addZ([n=1] n-1)+"n";
addZ(n = 0) = ""
str += "1" + addZ([n=1] n-1);
addZ(n = 0) = ""
str += "1" + addZ([n=2] n-1);
str += "0" + addZ([n=1] n-1)+"n";
addZ(n = 0) = ""
str += "1" + addZ([n=1] n-1);
addZ(n = 0) = ""
这个输出的优点是你可以把它卷起来
str += "0" + {addZ([n=2] n-1) = "0" + "" + "\n" + "1" + ""} +"\n";
str += "0" + {addZ([n=1] n-1) = ""} +"\n";
addZ(n == 0) = ""
str += "1" + {addZ([n=1] n-1) = ""};
addZ(n == 0) = ""
str += "1" + {addZ([n=2] n-1) = "0" + "" + "\n" + "1" + ""};
str += "0" + {addZ([n=1] n-1) = ""}+"\n";
addZ(n == 0) = ""
str += "1" + {addZ([n=1] n-1) = ""};
addZ(n == 0) = ""
所以
addZ(n = 2) = "0" + {"0" + "" + "\n" + "1" + ""} +"\n" "1" + {"0" + "" + "\n" + "1" + ""}
addZ(n = 2) = "00\n1\n10\n1"
这是您的输出:
00
1
10
1
如果您想体验较大的n
,我建议您在代码中添加日志记录,如下所示:使用递归,您将函数调用添加到堆栈顶部,直到达到基本情况,然后开始从堆栈中弹出调用
在addZ(2)
str被声明为空字符串,然后给出值“0”+addZ(2-1)或addZ(1)的返回值
addZ(1)
声明它自己的空str,该str的值为“0”+addZ(1-1)或addZ(0)的返回值
addZ(0)是基本大小写,将空的字符串返回到addZ(1)
addZ(1)
仍然只有一个值为“0”的str,并在其中添加换行符“\n”
addZ(1)
然后将值“1”添加到str+addZ(0)的返回值,正如我们从上面知道的,它是一个空字符串
addZ(1)
使用值“0\n1”将str返回到addZ(2)
addZ(2)
继续向str添加换行符
此时str=“00\n\1\n”,这就是输出示例所显示的内容
addZ(2)
然后将“1”+addZ(2-1)或addZ(1)的返回值添加到str
从上面我们知道,addZ(1)
最终返回“0\n1”
str现在保存值“00\n\1\n10\n1”,函数完成
基本上,只需遵循逻辑即可。如果你很难理解递归,那么使用调试器一步一步地完成它也会有帮助。好吧,没有main()它无法打印任何内容。看起来你正在将n=2
传递给它,并打印输出。前两行来自代码中的第一个str+=
,下两行来自第二个s