อะไรคือความแตกต่างระหว่างการค้นหาย้อนหลังและการค้นหาครั้งแรกเชิงลึก


111

อะไรคือความแตกต่างระหว่างการค้นหาย้อนหลังและการค้นหาครั้งแรกเชิงลึก

คำตอบ:


103

การย้อนรอยเป็นอัลกอริทึมที่มีวัตถุประสงค์ทั่วไปมากกว่า

การค้นหาแบบชัดลึกเป็นรูปแบบเฉพาะของการย้อนรอยที่เกี่ยวข้องกับการค้นหาโครงสร้างต้นไม้ จาก Wikipedia:

หนึ่งเริ่มต้นที่รูท (เลือกโหนดบางโหนดเป็นรูทในกรณีกราฟ) และสำรวจให้ไกลที่สุดในแต่ละสาขาก่อนที่จะย้อนรอย

ใช้การย้อนรอยเป็นส่วนหนึ่งของวิธีการทำงานกับต้นไม้ แต่ จำกัด เฉพาะโครงสร้างต้นไม้

แม้ว่าการย้อนกลับสามารถใช้กับโครงสร้างประเภทใดก็ได้ที่สามารถกำจัดบางส่วนของโดเมนได้ไม่ว่าจะเป็นโครงสร้างแบบลอจิคัลหรือไม่ก็ตาม ตัวอย่าง Wiki ใช้กระดานหมากรุกและปัญหาเฉพาะ - คุณสามารถดูการเคลื่อนไหวที่เฉพาะเจาะจงและกำจัดมันจากนั้นย้อนกลับไปยังการเคลื่อนไหวถัดไปที่เป็นไปได้กำจัดมัน ฯลฯ


15
ตอบกระทู้โบราณที่นี่ คำตอบที่ดี แต่ ... ปัญหากระดานหมากรุกไม่สามารถแสดงเป็นต้นไม้ได้หรือไม่? :-) จากตำแหน่งใด ๆ บนกระดานหมากรุกสำหรับชิ้นส่วนที่กำหนดไม่มีต้นไม้แห่งการเคลื่อนไหวที่เป็นไปได้ที่จะขยายไปสู่อนาคตหรือไม่? ส่วนหนึ่งของฉันรู้สึกเหมือนในกรณีใด ๆ ที่สามารถใช้การย้อนรอยย้อนกลับสามารถจำลองเป็นต้นไม้ได้ แต่ฉันไม่แน่ใจว่าฉันถูกต้องตามสัญชาตญาณนั้นหรือไม่
The111

5
@ The111: อันที่จริงปัญหาการค้นหาใด ๆสามารถแสดงเป็นโครงสร้างได้ - คุณมีโหนดสำหรับโซลูชันบางส่วนที่เป็นไปได้แต่ละรายการและขอบจากแต่ละโหนดไปยังทางเลือกอื่นที่เป็นไปได้อย่างน้อยหนึ่งรายการที่สามารถทำได้ในสถานะนี้ ฉันคิดว่าคำตอบของ lcn การย้อนรอยนั้นมักจะหมายถึง DFS บนแผนผังการค้นหา (โดยปกติโดยปริยาย) ที่สร้างขึ้นระหว่างการเรียกซ้ำนั้นใกล้เคียงกับความจริงมากที่สุด
j_random_hacker

5
@j_random_hacker ดังนั้น DFS จึงเป็นวิธีสำรวจต้นไม้ (หรือสร้างกราฟโดยทั่วไป) ในขณะที่การย้อนรอยเป็นวิธีการแก้ปัญหา (ซึ่งใช้ DFS ร่วมกับการตัดแต่งกิ่ง) :-)
The111

วิกิพีเดียอธิบายการย้อนรอยย้อนหลังเป็นอัลกอริธึมการค้นหาเชิงลึกอันดับแรกอย่างน่าสับสนซึ่งเห็นได้ชัดว่าแนวคิดทั้งสองนี้รวมกัน
Anderson Green

30

ฉันคิดว่าคำตอบสำหรับคำถามอื่นที่เกี่ยวข้องนี้ให้ข้อมูลเชิงลึกมากกว่า

