ความแตกต่างระหว่าง Divide และ Conquer Algo และ Dynamic Programming


140

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

โปรดยกตัวอย่างง่ายๆเพื่ออธิบายความแตกต่างระหว่างสองสิ่งนี้กับสิ่งที่ดูเหมือนว่าคล้ายคลึงกัน

คำตอบ:


156

หารและพิชิต

แบ่งและพิชิตงานโดยการแบ่งปัญหาออกเป็นปัญหาย่อยพิชิตแต่ละปัญหาย่อยซ้ำและรวมโซลูชันเหล่านี้

การเขียนโปรแกรมแบบไดนามิก

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

คุณอาจคิดถึง DP = recursion + re-use

ตัวอย่างคลาสสิกเพื่อทำความเข้าใจความแตกต่างคือการเห็นทั้งสองวิธีเหล่านี้ไปสู่การได้รับหมายเลข fibonacci ที่ n ตรวจสอบวัสดุนี้จาก MIT


วิธีหารและพิชิต วิธีหารและพิชิต

แนวทางการเขียนโปรแกรมแบบไดนามิก ป้อนคำอธิบายรูปภาพที่นี่


9
คุณสร้างภาพได้อย่างไร? ใช้เมาส์ไหม
Vihaan Verma

34
ฉันคิดว่าบรรทัดที่สำคัญที่สุดในคำตอบทั้งหมดนี้คือ: "ปัญหาย่อยที่ทับซ้อนกัน" DP มีมันแบ่งและพิชิตไม่ได้
Hasan Iqbal

@HasanIqbalAnik ปัญหาย่อยที่ทับซ้อนกันหมายถึงปัญหาที่เกิดขึ้นซ้ำแล้วซ้ำอีก เช่นเดียวกับการแก้ fn-2 ในตัวอย่างที่แสดงด้านบน ดังนั้นใน D&C มันอยู่ที่นั่นและเหตุนี้มันจึงไม่มีประสิทธิภาพเท่ากับ DP
Meena Chaudhary

1
แปลก! 'การทับซ้อนของปัญหาย่อย' คุณกำลังพูดถึงปัญหา แต่ 'การเขียนโปรแกรมแบบไดนามิก' เป็นอัลกอริทึมชนิดหนึ่ง ฉันคิดว่ามันสำคัญที่จะต้องแยกแยะ 'ปัญหา' และ 'อัลกอริทึม'
ZHU

ใช่ DP จะบันทึกส่วนที่ทับซ้อนกันเพื่อให้ได้เปรียบเหนือ Divide and Conquer
imagineerThat

25

ความแตกต่างอื่น ๆ ระหว่างการหารและการพิชิตและการเขียนโปรแกรมแบบไดนามิกอาจเป็น:

หารและพิชิต:

  1. ทำงานกับปัญหาย่อยได้มากขึ้นและใช้เวลามากขึ้น
  2. ในการแบ่งและพิชิตปัญหาย่อยนั้นเป็นอิสระจากกัน

การเขียนโปรแกรมแบบไดนามิก:

  1. แก้ไขปัญหาย่อยเพียงครั้งเดียวแล้วเก็บไว้ในตาราง
  2. ในการเขียนโปรแกรมแบบไดนามิกปัญหาย่อยไม่ได้เป็นอิสระ

อัลกอริธึมการแบ่งแยกและการยึดครองไม่จำเป็นต้องทำงานมากกว่าทางเลือก DP ของพวกเขา ตัวอย่างหนึ่งคืออัลกอริทึมของ Erickson สำหรับการค้นหาความก้าวหน้าทางคณิตศาสตร์สูงสุด
Michael Foukarakis

17

บางครั้งเมื่อโปรแกรมซ้ำคุณเรียกใช้ฟังก์ชันด้วยพารามิเตอร์เดียวกันหลาย ๆ ครั้งซึ่งไม่จำเป็น

