Я обнаружил еще одно различие между этими подходами. Это выглядит просто и неважно, но оно играет очень важную роль, пока вы готовитесь к собеседованиям, и эта тема возникает, так что присмотритесь.
Вкратце: 1) итеративный обход по порядку не прост - это делает DFT более сложным 2) проверка циклов легче с помощью рекурсии
Подробности:
В рекурсивном случае легко создать обходы до и после:
Представьте себе довольно стандартный вопрос: «напечатать все задачи, которые должны быть выполнены для выполнения задачи 5, когда задачи зависят от других задач»
Пример:
//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!
}
}
Обратите внимание, что рекурсивный обход после заказа не требует последующего обращения результата. Дети напечатали сначала, а ваше задание в вопросе напечатано последним. Все хорошо. Вы можете выполнить рекурсивный обход по предварительному заказу (также показан выше), и для этого потребуется изменить список результатов.
Не так просто с итеративным подходом! При итеративном подходе (один стек) вы можете выполнять только предварительный порядок обхода, поэтому в конце вы должны обратить массив результатов:
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;
}
Выглядит просто, нет?
Но это ловушка в некоторых интервью.
Это означает следующее: при рекурсивном подходе вы можете внедрить метод Depth First Traversal, а затем выбрать, какой порядок вам нужен до или после публикации (просто изменив местоположение «print», в нашем случае «добавление в список результатов»). ). С помощью итеративного подхода (один стек) вы можете легко выполнять только обход по предварительному заказу, и поэтому в ситуации, когда дочерние элементы должны быть напечатаны первыми (почти во всех ситуациях, когда вам нужно начать печать с нижних узлов, идущих вверх), вы находитесь в проблема. Если у вас возникли проблемы, вы можете вернуться позже, но это будет дополнение к вашему алгоритму. И если интервьюер смотрит на часы, это может стать для вас проблемой. Существуют сложные способы итеративного обхода после заказа, они существуют, но они не просты . Пример:https://www.geeksforgeeks.org/iterative-postorder-traversal-using-stack/
Таким образом, суть: я бы использовал рекурсию во время собеседований, проще управлять и объяснять. В любом неотложном случае у вас есть простой путь от обхода до заказа к отправке. С итеративным вы не так гибки.
Я бы использовал рекурсию и затем сказал бы: «Хорошо, но итеративный может предоставить мне более прямой контроль над используемой памятью, я могу легко измерить размер стека и предотвратить некоторые опасные переполнения ...»
Еще один плюс рекурсии - проще избегать / замечать циклы в графе.
Пример (преудокод):
dft(n){
mark(n)
for(child: n.children){
if(marked(child))
explode - cycle found!!!
dft(child)
}
unmark(n)
}