我发现了这些方法之间的另一个区别。
它看起来简单且不重要,但它在你准备面试时起着非常重要的作用,并且这个主题出现了,所以请仔细观察。
简而言之:
1) 迭代后序遍历并不容易——这使得 DFT 更加复杂
2) 使用递归更容易循环检查
详情:
在递归的情况下,很容易创建前后遍历:
想象一个非常标准的问题:“当任务依赖于其他任务时,打印所有应该执行的任务以执行任务 5”
例子:
//key-task, value-list of tasks the key task depends on
//"adjacency map":
Map<Integer, List<Integer>> tasksMap = new HashMap<>();
tasksMap.put(0, new ArrayList<>());
tasksMap.put(1, new ArrayList<>());
List<Integer> t2 = new ArrayList<>();
t2.add(0);
t2.add(1);
tasksMap.put(2, t2);
List<Integer> t3 = new ArrayList<>();
t3.add(2);
t3.add(10);
tasksMap.put(3, t3);
List<Integer> t4 = new ArrayList<>();
t4.add(3);
tasksMap.put(4, t4);
List<Integer> t5 = new ArrayList<>();
t5.add(3);
tasksMap.put(5, t5);
tasksMap.put(6, new ArrayList<>());
tasksMap.put(7, new ArrayList<>());
List<Integer> t8 = new ArrayList<>();
t8.add(5);
tasksMap.put(8, t8);
List<Integer> t9 = new ArrayList<>();
t9.add(4);
tasksMap.put(9, t9);
tasksMap.put(10, new ArrayList<>());
//task to analyze:
int task = 5;
List<Integer> res11 = getTasksInOrderDftReqPostOrder(tasksMap, task);
System.out.println(res11);**//note, no reverse required**
List<Integer> res12 = getTasksInOrderDftReqPreOrder(tasksMap, task);
Collections.reverse(res12);//note reverse!
System.out.println(res12);
private static List<Integer> getTasksInOrderDftReqPreOrder(Map<Integer, List<Integer>> tasksMap, int task) {
List<Integer> result = new ArrayList<>();
Set<Integer> visited = new HashSet<>();
reqPreOrder(tasksMap,task,result, visited);
return result;
}
private static void reqPreOrder(Map<Integer, List<Integer>> tasksMap, int task, List<Integer> result, Set<Integer> visited) {
if(!visited.contains(task)) {
visited.add(task);
result.add(task);//pre order!
List<Integer> children = tasksMap.get(task);
if (children != null && children.size() > 0) {
for (Integer child : children) {
reqPreOrder(tasksMap,child,result, visited);
}
}
}
}
private static List<Integer> getTasksInOrderDftReqPostOrder(Map<Integer, List<Integer>> tasksMap, int task) {
List<Integer> result = new ArrayList<>();
Set<Integer> visited = new HashSet<>();
reqPostOrder(tasksMap,task,result, visited);
return result;
}
private static void reqPostOrder(Map<Integer, List<Integer>> tasksMap, int task, List<Integer> result, Set<Integer> visited) {
if(!visited.contains(task)) {
visited.add(task);
List<Integer> children = tasksMap.get(task);
if (children != null && children.size() > 0) {
for (Integer child : children) {
reqPostOrder(tasksMap,child,result, visited);
}
}
result.add(task);//post order!
}
}
请注意,递归后序遍历不需要后续反转结果。孩子们最先打印,你在问题中的任务最后打印。一切顺利。您可以进行递归前序遍历(也如上所示),并且需要反转结果列表。
迭代方法没那么简单!在迭代(一个堆栈)方法中,您只能进行预排序遍历,因此您必须在最后反转结果数组:
List<Integer> res1 = getTasksInOrderDftStack(tasksMap, task);
Collections.reverse(res1);//note reverse!
System.out.println(res1);
private static List<Integer> getTasksInOrderDftStack(Map<Integer, List<Integer>> tasksMap, int task) {
List<Integer> result = new ArrayList<>();
Set<Integer> visited = new HashSet<>();
Stack<Integer> st = new Stack<>();
st.add(task);
visited.add(task);
while(!st.isEmpty()){
Integer node = st.pop();
List<Integer> children = tasksMap.get(node);
result.add(node);
if(children!=null && children.size() > 0){
for(Integer child:children){
if(!visited.contains(child)){
st.add(child);
visited.add(child);
}
}
}
//If you put it here - it does not matter - it is anyway a pre-order
//result.add(node);
}
return result;
}
看起来很简单,不是吗?
但这在某些采访中是一个陷阱。
这意味着:使用递归方法,您可以实现深度优先遍历,然后选择您需要的前后顺序(只需更改“打印”的位置,在我们的“添加到结果列表”)。使用迭代(一个堆栈)方法,您可以轻松只进行预排序遍历,因此在需要首先打印孩子的情况下(几乎所有需要从底部节点开始打印的情况,向上) - 你有麻烦了。如果你有这个麻烦,你可以稍后逆转,但这将是你算法的一个补充。如果面试官正在看他的手表,那对你来说可能是个问题。进行迭代后序遍历有很复杂的方法,它们存在,但它们并不简单。示例:https://www.geeksforgeeks.org/iterative-postorder-traversal-using-stack/
因此,底线:我会在面试中使用递归,它更易于管理和解释。在任何紧急情况下,您都可以轻松地从订单前遍历到订单后遍历。使用迭代,你就没有那么灵活了。
我会使用递归然后告诉:“好的,但是迭代可以让我更直接地控制已用内存,我可以轻松测量堆栈大小并禁止一些危险的溢出......”
递归的另一个优点 - 避免/注意图中的循环更简单。
示例(前置代码):
dft(n){
mark(n)
for(child: n.children){
if(marked(child))
explode - cycle found!!!
dft(child)
}
unmark(n)
}