Home    Previous page กำหนดการพลวัต Dynamic Programming Next page
หลักการกำหนดการพลวัต Dynamic Programming
ปกติเราใช้หลักการพลวัต (Dynamic programming) ในการแก้ปัญหา Optimization โดยมีลักษณะคล้ายกับ Divide-and-conquer ที่แบ่งปัญหาออกเป็นส่วนย่อย ๆ แล้วนำผลลัพธ์ของปัญหาย่อยเหล่านั้นมาแก้ปัญหาใหญ่ แต่สำหรับปัญหาย่อยที่การคำนวณนั้นไม่ขึ้นแก่กัน เราจะใช้ หลักการแบ่งเพื่อเอาชนะช่วยได้ แต่ถ้าการแก้ปัญหาย่อยเหล่านั้นขึ้นต่อกัน เรามักใช้หลักการพลวัตในการแก้ปัญหา
ถ้าเราพิจารณาต้นไม้ของการแก้ปัญหา เราจะพบว่าการแก้ปัญหาแบบแบ่งเพื่อเอาชนะเป็นการแก้ปัญหาจากบนลงล่าง (Top-down approach) นั่นคือเราแก้ปัญหาขนาดใหญ่โดยการแบ่งย่อยเป็นปัญหาขนาดเล็ก ในขณะที่วิธีการพลวัตจะแก้ปัญหาจากล่างขึ้นบน (Bottom-up approach) ซึ่งโดยทั่วไปเราจะเริ่มแก้ปัญหาขนาดเล็กก่อนแล้วจึงมารวมกันจนกระทั่งได้คำตอบของปัญหาจริง
การเขียนขั้นตอนวิธีการแก้ปัญหาแบบกำหนดการพลวัต ส่วนใหญ่ใช้หลักการดังนี้
  1. กำหนดลักษณะของโครงสร้างของผลเฉลยที่เหมาะที่สุด
  2. นิยามความสัมพันธ์เวียนเกิดของผลเฉลยที่เหมาะที่สุด
  3. คำนวณค่าโดยใช้วิธีการจากล่างขึ้นบน (Bottom-up approach)
  4. สร้างคำตอบจากค่าที่คำนวณได้

การคำนวณค่า Binomial coefficient
เราทราบว่า เราสามารถคำนวณค่าสัมประสิทธิของการกระจายได้จากสูตร

= 1 ถ้า k = 0 หรือ k = n
= + ถ้า 0 < k < n
ในกรณีที่เราทราบว่า 0 k n เราคำนวณค่า ได้โดยใช้ขั้นตอนวิธีนี้
C(n, k)
ข้อมูลเข้า n เป็นจำนวนเต็มบวก และ k แทนจำนวนเต็มที่ไม่น้อยกว่าศูนย์และไม่มากกว่า n
ข้อมูลออก ผลลัพธ์ที่ได้จาก
1. if k == 0 or k == n then return 1
2. return C(n-1, k-1) + C(n-1, k)
เราพบว่าขั้นตอนวิธีที่เสนอมาข้างต้นจะมีการคำนวณค่าของ C(i, j) เมื่อ i < n, j < k ซ้ำๆ กันหลายครั้ง เช่น
ในการคำนวณ C(5, 3) = C(4, 2) + C(4, 3) ซึ่งจะต้องมีการคำนวณ C(3, 2) ทั้งสองกรณี และในทำนองเดียวกับค่า C(2, 2)
ซึ่งเราจะใช้เวลาในการคำนวณอย่างน้อยคือ ()
แต่ถ้าเราใช้วิธีการคิดแบบ Pascal's triangle เราสามารถคำนวณค่า C(n, k) ได้มีประสิทธิภาพดังตาราง
0 1 2 3 ... k - 1 k
0 1
1 1 1
2 1 2 1
3 1 3 3 1
:
n-1 C(n-1,k-1) C(n-1,k)
n C(n,k)
เราสามารถคำนวณ แบบวนซ้ำได้ดังนี้
IterativeC(n, k)
ข้อมูลเข้า n เป็นจำนวนเต็มบวก และ k แทนจำนวนเต็มที่ไม่น้อยกว่าศูนย์และไม่มากกว่า n
ข้อมูลออก ผลลัพธ์ที่ได้จาก
1. Allocate table C of size n+1 by k+1 and set the default to be zero
2. for j = 0 to n
3.    C(0, j) = 1;
4. endfor
5. for i = 1 to n
6.     m = min{i, k}
6.     for j = 1 to m
7.        C(i, j) = C(i-1, j-1) + C(i-1, j);
8.     endfor
9. endfor
10. return C(n, k)