ตัวอย่างฟีโบนักชีที่มีชื่อเสียง:

           index: 1,2,3,4,5,6...
Fibonacci number: 1,1,2,3,5,8...

function F(n) {
    if (n < 3)
        return 1
    else
        return F(n-1) + F(n-2)
}

มารัน F (5):

F(5) = F(4) + F(3)
     = {F(3)+F(2)} + {F(2)+F(1)}
     = {[F(2)+F(1)]+1} + {1+1}
     = 1+1+1+1+1

ดังนั้นเราจึงเรียกว่า: 1 ครั้ง F (4) 2 ครั้ง F (3) 3 ครั้ง F (2) 2 ครั้ง F (1)

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

if (n==1 || n==2)
    return 1
else
    f1=1, f2=1
    for i=3 to n
         f = f1 + f2
         f1 = f2
         f2 = f

เรียก F (5) อีกครั้ง:

fibo1 = 1
fibo2 = 1 
fibo3 = (fibo1 + fibo2) = 1 + 1 = 2
fibo4 = (fibo2 + fibo3) = 1 + 2 = 3
fibo5 = (fibo3 + fibo4) = 2 + 3 = 5

อย่างที่คุณเห็นเมื่อใดก็ตามที่คุณต้องการการโทรหลายครั้งคุณเพียงแค่เข้าถึงตัวแปรที่เกี่ยวข้องเพื่อรับค่าแทนการคำนวณใหม่

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

// declare and initialize a dictionary
var dict = new Dictionary<int,int>();
for i=1 to n
    dict[i] = -1

function F(n) {
    if (n < 3)
        return 1
    else
    {
        if (dict[n] == -1)
            dict[n] = F(n-1) + F(n-2)

        return dict[n]                
    }
}

ดังนั้นความสัมพันธ์กับการหารและการพิชิตคืออัลกอริทึม D&D พึ่งพาการสอบถามซ้ำ และบางรุ่นก็มี "การเรียกใช้ฟังก์ชันหลายครั้งพร้อมปัญหาพารามิเตอร์เดียวกัน" ค้นหา "การคูณเมทริกซ์โซ่" และ "การเรียงลำดับทั่วไปที่ยาวที่สุด" สำหรับตัวอย่างที่ต้องการ DP เพื่อปรับปรุง T (n) ของ D&D algo


17

การเขียนโปรแกรมแบบไดนามิกและความคล้ายคลึงกันของการแบ่งและพิชิต

อย่างที่ฉันเห็นตอนนี้ฉันสามารถพูดได้ว่าการเขียนโปรแกรมแบบไดนามิกเป็นส่วนขยายของการแบ่งและพิชิตกระบวนทัศน์การเขียนโปรแกรมแบบไดนามิกเป็นส่วนขยายของแบ่งและพิชิตกระบวนทัศน์

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

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

ไปทีละขั้นตอน ...

ข้อกำหนดเบื้องต้น / ข้อ จำกัด ของการเขียนโปรแกรมแบบไดนามิก

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

  • โครงสร้างย่อยที่เหมาะสม  - ทางออกที่ดีที่สุดสามารถสร้างได้จากทางออกที่ดีที่สุดของปัญหาย่อย

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

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

ส่วนขยายการเขียนโปรแกรมแบบไดนามิกสำหรับการแบ่งและพิชิต

วิธีการเขียนโปรแกรมแบบไดนามิกขยายวิธีการแบ่งและพิชิตด้วยสองเทคนิค (การบันทึกและการจัดตาราง ) ที่ทั้งสองมีวัตถุประสงค์ในการจัดเก็บและการใช้โซลูชั่นย่อยปัญหาที่อาจปรับปรุงประสิทธิภาพอย่างมาก ตัวอย่างเช่นการใช้ฟังก์ชัน Fibonacci แบบเรียกซ้ำแบบเรียกซ้ำมีความซับซ้อนของเวลาO(2^n)ซึ่งโซลูชัน DP ทำเช่นเดียวกันกับเท่านั้นO(n)เวลาครั้ง

