Sự khác biệt giữa tìm kiếm theo chiều sâu và tìm kiếm đầu tiên theo chiều sâu là gì?
Sự khác biệt giữa tìm kiếm theo chiều sâu và tìm kiếm đầu tiên theo chiều sâu là gì?
Câu trả lời:
Backtracking là một thuật toán có mục đích chung hơn.
Tìm kiếm theo chiều sâu-đầu tiên là một dạng backtracking cụ thể liên quan đến việc tìm kiếm cấu trúc cây. Từ Wikipedia:
Một người bắt đầu từ gốc (chọn một số nút làm gốc trong trường hợp đồ thị) và khám phá càng nhiều càng tốt dọc theo mỗi nhánh trước khi bẻ khóa ngược.
Nó sử dụng backtracking như một phần của phương tiện làm việc với cây, nhưng chỉ giới hạn trong cấu trúc cây.
Tuy nhiên, backtracking có thể được sử dụng trên bất kỳ loại cấu trúc nào mà các phần của miền có thể bị loại bỏ - cho dù nó có phải là cây logic hay không. Ví dụ Wiki sử dụng bàn cờ và một vấn đề cụ thể - bạn có thể xem xét một nước đi cụ thể và loại bỏ nó, sau đó quay lại nước đi có thể tiếp theo, loại bỏ nó, v.v.
Tôi nghĩ câu trả lời này cho một câu hỏi liên quan khác cung cấp nhiều hiểu biết hơn.
Đối với tôi, sự khác biệt giữa backtracking và DFS là backtracking xử lý một cây ẩn và DFS xử lý một cây rõ ràng. Điều này có vẻ tầm thường, nhưng nó có ý nghĩa rất lớn. Khi không gian tìm kiếm của một vấn đề được truy cập bởi backtracking, cây ẩn sẽ bị cắt ngang và cắt tỉa ở giữa nó. Tuy nhiên, đối với DFS, cây / đồ thị mà nó xử lý được xây dựng một cách rõ ràng và các trường hợp không thể chấp nhận được đã bị loại bỏ, tức là bị cắt bỏ, đi trước khi thực hiện bất kỳ tìm kiếm nào.
Vì vậy, backtracking là DFS cho cây ngầm, trong khi DFS là backtracking mà không cắt tỉa.
Backtracking thường được thực hiện dưới dạng DFS cộng với việc cắt tỉa tìm kiếm. Bạn vượt qua độ sâu của cây không gian tìm kiếm, đầu tiên là xây dựng các giải pháp từng phần trên đường đi. Brute-force DFS có thể tạo ra tất cả các kết quả tìm kiếm, ngay cả những kết quả không có ý nghĩa trên thực tế. Điều này cũng có thể rất kém hiệu quả để xây dựng tất cả các nghiệm (n! Hoặc 2 ^ n). Vì vậy, trong thực tế khi bạn làm DFS, bạn cũng cần phải cắt bỏ các giải pháp từng phần, không có ý nghĩa trong bối cảnh của nhiệm vụ thực tế và tập trung vào các giải pháp từng phần, có thể dẫn đến các giải pháp tối ưu hợp lệ. Đây là kỹ thuật quay lui thực tế - bạn loại bỏ các giải pháp từng phần càng sớm càng tốt, lùi lại một bước và cố gắng tìm lại điểm tối ưu cục bộ.
Không có gì dừng lại để duyệt qua cây không gian tìm kiếm bằng cách sử dụng BFS và thực hiện chiến lược backtracking trong quá trình thực hiện, nhưng nó không có ý nghĩa trong thực tế vì bạn sẽ cần lưu trữ trạng thái tìm kiếm từng lớp trong hàng đợi và chiều rộng cây tăng theo cấp số nhân với chiều cao, vì vậy chúng tôi sẽ lãng phí rất nhiều không gian rất nhanh chóng. Đó là lý do tại sao cây thường được duyệt qua bằng DFS. Trong trường hợp này, trạng thái tìm kiếm được lưu trữ trong ngăn xếp (ngăn xếp cuộc gọi hoặc cấu trúc rõ ràng) và nó không thể vượt quá chiều cao cây.
Thông thường, tìm kiếm theo chiều sâu là một cách lặp lại thông qua một biểu đồ / cấu trúc cây thực tế để tìm kiếm một giá trị, trong khi backtracking là lặp lại qua một không gian vấn đề để tìm kiếm giải pháp. Backtracking là một thuật toán tổng quát hơn không nhất thiết phải liên quan đến cây.
Tôi có thể nói, DFS là một dạng backtracking đặc biệt; backtracking là hình thức chung của DFS.
Nếu chúng ta mở rộng DFS cho các vấn đề chung, chúng ta có thể gọi nó là backtracking. Nếu chúng ta sử dụng backtracking cho các vấn đề liên quan đến cây / đồ thị, chúng ta có thể gọi nó là DFS.
Chúng mang cùng một ý tưởng về khía cạnh thuật toán.
Theo Donald Knuth, nó cũng vậy. Đây là liên kết trên bài báo của anh ấy về thuật toán Dancing Links, được sử dụng để giải các bài toán "không phải cây" như N-Queen và bộ giải Sudoku.
IMHO, hầu hết các câu trả lời phần lớn là không chính xác và / hoặc không có bất kỳ tham chiếu nào để xác minh. Vì vậy, hãy để tôi chia sẻ một lời giải thích rất rõ ràng với một người tham khảo .
Đầu tiên, DFS là một thuật toán duyệt (và tìm kiếm) đồ thị tổng quát. Vì vậy, nó có thể được áp dụng cho bất kỳ đồ thị nào (hoặc thậm chí cả rừng). Cây là một loại Đồ thị đặc biệt, vì vậy DFS cũng hoạt động cho cây. Về bản chất, chúng ta hãy ngừng nói rằng nó chỉ hoạt động cho một cái cây hoặc những thứ tương tự.
Dựa trên [1], Backtracking là một loại DFS đặc biệt được sử dụng chủ yếu để tiết kiệm dung lượng (bộ nhớ). Sự khác biệt mà tôi sắp đề cập có vẻ khó hiểu vì trong các thuật toán Đồ thị loại như vậy, chúng ta đã quá quen với việc biểu diễn danh sách kề và sử dụng mẫu lặp để truy cập tất cả các hàng xóm ngay lập tức ( đối với cây, nó là con trực tiếp ) của một nút , chúng tôi thường bỏ qua rằng việc triển khai get_all_im ngay_neighbors không tốt có thể gây ra sự khác biệt trong việc sử dụng bộ nhớ của thuật toán cơ bản.
Hơn nữa, nếu một nút đồ thị có hệ số phân nhánh b và đường kính h ( đối với một cây thì đây là chiều cao của cây ), nếu chúng ta lưu trữ tất cả các hàng xóm ngay lập tức ở mỗi bước truy cập một nút, yêu cầu bộ nhớ sẽ là big-O (bh) . Tuy nhiên, nếu chúng ta chỉ lấy một hàng xóm duy nhất (tức thì) tại một thời điểm và mở rộng nó, thì độ phức tạp của bộ nhớ giảm xuống còn big-O (h) . Trong khi loại triển khai trước đây được gọi là DFS , loại sau được gọi là Backtracking .
Bây giờ bạn thấy, nếu bạn đang làm việc với các ngôn ngữ cấp cao, rất có thể bạn đang thực sự sử dụng Backtracking dưới vỏ bọc của DFS. Hơn nữa, việc theo dõi các nút đã truy cập cho một tập hợp vấn đề rất lớn có thể thực sự tốn nhiều bộ nhớ; yêu cầu một thiết kế cẩn thận của get_all_im Instant_neighbors (hoặc các thuật toán có thể xử lý việc truy cập lại một nút mà không cần đi vào vòng lặp vô hạn).
[1] Stuart Russell và Peter Norvig, Trí tuệ nhân tạo: Phương pháp tiếp cận hiện đại, xuất bản lần thứ 3
Độ sâu đầu tiên là một thuật toán để đi qua hoặc tìm kiếm một cây. Xem tại đây . Backtracking là một thuật ngữ rộng hơn nhiều được sử dụng bất cứ khi nào một ứng cử viên giải pháp được hình thành và sau đó bị loại bỏ bằng cách backtracking về trạng thái cũ. Xem tại đây . Tìm kiếm theo độ sâu đầu tiên sử dụng tính năng theo dõi ngược để tìm kiếm một nhánh trước (ứng cử viên giải pháp) và nếu không thành công tìm kiếm (các) nhánh khác.
DFS mô tả cách bạn muốn khám phá hoặc duyệt qua một biểu đồ. Nó tập trung vào khái niệm đi sâu nhất có thể cho sự lựa chọn.
Backtracking, trong khi thường được thực hiện thông qua DFS, tập trung nhiều hơn vào khái niệm cắt bỏ các không gian con tìm kiếm không giới hạn càng sớm càng tốt.
Trong tìm kiếm theo chiều sâu , bạn bắt đầu từ gốc của cây và sau đó khám phá dọc theo từng nhánh, sau đó bạn quay lại từng nút cha tiếp theo và duyệt qua nút con của nó.
Backtracking là một thuật ngữ tổng quát để chỉ việc bắt đầu từ khi kết thúc mục tiêu và dần dần lùi lại, dần dần xây dựng một giải pháp.
IMO, trên bất kỳ nút cụ thể nào của backtracking, bạn cố gắng phân nhánh sâu trước tiên vào từng nút con của nó, nhưng trước khi bạn phân nhánh vào bất kỳ nút con nào, bạn cần "xóa sạch" trạng thái của nút con trước đó (bước này tương đương với quay lại đi bộ đến nút cha). Nói cách khác, mỗi trạng thái anh chị em không nên tác động lẫn nhau.
Ngược lại, trong thuật toán DFS thông thường, bạn thường không có ràng buộc này, bạn không cần phải xóa (theo dõi ngược) trạng thái anh chị em trước đó để xây dựng nút anh em tiếp theo.
Ý tưởng - Bắt đầu từ bất kỳ điểm nào, kiểm tra xem nó có phải là điểm cuối mong muốn không, nếu có thì chúng tôi tìm thấy một giải pháp khác là chuyển đến tất cả các vị trí có thể tiếp theo và nếu chúng tôi không thể đi xa hơn thì hãy quay lại vị trí cũ và tìm kiếm các giải pháp thay thế khác đánh dấu hiện tại đó đường dẫn sẽ không dẫn chúng ta đến giải pháp.
Bây giờ backtracking và DFS là 2 tên khác nhau được đặt cho cùng một ý tưởng được áp dụng trên 2 kiểu dữ liệu trừu tượng khác nhau.
Nếu ý tưởng được áp dụng trên cấu trúc dữ liệu ma trận, chúng tôi gọi nó là backtracking.
Nếu cùng một ý tưởng được áp dụng trên cây hoặc đồ thị thì chúng ta gọi nó là DFS.
Câu nói sáo rỗng ở đây là một ma trận có thể được chuyển đổi thành một đồ thị và một đồ thị có thể được chuyển đổi thành một ma trận. Vì vậy, chúng tôi thực sự áp dụng ý tưởng. Nếu trên một đồ thị thì chúng ta gọi nó là DFS và nếu trên một ma trận thì chúng ta gọi nó là backtracking.
Ý tưởng trong cả hai thuật toán là giống nhau.
Backtracking chỉ là tìm kiếm chiều sâu đầu tiên với các điều kiện chấm dứt cụ thể.
Hãy xem xét việc đi qua một mê cung mà đối với mỗi bước bạn đưa ra quyết định, quyết định đó là một lệnh gọi đến ngăn xếp cuộc gọi (thực hiện tìm kiếm theo chiều sâu của bạn trước) ... nếu bạn đến cuối, bạn có thể quay lại con đường của mình. Tuy nhiên, nếu bạn đạt đến điểm dừng, bạn muốn quay trở lại từ một quyết định nhất định, về bản chất là quay lại một hàm trên ngăn xếp cuộc gọi của bạn.
Vì vậy, khi tôi nghĩ về backtracking, tôi quan tâm đến
Tôi giải thích nó trong video của tôi về backtracking ở đây .
Dưới đây là một phân tích về mã backtracking. Trong mã backtracking này, tôi muốn tất cả các kết hợp sẽ dẫn đến một tổng hoặc mục tiêu nhất định. Do đó, tôi có 3 quyết định thực hiện các cuộc gọi đến ngăn xếp cuộc gọi của mình, tại mỗi quyết định, tôi có thể chọn một số như một phần của đường dẫn để đạt đến số mục tiêu, bỏ qua số đó hoặc chọn và chọn lại. Và sau đó nếu tôi đạt đến điều kiện chấm dứt hợp đồng, bước quay lại của tôi chỉ là quay trở lại . Quay lại là bước quay lại vì nó thoát ra khỏi cuộc gọi đó trên ngăn xếp cuộc gọi.
class Solution:
"""
Approach: Backtracking
State
-candidates
-index
-target
Decisions
-pick one --> call func changing state: index + 1, target - candidates[index], path + [candidates[index]]
-pick one again --> call func changing state: index, target - candidates[index], path + [candidates[index]]
-skip one --> call func changing state: index + 1, target, path
Base Cases (Termination Conditions)
-if target == 0 and path not in ret
append path to ret
-if target < 0:
return # backtrack
"""
def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
"""
@desc find all unique combos summing to target
@args
@arg1 candidates, list of ints
@arg2 target, an int
@ret ret, list of lists
"""
if not candidates or min(candidates) > target: return []
ret = []
self.dfs(candidates, 0, target, [], ret)
return ret
def dfs(self, nums, index, target, path, ret):
if target == 0 and path not in ret:
ret.append(path)
return #backtracking
elif target < 0 or index >= len(nums):
return #backtracking
# for i in range(index, len(nums)):
# self.dfs(nums, i, target-nums[i], path+[nums[i]], ret)
pick_one = self.dfs(nums, index + 1, target - nums[index], path + [nums[index]], ret)
pick_one_again = self.dfs(nums, index, target - nums[index], path + [nums[index]], ret)
skip_one = self.dfs(nums, index + 1, target, path, ret)