การหาผู้ชนะในการแข่งทั้งรายการ World Series
พิจารณาการแข่งขันระหว่างสองทีม A และ B ซึ่งแข่งกันไม่เกิน 2n - 1 เกม โดยที่ผู้ชนะในการแข่งทั้งรายการคือทีมแรกที่ชนะ n เกมก่อน สมมติว่าเราไม่มีการเล่นเสมอ และผลการเล่นในแต่ละครั้งไม่ขึ้นกับครั้งใด ๆ และ สำหรับการแข่งในแต่ละครั้งมีความน่าจะเป็นที่ A จะชนะคงที่ คือ p ดังนั้นความน่าจะเป็นที่ B จะชนะ q = 1 - p
กำหนดให้ P(i, j) แทนความน่าจะเป็นที่ทีม A จะชนะในการแข่งทั้งรายการเมื่อทีม A ต้องการชัยชนะอีก i ครั้ง ในขณะที่ทีม B ต้องการอีก j ครั้ง
ตัวอย่างเช่น ก่อนการแข่งขันเราได้ความน่าจะเป็นที่ทีม A จะชนะการแข่งทั้งรายการเท่ากับ P(n, n) หรือกล่าวอีกนัยหนึ่งคือทั้งสองทีมต้องการอีก n เกมเพื่อชัยชนะทั้งรายการ

ในกรณีที่ทีม A ไม่ต้องการชนะอีกแล้วคือ P(0, i) เราได้ว่าทีม A ชนะทั้งรายการเรียบร้อยแล้ว นั่นคือ P(0, i) = 1
ในทำนองเดียวกัน ถ้าทีม B ไม่ต้องการชนะอีกแล้วคือ P(i, 0) เราถือว่าทีม A แพ้ในการแข่งทั้งรายการ นั่นคือ P(i, 0) = 0
และเราทราบว่าทีม A จะชนะในการแข่งด้วยความน่าจะเป็น p และแพ้ด้วยความน่าจะเป็น q ดังนั้น
P(i, j) = p P(i-1, j) + q P(i, j-1), i 1, j 1,
เราสามารถเขียนขั้นตอนวิธีในการคำนวณค่า P(i, j) ได้ดังนี้
P(i, j)
ข้อมูลเข้า i เป็นจำนวนครั้งที่ทีม A ต้องการเอาชนะ และ j แทนจำนวนครั้งที่ทีม B ต้องการเอาชนะ เพื่อให้ชนะการแข่งทั้งรายการ
ข้อมูลออก ค่าของ P(i, j)
1. if i == 0 then return 1
2. if j == 0 then return 0
3. return P(i-1, j) + P(i, j-1)
กำหนดให้ T(k) แทนเวลาที่แย่ที่สุดในการคำนวณ P(i, j) เมื่อ k = i + j เราได้ว่า
T(1) = c
T(k) 2 T(k - 1) + d, k > 1
เราได้ว่า T(k) 2k(c/2 + d/2) - d
ปัญหาลักษณะนี้จะคล้ายกับการคำนวณค่า ในการคำนวณให้เร็วขึ้น เราสามารถใช้หลักการเดียวกับ Binomial coefficient
series(n , p)
ข้อมูลเข้า n เป็นจำนวนครั้งที่แต่ละทีมต้องเอาชนะเพื่อชัยชนะของการแข่งขัน และ p แทนความน่าจะเป็นที่ทีม A จะชนะ
ข้อมูลออก ค่าของ P(n, n)
1. q = 1 - p
2. for s = 1 to n do
3.    P(0, s) = 1; P(s, 0) = 0
4.    for k = 1 to s - 1 do
5.        p(k, s-k) = pP(k-1, s-k) + q P(k, s-k-1)
6.    endfor
7. endfor
8. for s = 1 to n do
9.    for k = 0 to n-s do
10.        P(s+k, n-k) = p*P(s+k-1, n-k) + q*P(s+k, n-k-1)
11.    endfor
12. endfor
13. return P(n, n)

