Ich fand einen weiteren Unterschied zwischen diesen Ansätzen. Es sieht einfach und unwichtig aus, spielt aber eine sehr wichtige Rolle, während Sie sich auf Interviews vorbereiten und dieses Thema auftaucht. Schauen Sie also genau hin.
Kurz gesagt: 1) Das iterative Durchlaufen nach der Bestellung ist nicht einfach - das macht die DFT komplexer. 2) Die Überprüfung der Zyklen wird durch Rekursion einfacher
Einzelheiten:
Im rekursiven Fall ist es einfach, Vor- und Nachdurchläufe zu erstellen:
Stellen Sie sich eine ziemlich normale Frage vor: "Drucken Sie alle Aufgaben aus, die ausgeführt werden sollen, um die Aufgabe 5 auszuführen, wenn Aufgaben von anderen Aufgaben abhängen."
Beispiel:
//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!
}
}
Beachten Sie, dass für die rekursive Nachbestellungsdurchquerung keine spätere Umkehrung des Ergebnisses erforderlich ist. Kinder zuerst gedruckt und Ihre Aufgabe in der Frage zuletzt gedruckt. Alles ist gut. Sie können eine rekursive Vorbestellungsdurchquerung durchführen (siehe auch oben), für die eine Umkehrung der Ergebnisliste erforderlich ist.
Nicht so einfach mit iterativem Ansatz! Beim iterativen Ansatz (mit einem Stapel) können Sie nur eine Vorbestellungsdurchquerung durchführen, sodass Sie das Ergebnisarray am Ende umkehren müssen:
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;
}
Sieht einfach aus, nein?
Aber es ist eine Falle in einigen Interviews.
Dies bedeutet Folgendes: Mit dem rekursiven Ansatz können Sie Depth First Traversal implementieren und dann auswählen, welche Reihenfolge Sie vor oder nach dem Vorgang benötigen (indem Sie einfach die Position des "Drucks" ändern, in unserem Fall "Hinzufügen zur Ergebnisliste"). ). Mit dem iterativen Ansatz (ein Stapel) können Sie problemlos nur das Durchlaufen vorbestellen. In Situationen, in denen Kinder zuerst gedruckt werden müssen (so ziemlich alle Situationen, in denen Sie mit dem Drucken von den unteren Knoten nach oben beginnen müssen), befinden Sie sich in die Schwierigkeiten. Wenn Sie diese Probleme haben, können Sie sie später rückgängig machen, dies ist jedoch eine Ergänzung Ihres Algorithmus. Und wenn ein Interviewer auf seine Uhr schaut, kann dies ein Problem für Sie sein. Es gibt komplexe Möglichkeiten, eine iterative Nachbestellungsdurchquerung durchzuführen. Sie existieren, sind jedoch nicht einfach . Beispiel:https://www.geeksforgeeks.org/iterative-postorder-traversal-using-stack/
Fazit: Ich würde bei Interviews Rekursion verwenden, es ist einfacher zu verwalten und zu erklären. In dringenden Fällen haben Sie einen einfachen Weg von der Durchquerung vor und nach der Bestellung. Mit iterativ sind Sie nicht so flexibel.
Ich würde die Rekursion verwenden und dann sagen: "Ok, aber iterativ kann mir eine direktere Kontrolle über den verwendeten Speicher ermöglichen. Ich kann die Stapelgröße leicht messen und einen gefährlichen Überlauf verbieten."
Ein weiteres Plus der Rekursion: Es ist einfacher, Zyklen in einem Diagramm zu vermeiden / zu bemerken.
Beispiel (Preudocode):
dft(n){
mark(n)
for(child: n.children){
if(marked(child))
explode - cycle found!!!
dft(child)
}
unmark(n)
}