迭代(iteration)和递归(recursion)

本文详细对比了使用迭代和递归两种方法来计算给定文本内字符'A'的数量,并解释了递归方法的工作原理、优缺点以及如何通过尾递归来优化递归过程。此外,还讨论了递归在解决特定问题时的优势,如在循环树结构中的应用。
Q.请写一段代码来计算给定文本内字符“A”的个数。分别用迭代和递归两种方式。

A.假设给定文本为”AAA rating”。迭代方式就很直观,如下:

public class Iteration {

public int countA(String input) {
if (input == null || input.length( ) == 0) {
return 0;
}

int count = 0;
for (int i = 0; i < input.length( ); i++) {
if(input.substring(i, i+1).equals("A")){
count++;
}
}
return count;
}

public static void main(String[ ] args) {
System.out.println(new Iteration( ).countA("AAA rating")); // Ans.3
}
}


接下来,递归方式的代码如下:

public class RecursiveCall {

public int countA(String input) {

// 退出条件
if (input == null || input.length( ) == 0) {
return 0;
}

int count = 0;

//检查第一个字符
if (input.substring(0, 1).equals("A")) {
count = 1;
}

//递归调用来计算其结果
return count + countA(input.substring(1));
}

public static void main(String[ ] args) {
System.out.println(new RecursiveCall( ).countA("AAA rating")); // Ans. 3
}
}



Q.理解递归需要了解哪些概念?

A. 可重入方法(re-entrant method)是可以安全进入的方法,即使同一个方法正在被执行,深入到同一个线程的调用栈里面也不会影响此次执行的安全性。一个非可重入方法则不是可以安全进入的。例如,加入写文件或者向文件中写入日志的方法不是可重入方法时,有可能会毁坏那个文件。

如果一个方法调用了其自身的话,我们称之为递归调用。假定栈空间足够的话,尽管递归调用比较难以调试,在Java语言中实现递归调用也是完全可行的。递归方法是众多算法中替代循环的一个不错选择。所有的递归方法都是可重入的,但是不是所有可重入的方法都是递归的。

栈遵守LIFO(Last In First Out)规则,因此递归调用方法能够记住“调用者”并且知道此轮执行结束之返回至当初的被调用位置。递归利用系统栈来存储方法调用的返回地址。 Java是一种基于栈设计的编程语言。

顺着这个思路还有那些问题可以用来面试?

Q.什么情况下应该采用递归?

A. 上面的例子中其实不必采用递归,循环的方式可以达到目的,但是在某些情况下采用递归方式则代码会更加简短易读。递归方法在循环树结构以及避免丑陋的嵌套循环的情况下是非常好用的。

Q.什么是尾递归,为什么需要尾递归?上面的代码用尾递归方式如何重写?

A. 常规递归方法(亦称,头递归)在上面演示了,这种方式会增加调用栈的大小。每次递归,其入口需要被记录在栈中。方法返回之前需要给countA(input.substring(1)的结果加一个count。假定count大于1,那么返回结果就是count + countA(input.substring(1)),当然事先要算出来countA(input.substring(1))才行。同时,这也意味着直到countA(input.substring(1)计算出来才能得到最终的结果。因此,最后需要做的事其实是加法运算,而非递归本身。

尾递归的好处是什么?

在尾递归中,最后要做的是递归,加法运算在之前就已经完成了。一轮递归调用完毕后就没有其他事情了(除了加法运算),因此调用时生成的信息也就没什么用了。这些无用信息可以丢弃,然后用一组新的参数来调用一次递归方法来产生一个新的结果。这也就是说,栈调用减少带来了内存消耗减少并且程序的性能更好。

[b]尾递归重写的代码如下:[/b]

public class TailRecursiveCall {

public int countA(String input) {

// exit condition – recursive calls must have an exit condition
if (input == null || input.length() == 0) {
return 0;
}

return countA(input, 0) ;
}

public int countA(String input, int count) {
if (input.length() == 0) {
return count;
}

// check first character of the input
if (input.substring(0, 1).equals("A")) {
count = count + 1;
}

// recursive call is the last call as the count is cumulative
return countA(input.substring(1), count);
}

public static void main(String[] args) {
System.out.println(new TailRecursiveCall().countA("AAA rating"));
}
}
### Java 迭代方式优化递归查询目录 Tree 的代码实现 在 Java 中,可以通过使用栈(Stack)来模拟递归调用栈,从而将递归方法转换为迭代方法。以下是一个完整的代码示例,展示了如何使用迭代方式构建文件目录树形结构。 ```java import java.io.File; import java.util.ArrayList; import java.util.Deque; import java.util.LinkedList; import java.util.List; public class DirectoryTreeBuilder { public static class TreeNode { private String name; private List<TreeNode> children; public TreeNode(String name) { this.name = name; this.children = new ArrayList<>(); } public String getName() { return name; } public List<TreeNode> getChildren() { return children; } public void addChild(TreeNode child) { children.add(child); } } public static TreeNode buildDirectoryTreeIteratively(File directory) { if (!directory.isDirectory()) { throw new IllegalArgumentException("Provided path is not a directory."); } TreeNode root = new TreeNode(directory.getName()); Deque<Object[]> stack = new LinkedList<>(); // Stack to simulate recursion stack.push(new Object[]{root, directory.listFiles(), 0}); while (!stack.isEmpty()) { Object[] current = stack.peek(); TreeNode currentNode = (TreeNode) current[0]; File[] files = (File[]) current[1]; int index = (int) current[2]; if (files == null || index >= files.length) { stack.pop(); // Finished processing all children of current node continue; } File file = files[index]; TreeNode childNode = new TreeNode(file.getName()); if (file.isDirectory()) { stack.push(new Object[]{childNode, file.listFiles(), 0}); // Push child directory onto stack } currentNode.addChild(childNode); // Add child to parent current[2] = index + 1; // Increment index for next iteration } return root; } public static void printTree(TreeNode node, int level) { StringBuilder indent = new StringBuilder(); for (int i = 0; i < level; i++) { indent.append(" "); // 每层缩进两个空格 } System.out.println(indent + node.getName()); for (TreeNode child : node.getChildren()) { printTree(child, level + 1); // Recursive print for simplicity } } public static void main(String[] args) { File rootDir = new File("/path/to/your/directory"); // 替换为实际路径 if (rootDir.exists() && rootDir.isDirectory()) { TreeNode tree = buildDirectoryTreeIteratively(rootDir); printTree(tree, 0); } else { System.out.println("指定路径不是一个有效的目录"); } } } ``` 上述代码通过使用 `Deque`(双端队列)作为栈来模拟递归调用栈[^1]。每个栈元素包含当前节点、文件列表索引值,用于跟踪当前处理的子文件或子目录。这种方法避免了递归可能导致的栈溢出问题,同时保留了递归逻辑的清晰性[^2]。 ### 核心逻辑说明 - **栈的使用**:栈中的每个元素存储了当前节点、文件列表当前处理的索引值。这种设计允许程序逐个处理文件或目录,并在需要时回溯到上一层。 - **避免递归**:通过显式地管理栈,程序可以手动控制调用流程,从而避免递归带来的栈深度限制问题[^3]。 - **性能优化**:迭代方法通常比递归方法更高效,因为它减少了函数调用的开销,并且不会受到系统栈大小的限制[^4]。 ### 注意事项 - 确保提供的路径是有效的,并且程序具有读取该路径的权限。 - 如果目录层次较深,迭代方法仍然可能消耗大量内存。在这种情况下,可以考虑分批处理或限制目录深度。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值