สำหรับฉันความแตกต่างระหว่างการย้อนรอยและ DFS คือการย้อนกลับจะจัดการกับทรีโดยนัยและ DFS เกี่ยวข้องกับสิ่งที่ชัดเจน สิ่งนี้ดูเหมือนเป็นเรื่องเล็กน้อย แต่มีความหมายมาก เมื่อพื้นที่การค้นหาของปัญหาถูกเยี่ยมชมโดยการย้อนรอยต้นไม้โดยนัยจะถูกลากผ่านและตัดตรงกลางของปัญหา แต่สำหรับ DFS ต้นไม้ / กราฟที่เกี่ยวข้องนั้นถูกสร้างขึ้นอย่างชัดเจนและกรณีที่ยอมรับไม่ได้ได้ถูกโยนทิ้งไปแล้วเช่นตัดออกก่อนที่จะทำการค้นหาใด ๆ

ดังนั้นการย้อนรอยจึงเป็น DFS สำหรับต้นไม้โดยนัยในขณะที่ DFS กำลังย้อนรอยโดยไม่ต้องตัด


ฉันคิดว่ามันสับสนที่จะคิดว่าการย้อนรอยเป็นการจัดการต้นไม้โดยปริยาย เมื่อฉันอ่านสิ่งนี้ครั้งแรกฉันเห็นด้วย แต่การขุดลึกลงไปฉันตระหนักว่า "ต้นไม้นัย" คือ .. ต้นไม้การเรียกซ้ำ .. ใช้ตัวอย่างใดก็ได้ที่ใช้การย้อนกลับเช่นการกำหนดสตริงของอักขระไม่มีต้นไม้ตรรกะ (ไม่มีต้นไม้โดยนัย) ไม่ว่าจะเกี่ยวกับปัญหา แต่เรามีแผนผังการเรียกซ้ำที่จำลองกระบวนการสร้างสตริงที่เพิ่มขึ้น สำหรับการตัดแต่งกิ่งนั่นคือการตัดแต่งกิ่งที่ทำซ้ำกับต้นไม้ที่มีการใช้กำลังเดรัจฉานทั้งหมด ... (มีต่อไป)
Gang Fang

(ต่อ) เช่นพิมพ์การเรียงสับเปลี่ยนสตริง "คำตอบ" ทั้งหมดและสมมติว่าอักขระตัวที่ 3 ต้องเป็นอักขระ "a" ต้นไม้การเรียกซ้ำ 2 ระดับแรกเป็นไปตาม O (n!) แต่ในระดับที่ 3 ทุกสาขายกเว้นสาขาที่ต่อท้าย "a" จะถูกตัดออก (ย้อนรอย)
Gang Fang

8

โดยปกติแล้วการย้อนรอยจะใช้เป็น DFS บวกกับการตัดการค้นหา คุณสำรวจแผนผังพื้นที่ค้นหาเชิงลึกก่อนสร้างโซลูชันบางส่วนไปพร้อมกัน Brute-force DFS สามารถสร้างผลลัพธ์การค้นหาทั้งหมดแม้แต่สิ่งที่ไม่สมเหตุสมผลในทางปฏิบัติ สิ่งนี้อาจไม่มีประสิทธิภาพในการสร้างโซลูชันทั้งหมด (n! หรือ 2 ^ n) ดังนั้นในความเป็นจริงในขณะที่คุณทำ DFS คุณจำเป็นต้องตัดวิธีแก้ปัญหาบางส่วนซึ่งไม่สมเหตุสมผลในบริบทของงานจริงและมุ่งเน้นไปที่โซลูชันบางส่วนซึ่งอาจนำไปสู่โซลูชันที่เหมาะสมที่สุด นี่เป็นเทคนิคการย้อนรอยจริง - คุณทิ้งโซลูชันบางส่วนโดยเร็วที่สุดย้อนกลับไปและพยายามหาค่าที่เหมาะสมในท้องถิ่นอีกครั้ง

