Znalazłem inne różnice między tymi podejściami. Wygląda na prosty i nieważny, ale ma bardzo ważną rolę podczas przygotowywania się do wywiadów i ten temat powstaje, więc przyjrzyj się uważnie.
W skrócie: 1) iteracyjne przechodzenie po zamówieniu nie jest łatwe - to sprawia, że DFT jest bardziej złożone 2) sprawdzanie cykli z rekurencją
Detale:
W przypadku rekurencyjnym łatwo jest tworzyć przejścia przed i po:
Wyobraź sobie dość standardowe pytanie: „wydrukuj wszystkie zadania, które powinny zostać wykonane, aby wykonać zadanie 5, gdy zadania zależą od innych zadań”
Przykład:
//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!
}
}
Należy zauważyć, że rekurencyjne przechodzenie po zamówieniu nie wymaga późniejszego odwrócenia wyniku. Dzieci drukowane jako pierwsze, a twoje zadanie w pytaniu drukowane jako ostatnie. Wszystko w porządku. Możesz wykonać rekursywne przechodzenie przed zamówieniem (również pokazane powyżej), które będzie wymagało odwrócenia listy wyników.
Nie jest to takie proste z iteracyjnym podejściem! W podejściu iteracyjnym (jeden stos) możesz wykonać tylko przechodzenie w przedsprzedaży, więc musisz odwrócić tablicę wyników na końcu:
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;
}
Wygląda prosto, nie?
Ale w niektórych wywiadach jest to pułapka.
Oznacza to, że: stosując podejście rekurencyjne, możesz wdrożyć Głębokość Najpierw przemieszczenie, a następnie wybrać, jakiej kolejności potrzebujesz przed lub po (zmieniając położenie „wydruku”, w naszym przypadku „dodawania do listy wyników” ). Dzięki iteracyjnemu podejściu (jeden stos) możesz łatwo zrobić tylko przejście w przedsprzedaży, a więc w sytuacji, gdy dzieci muszą być najpierw wydrukowane (prawie wszystkie sytuacje, gdy potrzebujesz rozpocząć drukowanie od dolnych węzłów, idąc w górę) - jesteś w problem. Jeśli masz takie problemy, możesz później je odwrócić, ale będzie to dodatek do Twojego algorytmu. A jeśli ankieter patrzy na zegarek, może to stanowić dla ciebie problem. Istnieją złożone sposoby iteracyjnego przechodzenia po zamówieniu, istnieją, ale nie są proste . Przykład:https://www.geeksforgeeks.org/iterative-postorder-traversal-using-stack/
Podsumowując: użyłbym rekurencji podczas wywiadów, łatwiej jest zarządzać i wyjaśniać. W każdej pilnej sprawie masz łatwy sposób na przejście od zamówienia przed i po zamówieniu. Dzięki iteracji nie jesteś tak elastyczny.
Użyłbym rekurencji, a następnie powiedziałbym: „Ok, ale iteracja może zapewnić mi bardziej bezpośrednią kontrolę nad używaną pamięcią, mogę łatwo zmierzyć rozmiar stosu i zapobiec niebezpiecznemu przepełnieniu…”
Kolejny plus rekurencji - łatwiej jest unikać / zauważać cykle na wykresie.
Przykład (preudocode):
dft(n){
mark(n)
for(child: n.children){
if(marked(child))
explode - cycle found!!!
dft(child)
}
unmark(n)
}