J'ai trouvé une autre différence entre ces approches. Cela semble simple et sans importance, mais il a un rôle très important lorsque vous vous préparez pour des entretiens et que ce sujet se pose, alors regardez attentivement.
En bref: 1) la traversée itérative de post-ordre n'est pas facile - ce qui rend la DFT plus complexe 2) les cycles sont plus faciles à vérifier avec la récursivité
Détails:
Dans le cas récursif, il est facile de créer des traversées pré et post:
Imaginez une question assez standard: "imprimer toutes les tâches qui doivent être exécutées pour exécuter la tâche 5, lorsque les tâches dépendent d'autres tâches"
Exemple:
//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!
}
}
Notez que la traversée récursive post-commande ne nécessite pas une inversion ultérieure du résultat. Les enfants imprimés en premier et votre tâche dans la question imprimée en dernier. Tout va bien. Vous pouvez effectuer une traversée de pré-commande récursive (également illustrée ci-dessus) et celle-ci nécessitera une inversion de la liste des résultats.
Pas si simple avec une approche itérative! Dans l'approche itérative (une pile), vous ne pouvez effectuer qu'une traversée de précommande, vous devez donc inverser le tableau de résultats à la fin:
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;
}
Ça a l'air simple, non?
Mais c'est un piège dans certaines interviews.
Cela signifie ce qui suit: avec l'approche récursive, vous pouvez implémenter Depth First Traversal puis sélectionner l'ordre dont vous avez besoin avant ou après (simplement en changeant l'emplacement de l '"impression", dans notre cas de "l'ajout à la liste de résultats" ). Avec l'approche itérative (une pile), vous pouvez facilement faire uniquement une traversée de précommande et donc dans la situation où les enfants doivent être imprimés en premier (à peu près toutes les situations où vous devez commencer l'impression à partir des nœuds inférieurs, en remontant) - vous êtes dans le problème. Si vous rencontrez ce problème, vous pouvez revenir en arrière plus tard, mais ce sera un ajout à votre algorithme. Et si un intervieweur regarde sa montre, cela peut vous poser problème. Il existe des façons complexes de faire un parcours itératif de post-commande, elles existent, mais elles ne sont pas simples . Exemple:https://www.geeksforgeeks.org/iterative-postorder-traversal-using-stack/
Ainsi, l'essentiel: j'utiliserais la récursivité lors des entretiens, c'est plus simple à gérer et à expliquer. Vous avez un moyen facile de passer de la traversée pré-post-commande dans tous les cas urgents. Avec itératif, vous n'êtes pas si flexible.
J'utiliserais la récursivité et dirais ensuite: "Ok, mais l'itératif peut me fournir un contrôle plus direct sur la mémoire utilisée, je peux facilement mesurer la taille de la pile et interdire certains débordements dangereux .."
Un autre avantage de la récursivité - il est plus simple d'éviter les cycles / préavis dans un graphique.
Exemple (preudocode):
dft(n){
mark(n)
for(child: n.children){
if(marked(child))
explode - cycle found!!!
dft(child)
}
unmark(n)
}