ปัญหาการทอนเหรียญ Making Change (2)
จากปัญหาการทอนเหรียญที่น้อยที่สุดซึ่ง เราได้พิจารณาการแก้มาแล้วโดยใช้ Greedy algorithm เราพบว่าถ้าเรามีขนาดเหรียญ บางประเภท เราไม่สามารถแก้ปัญหานี้โดยใช้ Greedy algorithm ได้ ตัวอย่างเช่น ถ้าเรามีเหรียญ 1, 4 และ 6 หน่วย การทอนเหรียญให้ผลรวมเป็น 8 หน่วย โดยใช้ Greedy algorithm เราจะได้ว่าเราใช้ 3 เหรียญคือ 1 × 6 + 2 × 1 แต่ถ้าเราทอนเหรียญ 2 × 4 เราพบว่าเราใช้เพียงสองเหรียญเท่านั้น
เราแก้ปัญหานี้โดยใช้ Dynamic programming โดยทำการสร้างตารางการทอนของค่าที่ เพิ่มขึ้นเรื่อย ๆ จากค่าในตารางที่ได้คำนวณมาแล้ว

กำหนดให้ i แทนค่าของเหรียญ 1 แบบที่สามารถใช้ทอนได้ (แทนแถวที่ i) เรียก di ซึ่งมากกว่า 0
และ
กำหนดให้ N แทนค่าที่ต้องทอน
เราจะสร้างตารางของ c(1..n, 0..N) โดยที่ c(i, j) หมายถึง จำนวนเหรียญที่ทอนที่น้อยที่สุดสำหรับค่า j, 1 j N โดยใช้จำนวนเหรียญ 1, 2, ..., i คำตอบที่ต้องการคือค่าของ c(n, N)
ในการเติมค่าในตาราง เราเริ่มจาก c(i, 0) = 0 ทุก i = 1, 2, ..., n เพราะเราไม่มีความจำเป็นต้องทอนเหรียญใด ๆ
ในการทอนเหรียญ เราอาจไม่จำเป็นต้องทอนเหรียญที่มีค่า di เลย นั่นคือ c(i, j) = c(i-1, j) หรือ เราอาจเลือกทอนหนึ่งเหรียญของ di ทำให้ได้ว่า c(i, j) = 1 + c(i, j - di) เนื่องจากเราต้องการหาจำนวนเหรียญที่น้อยที่สุด
c(i, j) = min{c(i-1, j), 1 + c(i, j - di)}
สำหรับในกรณีที่ i < 1 หรือ j < di เราพบว่าค่าหนึ่งในที่เลือกจะออกนอกตาราง เราจะกำหนดให้ค่านั้นเป็น + เพื่อไม่ให้เกิดการเลือกใช้
ในตารางที่ให้เป็นการแก้ปัญหาการทอนเหรียญ สามประเภทคือ 1, 4, 6 โดยจำนวนที่ต้องทอนคือ 8
เหรียญ\ค่าที่ต้องทอน   0     1     2     3     4     5     6     7     8  
d1 = 1   0     1     2     3     4     5     6     7     8  
d2 = 4   0     1     2     3     1     2     3     4     2  
d3 = 6   0     1     2     3     1     2     1     2     2  
โดยใช้ขั้นตอนวิธีดังนี้
coins(N)
ข้อมูลเข้า N เป็นจำนวนเต็มบวกหรือศูนย์
ข้อมูลออก จำนวนเหรียญที่ต้องทอนที่น้อยที่สุด
1. d(1, 2, 3) = (1, 4, 6);
2. for i = 1 to n do
3.     c(i, 0) = 0
4. endfor
5. for i = 1 to n do
6.    for j = 1 to N do
7.        if i == 1 and j < d(i) then
8.             c(i, j) = +;
9.         else if i == 1 then
10.           c(i, j) = 1 + c(1, j - d(1));
11.        else if j < d(i) then
12.          c(i, j) = c(i-1, j)
13.        else 
14.          c(i, j) = min{c(i-1, j), 1+c(i, j-d(i))}
15.        endif
16.    endfor
17. endfor
18. return c(n, N)

