Bu yaklaşımlar arasında başka farklar buldum. Basit ve önemsiz görünüyor, ancak röportajlara hazırlanırken çok önemli bir rolü var ve bu konu ortaya çıkıyor, bu yüzden yakından bakın.
Kısacası: 1) yinelemeli post-order traversal kolay değildir - bu DFT'yi daha karmaşık hale getirir 2) döngüler özyineleme ile daha kolay kontrol edilir
Detaylar:
Özyinelemeli durumda, ön ve son geçişler oluşturmak kolaydır:
Oldukça standart bir soru düşünün: "görevler diğer görevlere bağlı olduğunda, görevi 5 yürütmek için gerçekleştirilmesi gereken tüm görevleri yazdırın"
Misal:
//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!
}
}
Yinelenen post-order-traversal sonucun daha sonra tersine çevrilmesini gerektirmediğini unutmayın. Önce çocuklar, sorudaki göreviniz de en son yazdırılır. Herşey yolunda. Yinelenen bir ön sipariş geçişi (yukarıda da gösterilmektedir) yapabilirsiniz ve sonuç listesinin tersine çevrilmesi gerekir.
Yinelemeli yaklaşımla o kadar da basit değil! Yinelemeli (bir yığın) yaklaşımda yalnızca bir ön sipariş-geçiş yapabilirsiniz, böylece sonuç dizisini sonunda tersine çevirmek zorunda kalırsınız:
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;
}
Basit görünüyor, değil mi?
Ama bazı görüşmelerde bir tuzak var.
Bu şu anlama gelir: özyinelemeli yaklaşımla, önce Derinlik İlk Geçişini uygulayabilir ve daha sonra hangi siparişe veya ön siparişe ihtiyacınız olduğunu seçebilirsiniz (sadece "baskı listesine" konumunu değiştirerek, "sonuç listesine ekleme" ). Yinelemeli (bir yığın) yaklaşımı ile kolayca sadece ön siparişi çaprazlama yapabilirsiniz ve böylece çocukların ilk önce basılması gerektiği durumda (hemen hemen alt düğümlerden yazdırmaya başlamanız gerektiğinde, hemen hemen tüm durumlar) - siz bela. Bu sorunla karşılaşırsanız, daha sonra tersine çevirebilirsiniz, ancak algoritmanıza bir ek olacaktır. Ve bir görüşmeci saatine bakıyorsa, bu sizin için bir sorun olabilir. Yinelenen bir post-order traversal yapmanın karmaşık yolları vardır, bunlar vardır, ancak basit değildir . Misal:https://www.geeksforgeeks.org/iterative-postorder-traversal-using-stack/
Sonuç olarak, görüşmeler sırasında özyineleme kullanırdım, yönetmek ve açıklamak daha kolaydır. Herhangi bir acil durumda ön siparişten sonrasına geçiş için kolay bir yolunuz var. Yinelemeyle o kadar esnek değilsiniz.
Özyineleme kullanır ve daha sonra şunu söylerdim: "Tamam, ama yinelemeli kullanılan bellek üzerinde daha doğrudan kontrol sağlayabilir, yığın boyutunu kolayca ölçebilir ve bazı tehlikeli taşmalara izin vermezim."
Bir başka özyineleme artı - bir grafikte döngüleri önlemek / fark etmek daha kolaydır.
Örnek (ön kod):
dft(n){
mark(n)
for(child: n.children){
if(marked(child))
explode - cycle found!!!
dft(child)
}
unmark(n)
}