ไม่มีอะไรหยุดที่จะสำรวจแผนผังพื้นที่การค้นหาโดยใช้ BFS และใช้กลยุทธ์การย้อนกลับไปพร้อมกัน แต่มันไม่สมเหตุสมผลในทางปฏิบัติเพราะคุณจะต้องจัดเก็บสถานะการค้นหาทีละเลเยอร์ในคิวและความกว้างของต้นไม้จะเพิ่มขึ้นเป็นทวีคูณตามความสูง เราจะเสียพื้นที่ไปมากอย่างรวดเร็ว นั่นเป็นเหตุผลว่าทำไมต้นไม้จึงมักถูกส่งผ่านโดยใช้ DFS ในกรณีนี้สถานะการค้นหาจะถูกเก็บไว้ในสแต็ก (เรียกสแต็กหรือโครงสร้างที่ชัดเจน) และต้องไม่เกินความสูงของต้นไม้


7

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


6

โดยปกติการค้นหาในเชิงลึกเป็นวิธีการวนซ้ำผ่านโครงสร้างกราฟ / โครงสร้างต้นไม้จริงเพื่อค้นหาค่าในขณะที่การย้อนรอยเป็นการวนซ้ำผ่านพื้นที่ปัญหาเพื่อค้นหาวิธีแก้ไข Backtracking เป็นอัลกอริทึมทั่วไปที่ไม่จำเป็นต้องเกี่ยวข้องกับต้นไม้ด้วยซ้ำ


6

ตามที่ Donald Knuth ก็เช่นเดียวกัน นี่คือลิงค์บนเอกสารของเขาเกี่ยวกับอัลกอริทึม Dancing Links ซึ่งใช้ในการแก้ปัญหา "ที่ไม่ใช่ต้นไม้" เช่น N-queens และตัวแก้ Sudoku

Backtracking เรียกอีกอย่างว่าการค้นหาในเชิงลึกก่อน


กล่าวถึงในหน้าที่ 1 ของ pdf ที่เชื่อมโยง
Steve Chavez

5

ฉันจะบอกว่า DFS เป็นรูปแบบพิเศษของการย้อนรอย backtracking เป็นรูปแบบทั่วไปของ DFS

ถ้าเราขยาย DFS ไปสู่ปัญหาทั่วไปเราสามารถเรียกมันว่า backtracking หากเราใช้การย้อนกลับไปยังปัญหาที่เกี่ยวข้องกับแผนภูมิ / กราฟเราสามารถเรียกมันว่า DFS

พวกเขามีแนวคิดเดียวกันในด้านอัลกอริทึม


ความสัมพันธ์ระหว่าง DFS และ Backtracking นั้นแท้จริงแล้วเป็นเพียงการสนทนาเท่านั้น ตรวจสอบคำตอบของฉันซึ่งมีรายละเอียดนี้
KGhatak

2

Depth first คืออัลกอริทึมสำหรับการสำรวจหรือค้นหาต้นไม้ ดูที่นี่ . Backtracking เป็นคำศัพท์ที่กว้างกว่ามากซึ่งใช้ไม่ว่าจะมีการสร้างผู้สมัครโซลูชันและถูกยกเลิกในภายหลังโดยการย้อนกลับไปยังสถานะเดิม ดูที่นี่ . การค้นหาครั้งแรกเชิงลึกจะใช้การย้อนกลับเพื่อค้นหาสาขาก่อน (ผู้สมัครโซลูชัน) และหากไม่ประสบความสำเร็จในการค้นหาสาขาอื่น


2

IMO บนโหนดเฉพาะใด ๆ ของ backtracking คุณพยายามเจาะลึกในขั้นแรกโดยแยกย่อยไปยังแต่ละลูกของมัน แต่ก่อนที่คุณจะแยกไปยังโหนดลูกใด ๆ คุณจะต้อง "ล้าง" สถานะของเด็กก่อนหน้านี้ (ขั้นตอนนี้เทียบเท่ากับ back เดินไปที่โหนดแม่) กล่าวอีกนัยหนึ่งรัฐพี่น้องแต่ละคนไม่ควรส่งผลกระทบต่อกันและกัน

ในทางตรงกันข้ามในระหว่างอัลกอริทึม DFS ปกติคุณมักไม่มีข้อ จำกัด นี้คุณไม่จำเป็นต้องล้างสถานะพี่น้องก่อนหน้า (back track) เพื่อสร้างโหนดพี่น้องถัดไป


2