การคูณต่อกันของเมทริกซ์ Matrix-chain multiplication
กำหนดชุดของเมทริกซ์ < A1, A2, A3, ..., An > ซึ่งต้องการหาคำตอบ A1 × A2 × ... × An
เรากล่าวว่า ชุดของเมทริกซ์มีผลคูณของเมทริกซ์ซึ่ง fully parenthesized ถ้า ชุดของเมทริกซ์นั้นมีหนึ่งเมทริกซ์ หรือ ชุดของเมทริกซ์นั้นประกอบด้วยผลคูณของ fully parenthesized เมทริกซ์ชุดย่อย ๆ โดยมีวงเล็บคล่อมไว้
ตัวอย่างเช่น ชุดของเมทริกซ์ < A1, A2, A3, A4 > จะมีผลคูณซึ่ง fully parenthesized อยู่ 5 แบบคือ

(A1 × (A2 × (A3 × A4))),    (A1 × ((A2 × A3) × A4)),    ((A1 × A2) × (A3 × A4)),    ((A1 × (A2 × A3)) × A4),    (((A1 × A2) × A3) × A4)
ในการคูณกันของเมทริกซ์เราจะพบว่าผลคูณของจำนวน scalar ในเมทริกซ์ และขนาดของเมทริกซ์มีผลต่อการวิเคราะห์ running time
MATRIX-MULTIPLY(A, B)
ข้อมูลเข้า A, B เป็นเมทริกซ์ขนาด p × q และ q × r ตามลำดับ
ข้อมูลออก C เป็นเมทริกซ์ผลคูณของ A × B ซึ่งมีขนาด p × r
1. if columns[A] != rows[B] then return error "incompatible multiplication"
2. for i = 1 to rows[A]
3.     for j = 1 to columns[B] do
4.         C[i, j] = 0
5.        for k = 1 to columns[A]
6.             C[i, j] = C[i, j] + A[i, k] * B[k, j]
7.        endfor
8.     endfor
9. endfor
10. return C
จำนวนการคูณกันของจำนวนตัวเลขสองจำนวน จะเป็นตัวกำหนด running time analysis ของการคูณเมทริกซ์
พิจารณาตัวอย่าง A1, A2, A3 มีขนาด 10 × 100, 100 × 5 และ 5 × 50 ตามลำดับ ถ้าเราให้การคูณคือ ((A1 × A2) × A3) เราพบว่าเราต้องคูณกัน 10 × 100 × 5 = 5000 ในการคำนวณ A1 × A2 และอีก 10 × 5 × 50 = 2500 ในการคูณต่อด้วย A3 ผลคูณสุดท้ายรวมทั้งหมดเป็น 7500
แต่ถ้าเราคำนวณผลคูณดังนี้ (A1 × (A2 × A3)) เราจะได้ผลรวมของการคูณคือ 100 × 5 × 50 + 10 × 100 × 50 = 25000 + 50000 = 75000 ซึ่งสูงกว่าการคูณแบบแรกถึง 10 เท่า

ปัญหาการคูณต่อกันของเมทริกซ์ Matrix-chain multiplication problem
กำหนดชุดของเมทริกซ์ < A1, A2, A3, ..., An > ซึ่งมีมิติ Ai = p[i-1] × p[i] เราต้องการหาการจัดชุดของเมทริกซ์แบบ fully parenthesized ที่มีจำนวนการคูณของ scalar ที่น้อยที่สุด
ถ้าเราคำนวณหาความเป็นไปได้ทั้งหมดในการใส่วงเล็บ เราจะได้ขั้นตอนวิธีที่มี running time เป็น O(2n)
กำหนดให้ T(n) เป็นจำนวนการใส่วงเล็บทั้งหมดที่เป็นไปได้ (โดยแต่ละแบบเกิดการคำนวณที่ต่างกัน) เราสามารถเขียนสมการเวียนเกิดของปัญหานี้ได้ดังนี้
T(1) = 1, T(2) = 1, T(n) = T(q) T(n - q) สำหรับ n > 1
คำตอบของปัญหานี้คือลำดับที่เรียกว่า Catalan numbers ซึ่งมีคำตอบคือ T(n) = / n

