Saya menemukan perbedaan lain antara pendekatan-pendekatan itu. Ini terlihat sederhana dan tidak penting, tetapi memiliki peran yang sangat penting saat Anda mempersiapkan wawancara dan masalah ini muncul, jadi perhatikan dengan cermat.
Singkatnya: 1) traversal post-order iteratif tidak mudah - yang membuat DFT lebih kompleks 2) siklus memeriksa lebih mudah dengan rekursi
Detail:
Dalam kasus rekursif, mudah untuk membuat travers pre dan post:
Bayangkan sebuah pertanyaan standar: "cetak semua tugas yang harus dieksekusi untuk menjalankan tugas 5, ketika tugas bergantung pada tugas lain"
Contoh:
//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!
}
}
Perhatikan bahwa post-order-traversal rekursif tidak memerlukan pembalikan hasil berikutnya. Anak-anak dicetak pertama dan tugas Anda dalam pertanyaan dicetak terakhir. Semuanya baik-baik saja. Anda dapat melakukan pra-pemesanan-traversal rekursif (juga ditunjukkan di atas) dan yang satu akan memerlukan pembalikan daftar hasil.
Tidak sesederhana itu dengan pendekatan berulang! Dalam pendekatan iteratif (satu tumpukan) Anda hanya dapat melakukan pra-pemesanan-traversal, sehingga Anda wajib membalikkan array hasil di akhir:
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;
}
Terlihat sederhana, bukan?
Tapi itu adalah jebakan dalam beberapa wawancara.
Ini berarti yang berikut: dengan pendekatan rekursif, Anda dapat mengimplementasikan Depth First Traversal dan kemudian memilih pesanan apa yang Anda butuhkan sebelum atau sesudahnya (hanya dengan mengubah lokasi "cetak", dalam kasus kami "menambahkan ke daftar hasil" ). Dengan pendekatan berulang (satu tumpukan) Anda dapat dengan mudah melakukan hanya pre-order traversal dan dalam situasi ketika anak-anak perlu dicetak terlebih dahulu (hampir semua situasi ketika Anda perlu mulai mencetak dari node bawah, naik ke atas) - Anda berada di masalah. Jika Anda memiliki masalah itu, Anda dapat membalikkannya nanti, tetapi itu akan menjadi tambahan untuk algoritma Anda. Dan jika pewawancara melihat arlojinya, itu mungkin menjadi masalah bagi Anda. Ada cara-cara kompleks untuk melakukan traversal post-order berulang, mereka ada, tetapi mereka tidak sederhana . Contoh:https://www.geeksforgeeks.org/iterative-postorder-traversal-using-stack/
Jadi, intinya: Saya akan menggunakan rekursi selama wawancara, lebih mudah untuk mengelola dan menjelaskan. Anda memiliki cara yang mudah untuk beralih dari traversal pra ke post-order dalam kasus mendesak apa pun. Dengan iteratif Anda tidak sefleksibel itu.
Saya akan menggunakan rekursi dan kemudian mengatakan: "Ok, tapi iteratif dapat memberi saya kontrol lebih langsung pada memori yang digunakan, saya dapat dengan mudah mengukur ukuran stack dan melarang beberapa overflow berbahaya .."
Kelebihan lain dari rekursi - lebih mudah untuk menghindari / memperhatikan siklus dalam grafik.
Contoh (preudocode):
dft(n){
mark(n)
for(child: n.children){
if(marked(child))
explode - cycle found!!!
dft(child)
}
unmark(n)
}