การบันทึก (การเติมแคชจากบนลงล่าง)หมายถึงเทคนิคการแคชและการนำผลลัพธ์ที่คำนวณมาก่อนหน้านี้มาใช้ใหม่ fibฟังก์ชั่นบันทึกความจำจึงมีลักษณะเช่นนี้:

memFib(n) {
    if (mem[n] is undefined)
        if (n < 2) result = n
        else result = memFib(n-2) + memFib(n-1)

        mem[n] = result
    return mem[n]
}

ตาราง (การเติมแคชจากล่างขึ้นบน)คล้ายกัน แต่เน้นที่การเติมรายการของแคช การคำนวณค่าในแคชทำได้ง่ายที่สุดซ้ำ ๆ เวอร์ชันการจัดระเบียบของfibจะมีลักษณะเช่นนี้:

tabFib(n) {
    mem[0] = 0
    mem[1] = 1
    for i = 2...n
        mem[i] = mem[i-2] + mem[i-1]
    return mem[n]
}

คุณสามารถอ่านเพิ่มเติมเกี่ยวกับ memoization และการเปรียบเทียบการจัดระเบียบที่นี่

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

ดังนั้นความแตกต่างระหว่าง DP และ DC เป็นอย่างไร

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

Dynamic Programming vs Divide-and-Conquer

หากคุณต้องการดูตัวอย่างโค้ดคุณสามารถดูคำอธิบายโดยละเอียดเพิ่มเติมได้ที่นี่ซึ่งคุณจะพบตัวอย่างอัลกอริทึมสองตัวอย่าง: การค้นหาแบบไบนารีและระยะทางแก้ไขขั้นต่ำ (ระยะทาง Levenshtein) ที่แสดงถึงความแตกต่างระหว่าง DP และ DC


1
Offtopic: คุณใช้แท็บเล็ตกราฟิกในการวาดหรือไม่
Geon George

1
@GeonGeorge no ภาพวาดทำด้วยปากกาจากนั้นสแกน
4259 Oleksii

นี่เป็นหนึ่งในคำตอบที่ดีที่สุดที่ฉันได้อ่านเกี่ยวกับการจัดการ DP
Ridhwaan Shakeel

8

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

การเขียนโปรแกรมแบบไดนามิก

แบ่งปัญหาออกเป็นปัญหาย่อยโดยสิ้นเชิง อัลกอริทึมแบบเรียกซ้ำสำหรับลำดับฟีโบนักชีเป็นตัวอย่างของการเขียนโปรแกรมแบบไดนามิกเพราะมันจะแก้หา Fib (n) โดยการแก้ปัญหาครั้งแรกสำหรับ Fib (n-1) เพื่อที่จะแก้ปัญหาเดิมก็แก้ที่แตกต่างกันปัญหา

หารและพิชิต

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


5

ฉันคิดว่าDivide & Conquerเป็นวิธีแบบเรียกซ้ำและDynamic Programmingเติมตาราง

ตัวอย่างเช่นMerge Sortเป็นDivide & Conquerอัลกอริธึมในแต่ละขั้นตอนคุณแบ่งอาร์เรย์ออกเป็นสองส่วนเรียกซ้ำสองส่วนซ้ำMerge Sortแล้วรวมเข้าด้วยกัน

Knapsackเป็นDynamic Programmingอัลกอริทึมในขณะที่คุณกำลังกรอกตารางแสดงโซลูชันที่ดีที่สุดสำหรับปัญหาย่อยของเป้โดยรวม แต่ละรายการในตารางสอดคล้องกับค่าสูงสุดที่คุณสามารถพกพาได้ในถุงน้ำหนักที่ได้รับรายการ 1-j


1
แม้ว่าสิ่งนี้จะเป็นจริงในหลายกรณี แต่ก็ไม่เป็นความจริงเสมอไปที่เราเก็บผลลัพธ์ของปัญหาย่อยไว้ในตาราง
Gokul