การแก้ปัญหา Matrix-chain multiplication problem
เราแบ่งเป็นขั้น ๆ ได้ดังนี้

  1. กำหนด A(i..j) หมายถึงการคูณของ Ai × Ai+1 × ... × Aj ดังนั้น A(1..n) สามารถคำนวณได้จากปัญหาย่อย ๆ คือ A(1..k) กับ A(k..n) ซึ่งเราพบว่าผลเฉลยที่เหมาะที่สุดจะเกิดจากการแก้ปัญหาย่อยเหล่านี้ โดยให้มีการคูณกันที่น้อยที่สุดด้วย
  2. เรานิยามความสัมพันธ์เวียนเกิดโดยให้ m[i,j] เป็นจำนวนผลคูณของ scalar product ที่น้อยที่สุดสำหรับการคำนวณ A(i..j) จะได้
    m[i, i] = 0 และ m[i, j] = min{m[i, k] + m[k+1, j] + p(i-1) p(k) p(j) สำหรับ i < j }
    เรากำหนด s[i, j] คือดัชนีที่ใช้ในการแยกผลคูณ A(i...k) × A(k+1...j) เพื่อใช้ในการกำหนดตำแหน่งการใส่วงเล็บ
  3. การแก้สมการเวียนเกิดนี้สามารถใช้หลักการหรือสูตรซึ่งได้ศึกษามาแล้ว ในทางปฏิบัติแล้วเราสามารถเขียนขั้นตอนวิธีซึ่งสามารถหาคำตอบได้ในลักษณะของ polynomial ดังนี้
    MATRIX-CHAIN-ORDER(p)
    ข้อมูลเข้า p เป็นเวกเตอร์ของขนาดของเมทริกซ์
    ข้อมูลออก m เป็นเมทริกซ์คำตอบของการคำนวณที่ใช้ผลคูณของ scalar ที่น้อยที่สุด และ s เป็นเมทริกซ์ของคำตอบในการคูณ
    1. n = length[p] - 1
    2. for i = 1 to n
    3.      m[i, i] = 0
    4. endfor
    5. for a = 2 to n do
    6.    for i = 1 to n-a + 1
    7.         j = i+a - 1
    8.         m[i, j] =  Infinity
    9.         for k = i to j - 1
    10.               q = m[i, k] + m[k+1, j] + p[i-1] p[k] p[j]
    11.             if q < m[i, j] then
    12.                     (m[i, j], s[i, j]) = (q, k)
    13.             endif
    14.        endfor
    15.     endfor
    16. endfor
    17. return m and s
    
    ตัวอย่างเช่นกำหนด
    MatrixDimension
    A130 × 35
    A235 × 15
    A315 × 5
    A4 5 × 10
    A510 × 20
    A620 × 25
    เราสามารถสร้างตารางคำตอบได้ดังนี้


    นั่นคือคำตอบของ m[1, 6] คือ 15125 และคำตอบของ m[2,5] คือค่าต่ำสุดของชุดตัวเลขดังต่อไปนี้
    m[2, 2] + m[3, 5] + p[1]p[2]p[5] = 13000
    m[2, 3] + m[4, 5] + p[1]p[3]p[5] = 7125
    m[2, 4] + m[5, 5] + p[1]p[4]p[5] = 11375
  4. เราจะพบว่าคำตอบที่ได้ เพียงกำหนดลำดับการคูณกันของเมทริกซ์ ซึ่งถ้าเราต้องการคำนวณการคูณเราสามารถเขียนขั้นตอนวิธีดังนี้
    MATRIX-CHAIN-MULTIPLY(A, s, i, j)
    ข้อมูลเข้า A ชุดของเมทริกซ์ A1, ..., An, s เป็นเมทริกซ์คำตอบของ MATRIX-CHAIN-ORDER(p), i เป็นดัชนีเริ่มต้นและ j เป็นดัชนีสุดท้าย
    ข้อมูลออก B เป็นเมทริกซ์คำตอบของการคูณ Ai × ... × Aj
    1. if j > i then
    2.     X = MATRIX-CHAIN-MULTIPLY(A, s, i, s[i, j])
    3.     Y = MATRIX-CHAIN-MULTIPLY(A, s, s[i, j]+1, j)
    4.     return MATRIX-MULTIPLY(X, Y)
    5. endif
    6. return Ai
    

Home | Previous | Next


© Copyright by กรุง สินอภิรมย์สราญ