อะไรคือความแตกต่างระหว่างการค้นหาย้อนหลังและการค้นหาครั้งแรกเชิงลึก
อะไรคือความแตกต่างระหว่างการค้นหาย้อนหลังและการค้นหาครั้งแรกเชิงลึก
คำตอบ:
การย้อนรอยเป็นอัลกอริทึมที่มีวัตถุประสงค์ทั่วไปมากกว่า
การค้นหาแบบชัดลึกเป็นรูปแบบเฉพาะของการย้อนรอยที่เกี่ยวข้องกับการค้นหาโครงสร้างต้นไม้ จาก Wikipedia:
หนึ่งเริ่มต้นที่รูท (เลือกโหนดบางโหนดเป็นรูทในกรณีกราฟ) และสำรวจให้ไกลที่สุดในแต่ละสาขาก่อนที่จะย้อนรอย
ใช้การย้อนรอยเป็นส่วนหนึ่งของวิธีการทำงานกับต้นไม้ แต่ จำกัด เฉพาะโครงสร้างต้นไม้
แม้ว่าการย้อนกลับสามารถใช้กับโครงสร้างประเภทใดก็ได้ที่สามารถกำจัดบางส่วนของโดเมนได้ไม่ว่าจะเป็นโครงสร้างแบบลอจิคัลหรือไม่ก็ตาม ตัวอย่าง Wiki ใช้กระดานหมากรุกและปัญหาเฉพาะ - คุณสามารถดูการเคลื่อนไหวที่เฉพาะเจาะจงและกำจัดมันจากนั้นย้อนกลับไปยังการเคลื่อนไหวถัดไปที่เป็นไปได้กำจัดมัน ฯลฯ
ฉันคิดว่าคำตอบสำหรับคำถามอื่นที่เกี่ยวข้องนี้ให้ข้อมูลเชิงลึกมากกว่า
สำหรับฉันความแตกต่างระหว่างการย้อนรอยและ DFS คือการย้อนกลับจะจัดการกับทรีโดยนัยและ DFS เกี่ยวข้องกับสิ่งที่ชัดเจน สิ่งนี้ดูเหมือนเป็นเรื่องเล็กน้อย แต่มีความหมายมาก เมื่อพื้นที่การค้นหาของปัญหาถูกเยี่ยมชมโดยการย้อนรอยต้นไม้โดยนัยจะถูกลากผ่านและตัดตรงกลางของปัญหา แต่สำหรับ DFS ต้นไม้ / กราฟที่เกี่ยวข้องนั้นถูกสร้างขึ้นอย่างชัดเจนและกรณีที่ยอมรับไม่ได้ได้ถูกโยนทิ้งไปแล้วเช่นตัดออกก่อนที่จะทำการค้นหาใด ๆ
ดังนั้นการย้อนรอยจึงเป็น DFS สำหรับต้นไม้โดยนัยในขณะที่ DFS กำลังย้อนรอยโดยไม่ต้องตัด
โดยปกติแล้วการย้อนรอยจะใช้เป็น DFS บวกกับการตัดการค้นหา คุณสำรวจแผนผังพื้นที่ค้นหาเชิงลึกก่อนสร้างโซลูชันบางส่วนไปพร้อมกัน Brute-force DFS สามารถสร้างผลลัพธ์การค้นหาทั้งหมดแม้แต่สิ่งที่ไม่สมเหตุสมผลในทางปฏิบัติ สิ่งนี้อาจไม่มีประสิทธิภาพในการสร้างโซลูชันทั้งหมด (n! หรือ 2 ^ n) ดังนั้นในความเป็นจริงในขณะที่คุณทำ DFS คุณจำเป็นต้องตัดวิธีแก้ปัญหาบางส่วนซึ่งไม่สมเหตุสมผลในบริบทของงานจริงและมุ่งเน้นไปที่โซลูชันบางส่วนซึ่งอาจนำไปสู่โซลูชันที่เหมาะสมที่สุด นี่เป็นเทคนิคการย้อนรอยจริง - คุณทิ้งโซลูชันบางส่วนโดยเร็วที่สุดย้อนกลับไปและพยายามหาค่าที่เหมาะสมในท้องถิ่นอีกครั้ง
ไม่มีอะไรหยุดที่จะสำรวจแผนผังพื้นที่การค้นหาโดยใช้ BFS และใช้กลยุทธ์การย้อนกลับไปพร้อมกัน แต่มันไม่สมเหตุสมผลในทางปฏิบัติเพราะคุณจะต้องจัดเก็บสถานะการค้นหาทีละเลเยอร์ในคิวและความกว้างของต้นไม้จะเพิ่มขึ้นเป็นทวีคูณตามความสูง เราจะเสียพื้นที่ไปมากอย่างรวดเร็ว นั่นเป็นเหตุผลว่าทำไมต้นไม้จึงมักถูกส่งผ่านโดยใช้ DFS ในกรณีนี้สถานะการค้นหาจะถูกเก็บไว้ในสแต็ก (เรียกสแต็กหรือโครงสร้างที่ชัดเจน) และต้องไม่เกินความสูงของต้นไม้
IMHO คำตอบส่วนใหญ่ไม่ชัดเจนและ / หรือไม่มีการอ้างอิงใด ๆ เพื่อยืนยัน เพื่อให้ฉันแบ่งปันคำอธิบายที่ชัดเจนมากกับการอ้างอิง
ประการแรก DFS เป็นอัลกอริธึมการข้ามผ่านกราฟทั่วไป (และการค้นหา) ดังนั้นจึงสามารถนำไปใช้กับกราฟใดก็ได้ (หรือแม้แต่ฟอเรสต์) Tree เป็นกราฟชนิดพิเศษดังนั้น DFS จึงใช้ได้กับต้นไม้เช่นกัน โดยพื้นฐานแล้วเราจะหยุดพูดว่ามันใช้ได้กับต้นไม้หรือสิ่งที่ชอบเท่านั้น
โดยอ้างอิงจาก [1] Backtracking เป็น DFS ชนิดพิเศษที่ใช้สำหรับการประหยัดพื้นที่ (หน่วยความจำ) เป็นหลัก ความแตกต่างที่ฉันกำลังจะพูดถึงอาจดูสับสนเนื่องจากในอัลกอริธึมกราฟประเภทนี้เราคุ้นเคยกับการแสดงรายการ adjacency และใช้รูปแบบซ้ำสำหรับการเยี่ยมเพื่อนบ้านทั้งหมดที่อยู่ใกล้เคียง ( สำหรับต้นไม้ที่เป็นลูกในทันที ) ของโหนด เรามักจะเพิกเฉยว่าการนำget_all_immediate_neighborsไปใช้อย่างไม่ถูกต้องอาจทำให้เกิดความแตกต่างในการใช้หน่วยความจำของอัลกอริทึมพื้นฐาน
ต่อไปหากโหนดกราฟได้แตกแขนงปัจจัย B และเส้นผ่าศูนย์กลาง h ( ต้นไม้นี้เป็นความสูงของต้นไม้ ) ถ้าเราเก็บเพื่อนบ้านทั้งหมดในขั้นตอนของการเยี่ยมชมโหนดแต่ละต้องการหน่วยความจำจะใหญ่ O (BH) แต่ถ้าเราใช้เวลาเพียงเดียว (ทันที) เพื่อนบ้านที่เวลาและขยายแล้วซับซ้อนหน่วยความจำลดไปใหญ่-O (H) ขณะที่อดีตชนิดของการดำเนินการจะเรียกว่าเป็นDFSชนิดหลังเรียกว่าย้อนรอย
ตอนนี้คุณเห็นแล้วว่าถ้าคุณกำลังทำงานกับภาษาระดับสูงส่วนใหญ่แล้วคุณจะใช้ Backtracking ในหน้ากาก DFS ยิ่งไปกว่านั้นการติดตามโหนดที่เยี่ยมชมสำหรับชุดปัญหาที่มีขนาดใหญ่มากอาจทำให้หน่วยความจำต้องใช้มากเกินไป เรียกร้องให้มีการออกแบบget_all_immediate_neighborsอย่างระมัดระวัง(หรืออัลกอริทึมที่สามารถจัดการการกลับมาที่โหนดได้โดยไม่ต้องวนซ้ำแบบไม่สิ้นสุด)
[1] สจวร์ตรัสเซลและปีเตอร์นอร์วิกปัญญาประดิษฐ์: แนวทางสมัยใหม่ฉบับที่ 3
โดยปกติการค้นหาในเชิงลึกเป็นวิธีการวนซ้ำผ่านโครงสร้างกราฟ / โครงสร้างต้นไม้จริงเพื่อค้นหาค่าในขณะที่การย้อนรอยเป็นการวนซ้ำผ่านพื้นที่ปัญหาเพื่อค้นหาวิธีแก้ไข Backtracking เป็นอัลกอริทึมทั่วไปที่ไม่จำเป็นต้องเกี่ยวข้องกับต้นไม้ด้วยซ้ำ
ตามที่ Donald Knuth ก็เช่นเดียวกัน นี่คือลิงค์บนเอกสารของเขาเกี่ยวกับอัลกอริทึม Dancing Links ซึ่งใช้ในการแก้ปัญหา "ที่ไม่ใช่ต้นไม้" เช่น N-queens และตัวแก้ Sudoku
ฉันจะบอกว่า DFS เป็นรูปแบบพิเศษของการย้อนรอย backtracking เป็นรูปแบบทั่วไปของ DFS
ถ้าเราขยาย DFS ไปสู่ปัญหาทั่วไปเราสามารถเรียกมันว่า backtracking หากเราใช้การย้อนกลับไปยังปัญหาที่เกี่ยวข้องกับแผนภูมิ / กราฟเราสามารถเรียกมันว่า DFS
พวกเขามีแนวคิดเดียวกันในด้านอัลกอริทึม
Depth first คืออัลกอริทึมสำหรับการสำรวจหรือค้นหาต้นไม้ ดูที่นี่ . Backtracking เป็นคำศัพท์ที่กว้างกว่ามากซึ่งใช้ไม่ว่าจะมีการสร้างผู้สมัครโซลูชันและถูกยกเลิกในภายหลังโดยการย้อนกลับไปยังสถานะเดิม ดูที่นี่ . การค้นหาครั้งแรกเชิงลึกจะใช้การย้อนกลับเพื่อค้นหาสาขาก่อน (ผู้สมัครโซลูชัน) และหากไม่ประสบความสำเร็จในการค้นหาสาขาอื่น
IMO บนโหนดเฉพาะใด ๆ ของ backtracking คุณพยายามเจาะลึกในขั้นแรกโดยแยกย่อยไปยังแต่ละลูกของมัน แต่ก่อนที่คุณจะแยกไปยังโหนดลูกใด ๆ คุณจะต้อง "ล้าง" สถานะของเด็กก่อนหน้านี้ (ขั้นตอนนี้เทียบเท่ากับ back เดินไปที่โหนดแม่) กล่าวอีกนัยหนึ่งรัฐพี่น้องแต่ละคนไม่ควรส่งผลกระทบต่อกันและกัน
ในทางตรงกันข้ามในระหว่างอัลกอริทึม DFS ปกติคุณมักไม่มีข้อ จำกัด นี้คุณไม่จำเป็นต้องล้างสถานะพี่น้องก่อนหน้า (back track) เพื่อสร้างโหนดพี่น้องถัดไป
DFS อธิบายวิธีที่คุณต้องการสำรวจหรือสำรวจกราฟ มุ่งเน้นไปที่แนวคิดของการลงลึกที่สุดเท่าที่จะเป็นไปได้ตามทางเลือก
Backtracking แม้ว่าโดยปกติจะใช้งานผ่าน DFS แต่จะเน้นไปที่แนวคิดของการตัดส่วนย่อยการค้นหาที่ไม่เป็นอันตรายให้เร็วที่สุด
ในการค้นหาเชิงลึกก่อนอื่นคุณเริ่มต้นที่รากของต้นไม้จากนั้นสำรวจไปตามแต่ละกิ่งก้านจากนั้นคุณจะย้อนกลับไปยังโหนดแม่ที่ตามมาแต่ละโหนดที่ตามมา
Backtrackingเป็นคำทั่วไปสำหรับการเริ่มต้นที่จุดสิ้นสุดของเป้าหมายและค่อยๆเคลื่อนไปข้างหลังค่อยๆสร้างวิธีแก้ปัญหา
แนวคิด - เริ่มต้นจากจุดใดก็ได้ตรวจสอบว่าเป็นจุดสิ้นสุดที่ต้องการหรือไม่ถ้าใช่เราพบวิธีแก้ปัญหาอื่นไปยังตำแหน่งที่เป็นไปได้ถัดไปทั้งหมดและหากเราไม่สามารถไปต่อได้ให้กลับไปที่ตำแหน่งก่อนหน้าและมองหาทางเลือกอื่นที่ทำเครื่องหมายปัจจุบันนั้น เส้นทางจะไม่นำเราไปสู่การแก้ปัญหา
ตอนนี้ backtracking และ DFS เป็นชื่อที่แตกต่างกัน 2 ชื่อที่กำหนดให้กับแนวคิดเดียวกันซึ่งใช้กับข้อมูลนามธรรม 2 ประเภทที่แตกต่างกัน
หากแนวคิดถูกนำไปใช้กับโครงสร้างข้อมูลเมทริกซ์เราเรียกว่าการย้อนรอย
หากใช้แนวคิดเดียวกันกับต้นไม้หรือกราฟเราจะเรียกมันว่า DFS
ถ้อยคำที่เบื่อหูในที่นี้คือเมทริกซ์สามารถแปลงเป็นกราฟและกราฟสามารถเปลี่ยนเป็นเมทริกซ์ได้ ดังนั้นเราจึงใช้แนวคิดนี้ ถ้าบนกราฟเราเรียกมันว่า DFS และถ้าอยู่บนเมทริกซ์เราจะเรียกมันว่าการย้อนรอย
ความคิดในอัลกอริทึมทั้งสองเหมือนกัน
การย้อนรอยเป็นการค้นหาในเชิงลึกก่อนโดยมีเงื่อนไขการยุติที่เฉพาะเจาะจง
พิจารณาการเดินผ่านเขาวงกตที่ซึ่งในแต่ละขั้นตอนที่คุณตัดสินใจการตัดสินใจนั้นเป็นการเรียกร้องให้กลุ่มการโทร (ซึ่งดำเนินการค้นหาเชิงลึกของคุณก่อน) ... หากคุณไปถึงจุดสิ้นสุดคุณสามารถกลับเส้นทางของคุณได้ อย่างไรก็ตามหากคุณมาถึงทางตันคุณต้องการกลับออกจากการตัดสินใจบางอย่างโดยพื้นฐานแล้วคือการกลับออกจากฟังก์ชันในกลุ่มการโทร
ดังนั้นเมื่อฉันคิดถึงการย้อนรอยฉันจึงสนใจ
ผมอธิบายในวิดีโอของฉันใน backtracking ที่นี่
การวิเคราะห์โค้ดย้อนหลังอยู่ด้านล่าง ในรหัสย้อนกลับนี้ฉันต้องการชุดค่าผสมทั้งหมดที่จะทำให้เกิดผลรวมหรือเป้าหมายที่แน่นอน ดังนั้นฉันจึงมีการตัดสินใจ 3 ครั้งในการโทรไปยังกลุ่มการโทรของฉันในการตัดสินใจแต่ละครั้งฉันสามารถเลือกหมายเลขเป็นส่วนหนึ่งของเส้นทางเพื่อไปให้ถึงเป้าหมายข้ามหมายเลขนั้นหรือเลือกแล้วเลือกอีกครั้ง จากนั้นถ้าผมถึงสภาพการเลิกจ้างขั้นตอนย้อนรอยของฉันเป็นเพียงการกลับมา การย้อนกลับเป็นขั้นตอนการย้อนรอยเนื่องจากการโทรออกจากกลุ่มการโทรนั้น
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)