Eu encontrei outras diferenças entre essas abordagens. Parece simples e sem importância, mas tem um papel muito importante enquanto você se prepara para entrevistas e esse assunto surge, portanto, preste atenção.
Resumindo: 1) o percurso iterativo de pós-encomenda não é fácil - isso torna a DFT mais complexa 2) os ciclos de verificação mais fácil com recursão
Detalhes:
No caso recursivo, é fácil criar percursos anteriores e posteriores:
Imagine uma pergunta bastante padrão: "imprima todas as tarefas que devem ser executadas para executar a tarefa 5, quando as tarefas dependem de outras tarefas"
Exemplo:
//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!
}
}
Observe que a passagem pós-ordem recursiva não requer uma reversão subsequente do resultado. Filhos impressos primeiro e sua tarefa na pergunta impressa por último. Tudo está bem. Você pode fazer um percurso de pré-ordem recursiva (também mostrado acima) e esse exigirá uma reversão da lista de resultados.
Não é tão simples com abordagem iterativa! Na abordagem iterativa (uma pilha), você só pode fazer um percurso de pré-encomenda, portanto, você deve reverter a matriz de resultados no 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 simples, não?
Mas é uma armadilha em algumas entrevistas.
Significa o seguinte: com a abordagem recursiva, você pode implementar o Depth First Traversal e selecionar a ordem em que deseja pré ou pós (simplesmente alterando o local da "impressão", no nosso caso, "adicionando à lista de resultados" ) Com a abordagem iterativa (uma pilha), você pode facilmente fazer a pré-encomenda do percurso e, portanto, na situação em que as crianças precisam ser impressas primeiro (praticamente todas as situações em que você precisa começar a imprimir nos nós inferiores, subindo) - você está em o problema. Se você tiver esse problema, poderá reverter mais tarde, mas será uma adição ao seu algoritmo. E se um entrevistador estiver olhando para o relógio, pode ser um problema para você. Existem maneiras complexas de fazer um percurso iterativo de pós-ordem, elas existem, mas não são simples . Exemplo:https://www.geeksforgeeks.org/iterative-postorder-traversal-using-stack/
Assim, o ponto principal: eu usaria recursão durante as entrevistas, é mais simples de gerenciar e explicar. Você tem uma maneira fácil de percorrer a travessia pré e pós-encomenda em qualquer caso urgente. Com iterativo, você não é tão flexível.
Eu usaria a recursão e diria: "Ok, mas iterativo pode me fornecer um controle mais direto sobre a memória usada, posso medir facilmente o tamanho da pilha e desaprovar algum estouro perigoso .."
Outra vantagem da recursão - é mais simples evitar / observar ciclos em um gráfico.
Exemplo (pré-código):
dft(n){
mark(n)
for(child: n.children){
if(marked(child))
explode - cycle found!!!
dft(child)
}
unmark(n)
}