2

การหารและการพิชิตเกี่ยวข้องกับสามขั้นตอนในการเรียกซ้ำแต่ละระดับ:

  1. หารปัญหาออกเป็นปัญหาย่อย
  2. พิชิตปัญหาย่อยด้วยการแก้ปัญหาซ้ำ ๆ
  3. รวมโซลูชันสำหรับปัญหาย่อยเข้ากับโซลูชันสำหรับปัญหาดั้งเดิม
    • มันเป็นวิธีการจากบนลงล่าง
    • มันทำงานได้มากขึ้นในปัญหาย่อยและด้วยเหตุนี้จึงใช้เวลามากขึ้น
    • เช่น. คำที่ n ของ Fibonacci series สามารถคำนวณได้ในความซับซ้อนของเวลา O (2 ^ n)

การเขียนโปรแกรมแบบไดนามิกที่เกี่ยวข้องดังต่อไปนี้สี่ขั้นตอน:

1. สมบัติโครงสร้างของการแก้ปัญหาที่ดีที่สุด
2. กำหนดค่าของโซลูชันที่ดีที่สุดซ้ำ ๆ
3. คำนวณมูลค่าโซลูชันที่เหมาะสมที่สุด
4. สร้างทางออกที่ดีที่สุดจากข้อมูลคำนวณ

  • มันเป็นวิธีการจากล่างขึ้นบน
  • ใช้เวลาน้อยกว่าการหารและพิชิตเนื่องจากเราใช้ประโยชน์จากค่าที่คำนวณก่อนหน้านี้มากกว่าการคำนวณอีกครั้ง
  • เช่น. คำที่ n ของ Fibonacci series สามารถคำนวณได้ใน O (n) time complex

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

NBแบ่งและเอาชนะอัลกอริธึมที่มีปัญหาย่อยที่ทับซ้อนกันสามารถปรับให้เหมาะสมกับ dp เท่านั้น


การหารและการพิชิตนั้นเป็นแบบล่างขึ้นบนและการเขียนโปรแกรมแบบไดนามิกนั้นจากบนลงล่าง
Bahgat Mashaly

0
  • หารและพิชิต
    • พวกเขาพบปัญหาย่อยที่ไม่ทับซ้อนกัน
    • ตัวอย่าง: ตัวเลขแฟคทอเรียลเช่นข้อเท็จจริง (n) = n * ข้อเท็จจริง (n-1)
fact(5) = 5* fact(4) = 5 * (4 * fact(3))= 5 * 4 * (3 *fact(2))= 5 * 4 * 3 * 2 * (fact(1))

อย่างที่เราเห็นข้างต้นไม่มีการทำซ้ำความจริง (x) ดังนั้นแฟคทอเรียลจึงไม่มีปัญหาการซ้อนทับกัน

  • การเขียนโปรแกรมแบบไดนามิก
    • พวกเขาเข้าสู่ปัญหาย่อยที่ทับซ้อนกัน
    • ตัวอย่าง: หมายเลขฟีโบนักชีเช่น Fib (n) = fib (n-1) + fib (n-2)
fib(5) = fib(4) + fib(3) = (fib(3)+fib(2)) + (fib(2)+fib(1))

อย่างที่เราเห็นข้างต้น Fib (4) และ fib (3) ทั้งคู่ใช้ fib (2) ในทำนองเดียวกัน fib (x) จำนวนมากจะถูกทำซ้ำ นั่นเป็นสาเหตุที่ฟีโบนัชชีซ้อนทับปัญหาย่อย

  • จากการซ้ำซ้อนของปัญหาย่อยใน DP เราสามารถเก็บผลลัพธ์ดังกล่าวไว้ในตารางและบันทึกความพยายามในการคำนวณ สิ่งนี้เรียกว่าเป็นการบันทึก
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.