Encontré otras diferencias entre esos enfoques. Parece simple y sin importancia, pero tiene un papel muy importante mientras te preparas para las entrevistas y surge este tema, así que mira de cerca.
En resumen: 1) el recorrido iterativo de orden posterior no es fácil, eso hace que DFT sea más complejo 2) los ciclos de verificación más fáciles con recursividad
Detalles:
En el caso recursivo, es fácil crear recorridos previos y posteriores:
Imagine una pregunta bastante estándar: "imprima todas las tareas que deben ejecutarse para ejecutar la tarea 5, cuando las tareas dependen de otras tareas"
Ejemplo:
//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!
}
}
Tenga en cuenta que el recorrido recursivo posterior al pedido no requiere una inversión posterior del resultado. Los niños imprimieron primero y su tarea en la pregunta se imprimió al final. Todo esta bien. Puede hacer un recorrido recursivo de pre-orden (también se muestra arriba) y ese requerirá una inversión de la lista de resultados.
¡No es tan simple con un enfoque iterativo! En el enfoque iterativo (una pila) solo puede hacer un recorrido de preordenamiento, por lo que se vio obligado a invertir la matriz de resultados al final:
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;
}
Parece simple, no?
Pero es una trampa en algunas entrevistas.
Significa lo siguiente: con el enfoque recursivo, puede implementar Profund First Traversal y luego seleccionar el orden que necesita antes o después (simplemente cambiando la ubicación de la "impresión", en nuestro caso de "agregar a la lista de resultados" ) Con el enfoque iterativo (una pila), puede hacer fácilmente un recorrido transversal de preorden y, por lo tanto, en la situación en la que los niños necesitan imprimirse primero (casi todas las situaciones en las que necesita comenzar a imprimir desde los nodos inferiores, hacia arriba): está en el problema. Si tiene ese problema, puede revertirlo más tarde, pero será una adición a su algoritmo. Y si un entrevistador está mirando su reloj, puede ser un problema para usted. Existen formas complejas de hacer un recorrido iterativo de orden posterior, existen, pero no son simples . Ejemplo:https://www.geeksforgeeks.org/iterative-postorder-traversal-using-stack/
Por lo tanto, la conclusión: usaría la recursividad durante las entrevistas, es más fácil de manejar y explicar. Usted tiene una manera fácil de pasar de un recorrido pre a otro en cualquier caso urgente. Con iterativo no eres tan flexible.
Usaría la recursión y luego diría: "Ok, pero iterativo puede proporcionarme un control más directo sobre la memoria usada, puedo medir fácilmente el tamaño de la pila y no permitir algún desbordamiento peligroso ..."
Otra ventaja de la recursividad: es más sencillo evitar / notar ciclos en un gráfico.
Ejemplo (preudocódigo):
dft(n){
mark(n)
for(child: n.children){
if(marked(child))
explode - cycle found!!!
dft(child)
}
unmark(n)
}