Tôi tìm thấy một sự khác biệt khác giữa những cách tiếp cận đó. Trông có vẻ đơn giản và không quan trọng, nhưng nó có một vai trò rất quan trọng trong khi bạn chuẩn bị cho các cuộc phỏng vấn và chủ đề này phát sinh, vì vậy hãy xem xét kỹ.
Tóm lại: 1) chuyển đổi theo thứ tự lặp lại không dễ dàng - điều đó làm cho DFT phức tạp hơn 2) kiểm tra chu kỳ dễ dàng hơn với đệ quy
Chi tiết:
Trong trường hợp đệ quy, thật dễ dàng để tạo các giao dịch trước và sau:
Hãy tưởng tượng một câu hỏi khá chuẩn: "in tất cả các tác vụ nên được thực thi để thực thi tác vụ 5, khi các tác vụ phụ thuộc vào các tác vụ khác"
Thí dụ:
//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!
}
}
Lưu ý rằng đệ quy theo thứ tự đệ quy không yêu cầu đảo ngược kết quả sau đó. Trẻ em in trước và nhiệm vụ của bạn trong câu hỏi được in cuối cùng. Mọi thứ đều ổn. Bạn có thể thực hiện chuyển đổi theo thứ tự đệ quy (cũng được hiển thị ở trên) và người ta sẽ yêu cầu đảo ngược danh sách kết quả.
Không đơn giản với cách tiếp cận lặp đi lặp lại! Trong phương pháp lặp (một ngăn xếp), bạn chỉ có thể thực hiện đặt hàng trước, vì vậy bạn bắt buộc phải đảo ngược mảng kết quả ở cuối:
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;
}
Trông đơn giản phải không?
Nhưng nó là một cái bẫy trong một số cuộc phỏng vấn.
Nó có nghĩa như sau: với cách tiếp cận đệ quy, bạn có thể triển khai Depth First Traversal và sau đó chọn thứ tự bạn cần trước hoặc đăng (đơn giản bằng cách thay đổi vị trí của "bản in", trong trường hợp của chúng tôi là "thêm vào danh sách kết quả" ). Với cách tiếp cận lặp (một ngăn xếp), bạn có thể dễ dàng thực hiện chỉ cần đặt hàng trước và trong trường hợp khi trẻ cần in trước (khá nhiều tình huống khi bạn cần bắt đầu in từ các nút dưới cùng, đi lên trên) - bạn đang ở trong rắc rối. Nếu bạn gặp rắc rối đó, bạn có thể đảo ngược lại sau, nhưng nó sẽ là một bổ sung cho thuật toán của bạn. Và nếu một người phỏng vấn đang nhìn vào đồng hồ của anh ta, nó có thể là một vấn đề cho bạn. Có nhiều cách phức tạp để thực hiện một giao dịch lặp theo thứ tự lặp, chúng tồn tại, nhưng chúng không đơn giản . Thí dụ:https://www.geekforgeek.org/iterative-postorder-traversal-USE-stack/
Do đó, điểm mấu chốt: Tôi sẽ sử dụng đệ quy trong các cuộc phỏng vấn, việc quản lý và giải thích sẽ đơn giản hơn. Bạn có một cách dễ dàng để đi từ trước đến sau khi đặt hàng trong bất kỳ trường hợp khẩn cấp nào. Với phép lặp bạn không linh hoạt.
Tôi sẽ sử dụng đệ quy và sau đó nói: "Ok, nhưng lặp có thể cung cấp cho tôi quyền kiểm soát trực tiếp hơn trên bộ nhớ đã sử dụng, tôi có thể dễ dàng đo kích thước ngăn xếp và không cho phép một số tràn nguy hiểm .."
Một điểm cộng khác của đệ quy - đơn giản hơn là tránh / thông báo các chu kỳ trong biểu đồ.
Ví dụ (preudocode):
dft(n){
mark(n)
for(child: n.children){
if(marked(child))
explode - cycle found!!!
dft(child)
}
unmark(n)
}