DFS อธิบายวิธีที่คุณต้องการสำรวจหรือสำรวจกราฟ มุ่งเน้นไปที่แนวคิดของการลงลึกที่สุดเท่าที่จะเป็นไปได้ตามทางเลือก

Backtracking แม้ว่าโดยปกติจะใช้งานผ่าน DFS แต่จะเน้นไปที่แนวคิดของการตัดส่วนย่อยการค้นหาที่ไม่เป็นอันตรายให้เร็วที่สุด


1

ในการค้นหาเชิงลึกก่อนอื่นคุณเริ่มต้นที่รากของต้นไม้จากนั้นสำรวจไปตามแต่ละกิ่งก้านจากนั้นคุณจะย้อนกลับไปยังโหนดแม่ที่ตามมาแต่ละโหนดที่ตามมา

Backtrackingเป็นคำทั่วไปสำหรับการเริ่มต้นที่จุดสิ้นสุดของเป้าหมายและค่อยๆเคลื่อนไปข้างหลังค่อยๆสร้างวิธีแก้ปัญหา


5
การย้อนรอยไม่ได้หมายความว่าให้เริ่มต้นจากจุดสิ้นสุดและถอยหลัง จะเก็บบันทึกของโหนดที่เยี่ยมชมเพื่อย้อนกลับหากพบทางตัน
Günther Jena

1
"เริ่มที่จุดจบ ... ", ห๊ะ !!
7kemZmani

1

แนวคิด - เริ่มต้นจากจุดใดก็ได้ตรวจสอบว่าเป็นจุดสิ้นสุดที่ต้องการหรือไม่ถ้าใช่เราพบวิธีแก้ปัญหาอื่นไปยังตำแหน่งที่เป็นไปได้ถัดไปทั้งหมดและหากเราไม่สามารถไปต่อได้ให้กลับไปที่ตำแหน่งก่อนหน้าและมองหาทางเลือกอื่นที่ทำเครื่องหมายปัจจุบันนั้น เส้นทางจะไม่นำเราไปสู่การแก้ปัญหา

ตอนนี้ backtracking และ DFS เป็นชื่อที่แตกต่างกัน 2 ชื่อที่กำหนดให้กับแนวคิดเดียวกันซึ่งใช้กับข้อมูลนามธรรม 2 ประเภทที่แตกต่างกัน

หากแนวคิดถูกนำไปใช้กับโครงสร้างข้อมูลเมทริกซ์เราเรียกว่าการย้อนรอย

หากใช้แนวคิดเดียวกันกับต้นไม้หรือกราฟเราจะเรียกมันว่า DFS

ถ้อยคำที่เบื่อหูในที่นี้คือเมทริกซ์สามารถแปลงเป็นกราฟและกราฟสามารถเปลี่ยนเป็นเมทริกซ์ได้ ดังนั้นเราจึงใช้แนวคิดนี้ ถ้าบนกราฟเราเรียกมันว่า DFS และถ้าอยู่บนเมทริกซ์เราจะเรียกมันว่าการย้อนรอย

ความคิดในอัลกอริทึมทั้งสองเหมือนกัน


0

การย้อนรอยเป็นการค้นหาในเชิงลึกก่อนโดยมีเงื่อนไขการยุติที่เฉพาะเจาะจง

พิจารณาการเดินผ่านเขาวงกตที่ซึ่งในแต่ละขั้นตอนที่คุณตัดสินใจการตัดสินใจนั้นเป็นการเรียกร้องให้กลุ่มการโทร (ซึ่งดำเนินการค้นหาเชิงลึกของคุณก่อน) ... หากคุณไปถึงจุดสิ้นสุดคุณสามารถกลับเส้นทางของคุณได้ อย่างไรก็ตามหากคุณมาถึงทางตันคุณต้องการกลับออกจากการตัดสินใจบางอย่างโดยพื้นฐานแล้วคือการกลับออกจากฟังก์ชันในกลุ่มการโทร

ดังนั้นเมื่อฉันคิดถึงการย้อนรอยฉันจึงสนใจ

  1. สถานะ
  2. การตัดสินใจ
  3. กรณีฐาน (เงื่อนไขการยกเลิก)

ผมอธิบายในวิดีโอของฉันใน 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)
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.