Phân tích các chương trình đệ quy
Phương trình đệ quy là một phương trình biểu diễn mối liên hệ giữa T(n) và T(k), trong đó T(n) là thời gian thực hiện chương trình với kích thước dữ liệu nhập là n, T(k) thời gian thực hiện chương trình với kích thước dữ liệu nhập là k, với k ...
Phương trình đệ quy là một phương trình biểu diễn mối liên hệ giữa T(n) và T(k), trong đó T(n) là thời gian thực hiện chương trình với kích thước dữ liệu nhập là n, T(k) thời gian thực hiện chương trình với kích thước dữ liệu nhập là k, với k < n. Ðể thành lập được phương trình đệ quy, ta phải căn cứ vào chương trình đệ quy.
Thông thường một chương trình đệ quy để giải bài toán kích thước n, phải có ít nhất một trường hợp dừng ứng với một n cụ thể và lời gọi đệ quy để giải bài toán kích thước k (k<n).
Để thành lập phương trình đệ quy, ta gọi T(n) là thời gian để giải bài toán kích thước n, ta có T(k) là thời gian để giải bài toán kích thước k. Khi đệ quy dừng, ta phải xem xét khi đó chương trình làm gì và tốn hết bao nhiêu thời gian, chẳng hạn thời gian này là c(n). Khi đệ quy chưa dừng thì phải xét xem có bao nhiêu lời gọi đệ quy với kích thước k ta sẽ có bấy nhiêu T(k). Ngoài ra ta còn phải xem xét đến thời gian để phân chia bài toán và tổng hợp các lời giải, chẳng hạn thời gian này là d(n).
Dạng tổng quát của một phương trình đệ quy sẽ là:
T(n) = C(n)F(T(k))+d(n){ size 12{alignl { stack { left lbrace C ( n ) " " {} # right none left lbrace F ( T ( k ) ) +d ( n ) {} # right no } } lbrace } {}
Trong đó C(n) là thời gian thực hiện chương trình ứng với trường hợp đệ quy dừng. F(T(k)) là một đa thức của các T(k). d(n) là thời gian để phân chia bài toán và tổng hợp các kết quả.
Ví dụ 1-10: Xét hàm tính giai thừa viết bằng giải thuật đệ quy như sau:
FUNCTION Giai_thua(n:Integer): Integer;
BEGIN
IF n=0 then Giai_thua :=1
ELSE Giai_thua := n* Giai_thua(n-1);
END;
Gọi T(n) là thời gian thực hiện việc tính n giai thừa, thì T(n-1) là thời gian thực hiện việc tính n-1 giai thừa. Trong trường hợp n = 0 thì chương trình chỉ thực hiện một lệnh gán Giai_thua:=1, nên tốn O(1), do đó ta có T(0) = C1. Trong trường hợp n>0 chương trình phải gọi đệ quy Giai_thua(n-1), việc gọi đệ quy này tốn T(n-1), sau khi có kết quả của việc gọi đệ quy, chương trình phải nhân kết quả đó với n và gán cho Giai_thua. Thời gian để thực hiện phép nhân và phép gán là một hằng C2. Vậy ta có
T(n) = C1 nêun=0T(n-1)+C2 nêun>0{ size 12{alignl { stack { left lbrace C rSub { size 8{1} } " nêu"~"n=0" {} # right none left lbrace T ( "n-1" ) +C rSub { size 8{2} } " nêu"~n>0 {} # right no } } lbrace } {}
Ðây là phương trình đệ quy để tính thời gian thực hiện của chương trình đệ quy Giai_thua.
Ví du 1-11: Chúng ta xét thủ tục MergeSort một cách phác thảo như sau:
FUNCTION MergeSort (L:List; n:Integer):List;
VAR L1,L2:List;
BEGIN
IF n=1 THEN RETURN(L)
ELSE BEGIN
Chia đôi L thành L1 và L2, với độ dài n/2;
RETURN(Merge(MergeSort(L1,n/2),MergeSort(L2,n/2)));
END;
END;
Chẳng hạn để sắp xếp danh sách L gồm 8 phần tử 7, 4, 8, 9, 3, 1, 6, 2 ta có mô hình minh họa của MergeSort như sau:
Hàm MergeSort nhận một danh sách có độ dài n và trả về một danh sách đã được sắp xếp. Thủ tục Merge nhận hai danh sách đã được sắp L1 và L2 mỗi danh sách có độ dài n2 size 12{ { {n} over {2} } } {}, trộn chúng lại với nhau để được một danh sách gồm n phần tử có thứ tự. Giải thuật chi tiết của Merge ta sẽ bàn sau, chúng ta chỉ để ý rằng thời gian để Merge các danh sách có độ dài n2 size 12{ { {n} over {2} } } {} là O(n).
Gọi T(n) là thời gian thực hiện MergeSort một danh sách n phần tử thì T( n2 size 12{ { {n} over {2} } } {}) là thời gian thực hiện MergeSort một danh sách n2 size 12{ { {n} over {2} } } {} phần tử.
Khi L có độ dài 1 (n = 1) thì chương trình chỉ làm một việc duy nhất là return(L), việc này tốn O(1) = C1 thời gian. Trong trường hợp n > 1, chương trình phải thực hiện gọi đệ quy MergeSort hai lần cho L1 và L2 với độ dài n2 size 12{ { {n} over {2} } } {} do đó thời gian để gọi hai lần đệ quy này là 2T( n2 size 12{ { {n} over {2} } } {}). Ngoài ra còn phải tốn thời gian cho việc chia danh sách L thành hai nửa bằng nhau và trộn hai danh sách kết quả (Merge). Người ta xác đinh được thời gian để chia danh sách và Merge là O(n) = C2n . Vậy ta có phương trình đệ quy như sau:
T(n) = {C1 nêu n =12T(n2)+ C2 n nêu n > 1 size 12{ left lbrace matrix { C rSub { size 8{1} } " nêu n "=1 {} ## "2T" ( { {n} over {2} } ) +" C" rSub { size 8{2} } " n nêu n ">" 1" } right none } {}
Có ba phương pháp giải phương trình đệ quy:
1.- Phương pháp truy hồi
2.- Phương pháp đoán nghiệm.
3.- Lời giải tổng quát của một lớp các phương trình đệ quy.
Phương pháp truy hồi
Dùng đệ quy để thay thế bất kỳ T(m) với m < n vào phía phải của phương trình cho đến khi tất cả T(m) với m > 1 được thay thế bởi biểu thức của các T(1) hoặc T(0). Vì T(1) và T(0) luôn là hằng số nên chúng ta có công thức của T(n) chứa các số hạng chỉ liên quan đến n và các hằng số. Từ công thức đó ta suy ra T(n).
Ví dụ 1-12: Giải phương trình T(n) = C1 nêun=0T(n-1)+C2 nêun>0{ size 12{alignl { stack { left lbrace C rSub { size 8{1} } " nêu"~"n=0" {} # right none left lbrace T ( "n-1" ) +C rSub { size 8{2} } " nêu"~n>0 {} # right no } } lbrace } {}
Ta có T(n) = T(n-1) + C2
T(n) = [T(n-2) + C2] + C2 = T(n-2) + 2C2
T(n) = [T(n-3) + C2] + 2C2 = T(n-3) + 3C2
……
T(n) = T(n-i) + iC2
Quá trình trên kết thúc khi n - i = 0 hay i = n. Khi đó ta có
T(n) = T(0) + nC2 = C1 + n C2 = O(n)
Ví dụ 1-13: Giải phương trình T(n) = {C1 nêu n =12T(n2)+ C2 n nêu n > 1 size 12{ left lbrace matrix { C rSub { size 8{1} } " nêu n "=1 {} ## "2T" ( { {n} over {2} } ) +" C" rSub { size 8{2} } " n nêu n ">" 1" } right none } {}
Ta có T(n)=2T(n2)+2C2n size 12{T ( n ) "=2T" ( { {n} over {2} } ) "+2C" rSub { size 8{2} } n} {}
T ( n ) = 2 [ 2T ( n 4 ) + C 2 n 2 ] + C 2 n = 4T ( n 4 ) + 2C 2 n size 12{T ( n ) ="2 " [ " 2T" ( { {n} over {4} } ) +" C" rSub { size 8{2} } { {n} over {2} } ] +C rSub { size 8{2} } "n "=" 4T" ( { {n} over {4} } ) +"2C" rSub { size 8{2} } n} {}
……….
Quá trình suy rộng sẽ kết thúc khi n2i size 12{ { {n} over {2 rSup { size 8{i} } } } } {} = 1 hay 2i = n và do đó i = logn. Khi đó ta có:
T(n) = nT(1) + lognC2n = C1n + C2nlogn = O(nlogn).
Phương pháp đoán nghiệm
Ta đoán một nghiệm f(n) và dùng chứng minh quy nạp để chứng tỏ rằng T(n) ≤ f(n) với mọi n.
Thông thường f(n) là một trong các hàm quen thuộc như logn, n, nlogn, n2 size 12{n rSup { size 8{2} } } {}, n3 size 12{n rSup { size 8{3} } } {}, 2n size 12{2 rSup { size 8{n} } } {}, n!,nn size 12{n rSup { size 8{n} } } {}.
Ðôi khi chúng ta chỉ đoán dạng của f(n) trong đó có một vài tham số chưa xác định (chẳng hạn f(n) = an2 với a chưa xác định) và trong quá trình chứng minh quy nạp ta sẽ suy diễn ra giá trị thích hợp của các tham số.
Ví dụ 1-12: Giải phương trình đệ quy T(n) = {C1 nêu n =12T(n2)+ C2 n nêu n > 1 size 12{ left lbrace matrix { C rSub { size 8{1} } " nêu n "=1 {} ## "2T" ( { {n} over {2} } ) +" C" rSub { size 8{2} } " n nêu n ">" 1" } right none } {}
Giả sử chúng ta đoán f(n) = anlogn. Với n = 1 ta thấy rằng cách đoán như vậy không được bởi vì anlogn có giá trị 0 không phụ thuộc vào giá trị của a. Vì thế ta thử tiếp theo f(n) = anlogn + b.
Với n = 1 ta có, T(1) = C1 và f(1) = b, muốn T(1) ≤ f(1) thì b ≥ C1 (*)
Giả sử rằng T(k) ≤ f(k), tức là T(k) ≤ aklogk + b với mọi k < n (giả thiết quy nạp). Ta phải chứng minh T(n) ≤ anlogn + b với mọi n.
Giả sử n ≥ 2, từ phương trình đã cho ta có T(n) = 2T( n2 size 12{ { {n} over {2} } } {}) + C2n
Áp dụng giả thiết quy nạp với k = n2 size 12{ { {n} over {2} } } {} < n ta có:
T(n) = 2T( n2 size 12{ { {n} over {2} } } {}) + C2n ≤ 2[a n2 size 12{ { {n} over {2} } } {}log n2 size 12{ { {n} over {2} } } {} + b] + C2n
T(n) ≤ (anlogn - an + 2b) + C2n
T(n) ≤ (anlogn + b) + [b + (C2 - a)n] . Nếu lấy a ≥ C2 + b (**) ta được
T(n) ≤ (anlogn + b) + [b +(C2 - C2 - b )n ]
T(n) ≤ (anlogn + b) + (1-n) b
T(n) ≤ anlogn + b = f(n). (do b>0 và 1-n<0)
Nếu ta lấy a và b sao cho cả (*) và (**) đều thoả mãn thì T(n) ≤ an logn + b với mọi n.
{b≥C1a≥C2+b size 12{ left lbrace matrix { b >= C rSub { size 8{1} } {} ## a >= C rSub { size 8{2} } +b } right none } {}Ta phải giải hệ Ðể đơn giản, ta giải hệ {b=C1a=C2+b size 12{ left lbrace matrix { "b=C" rSub { size 8{1} } {} ## "a=C" rSub { size 8{2} } "+b" } right none } {}
Dễ dàng ta có b = C1 và a = C1 +C2 ta được T(n) ≤ (C1 + C2)nlogn +C1 với mọi n.
Hay nói cách khác T(n) là O(nlogn).
Lời giải tổng quát cho một lớp các phương trình đệ quy
Khi thiết kế các giải thuật, người ta thường vận dụng phương pháp chia để trị mà ta sẽ bàn chi tiết hơn trong chương 3. Ở đây chi trình bày tóm tắt phương pháp như sau:
Ðể giải một bài toán kích thước n, ta chia bài toán đã cho thành a bài toán con, mỗi bài toán con có kích thước nb size 12{ { {n} over {b} } } {}. Giải các bài toán con này và tổng hợp kết quả lại để được kết quả của bài toán đã cho. Với các bài toán con chúng ta cũng sẽ áp dụng phương pháp đó để tiếp tục chia nhỏ ra nữa cho đến các bài toán con kích thước 1. Kĩ thuật này sẽ dẫn chúng ta đến một giải thuật đệ quy.
Giả thiết rằng mỗi bài toán con kích thước 1 lấy một đơn vị thời gian và thời gian để chia bài toán kích thước n thành các bài toán con kích thước nb size 12{ { {n} over {b} } } {} và tổng hợp kết quả từ các bài toán con để được lời giải của bài toán ban đầu là d(n). (Chẳng hạn đối với ví dụ MergeSort, chúng ta có a = b = 2, và d(n) = C2n. Xem C1 là một đơn vị).
Tất cả các giải thuật đệ quy như trên đều có thể thành lập một phương trinh đệ quy tổng quát, chung cho lớp các bài toán ấy.
Nếu gọi T(n) là thời gian để giải bài toán kích thước n thì T( nb size 12{ { {n} over {b} } } {}) là thời gian để giải bài toán con kích thước nb size 12{ { {n} over {b} } } {}. Khi n = 1 theo giả thiết trên thì thời gian giải bài toán kích thước 1 là 1 đơn vị, tức là T(1) = 1. Khi n lớn hơn 1, ta phải giải đệ quy a bài toán con kích thước nb size 12{ { {n} over {b} } } {}, mỗi bài toán con tốn T( nb size 12{ { {n} over {b} } } {}) nên thời gian cho a lời giải đệ quy này là aT( nb size 12{ { {n} over {b} } } {}). Ngoài ra ta còn phải tốn thời gian để phân chia bài toán và tổng hợp các kết quả, thời gian này theo giả thiết trên là d(n).
Vậy ta có phương trình đệ quy:
T(n) = {C1 nêu n =12T(n2)+ C2 n nêu n > 1 size 12{ left lbrace matrix { C rSub { size 8{1} } " nêu n "=1 {} ## "2T" ( { {n} over {2} } ) +" C" rSub { size 8{2} } " n nêu n ">" 1" } right none } {}
Ta sử dụng phương pháp truy hồi để giải phương trình này. Khi n > 1 ta có
T(n) = aT( nb size 12{ { {n} over {b} } } {}) + d(n)
T(n)= a[aT(nb2) + d(nb)]+d(n)=a2T(nb2)+ad(nb)+d(n) size 12{a [ "aT" ( left ( { {n} over {b rSup { size 8{2} } } } ) " + d" ( { {n} over {b} } ) right ) ] "+d" ( n ) "=a" rSup { size 8{2} } T ( left ( { {n} over {b rSup { size 8{2} } } } right ) ) "+ad" ( left ( { {n} over {b} } ) right )"+d" ( n ) } {}
T(n)= a2[a T(nb3)+d(nb2)]+ad(nb)+d(n)=a3T(nb3)+a2d(nb2)+ad(nb)+d(n) size 12{a rSup { size 8{2} } [ "a T" ( { {n} over {b rSup { size 8{3} } } } ) +d left ( ( { {n} over {b rSup { size 8{2} } } } ) right ) ] +"ad" left ( ( { {n} over {b} } ) right )+d ( n ) =a rSup { size 8{3} } T left ( ( { {n} over {b rSup { size 8{3} } } } ) right )+a rSup { size 8{2} } d left ( ( { {n} over {b rSup { size 8{2} } } } ) right )+"ad" left ( ( { {n} over {b} } ) right )+d ( n ) } {} = ........
= aiT(nbi)+∑j=0i-1ajd(abj) size 12{a rSup { size 8{i} } T ( left ( { {n} over {b rSup { size 8{i} } } } ) right )+ Sum cSub { size 8{j=0} } cSup { size 8{"i-1"} } {a rSup { size 8{j} } d ( left ( { {a} over {b rSup { size 8{j} } } } ) right )} } {}
Giả sử n = bk, quá trình suy rộng trên sẽ kết thúc khi i = k.
Khi đó ta được T( nbk size 12{ { {n} over {b rSup { size 8{k} } } } } {}) = T(1) = 1. Thay vào trên ta có:
T(n) = ak+∑j=0k-1ajdbk-j size 12{a rSup { size 8{k} } + Sum cSub { size 8{j=0} } cSup { size 8{"k-1"} } {a rSup { size 8{j} } d left (b rSup { size 8{"k-j"} } right )} } {} (I.2)
Hàm tiến triển, nghiệm thuần nhất và nghiệm riêng
Trong phương trình đệ quy (I.1) hàm thời gian d(n) được gọi là hàm tiến triển (driving function)
Trong công thức (I.2), ak = nlogba được gọi là nghiệm thuần nhất (homogeneous solutions).
Nghiệm thuần nhất là nghiệm chính xác khi d(n) = 0 với mọi n. Nói một cách khác, nghiệm thuần nhất biểu diễn thời gian để giải tất cả các bài toán con.
Trong công thức (I.2), ∑j=0k-1ajdbk-j size 12{ Sum cSub { size 8{j=0} } cSup { size 8{"k-1"} } {a rSup { size 8{j} } d left (b rSup { size 8{"k-j"} } right )} } {}được gọi là nghiệm riêng (particular solutions).
Nghiệm riêng biểu diễn thời gian phải tốn để tạo ra các bài toán con và tổng hợp các kết quả của chúng. Nhìn vào công thức ta thấy nghiệm riêng phụ thuộc vào hàm tiến triển, số lượng và kích thước các bài toán con.
Khi tìm nghiệm của phương trình (I.1), chúng ta phải tìm nghiệm riêng và so sánh với nghiệm thuần nhất. Nếu nghiệm nào lớn hơn, ta lấy nghiệm đó làm nghiệm của phương trình (I.1).
Việc xác định nghiệm riêng không đơn giản chút nào, tuy vậy, chúng ta cũng tìm được một lớp các hàm tiến triển có thể dễ dàng xác định nghiệm riêng.
Hàm nhân
Một hàm f(n) được gọi là hàm nhân (multiplicative function) nếu f(m.n) = f(m).f(n) với mọi số nguyên dương m và n.
Ví dụ 1-13: Hàm f(n) = nk là một hàm nhân, vì f(m.n) = (m.n)k = mk.nk = f(m) f(n)
Tính nghiệm của phương trình tổng quát trong trường hợp d(n) là hàm nhân:
Nếu d(n) trong (I.1) là một hàm nhân thì theo tính chất của hàm nhân ta có
d(bk-j) = [d(b)]k-j và nghiệm riêng của (I.2) là
∑j=0k-1ajdbk-j size 12{ Sum cSub { size 8{j=0} } cSup { size 8{"k-1"} } {a rSup { size 8{j} } d left (b rSup { size 8{"k-j"} } right )} } {} = ∑j=0k-1aj[d(b)]k-j size 12{ Sum cSub { size 8{j=0} } cSup { size 8{"k-1"} } {a rSup { size 8{j} } [ d ( b ) ] rSup { size 8{"k-j"} } } } {} = [d(b)]k∑j=0k-1[ad(b)]j size 12{ Sum cSub { size 8{j=0} } cSup { size 8{"k-1"} } { [ { {a} over {d ( b ) } } ] rSup { size 8{j} } } } {} = [d(b)]k[ad(b)]k- 1ad(b) - 1 size 12{ { { [ { {a} over {d ( b ) } } ] rSup { size 8{k} } "- 1"} over { { {a} over {d ( b ) } } " - 1"} } } {}
Hay nghiệm riêng = ak - [d(b)]kad(b) - 1 size 12{ { {a rSup { size 8{k} } " - " [ d ( b ) ] rSup { size 8{k} } } over { { {a} over {d ( b ) } } " - 1"} } } {} (I.3)
Xét ba trường hợp sau:
1.- Trường hợp 1: a > d(b) thì trong công thức (I.3) ta có ak > [d(b)]k, theo quy tắc lấy độ phức tạp ta có nghiệm riêng là O(ak) = O(nlogba). Như vậy nghiệm riêng và nghiệm thuần nhất bằng nhau do đó T(n) là O(nlogba).
Trong trương hợp này ta thấy thời gian thực hiện chỉ phụ thuộc vào a, b mà không phụ thuộc vào hàm tiến triển d(n). Vì vậy để cải tiến giải thuật ta cần giảm a hoặc tăng b.
2.- Trường hợp 2: a < d(b) thì trong công thức (I.3) ta có [d(b)]k > ak, theo quy tắc lấy độ phức tạp ta cónghiệm riêng là O([d(b)]k) = O(nlogbd(b)). Trong trường hợp này nghiệm riêng lớn hơn nghiệm thuần nhất nên T(n) là O(nlogbd(b)).
Ðể cải tiến giải thuật chúng ta cần giảm d(b) hoặc tăng b.
Trường hợp đặc biệt quan trọng khi d(n) = n . Khi đó d(b) = b và logbb = 1. Vì thế nghiệm riêng là O(n) và do vậy T(n) là O(n).
3.- Trường hợp 3: a = d(b) thì công thức (I.3) không xác đinh nên ta phải tính trực tiếp nghiệm riêng:
Nghiệm riêng = [d(b)]k∑j=0k-1[ad(b)]j size 12{ Sum cSub { size 8{j=0} } cSup { size 8{"k-1"} } { [ { {a} over {d ( b ) } } ] rSup { size 8{j} } } } {} = ak∑j=0k-11 size 12{ Sum cSub { size 8{j=0} } cSup { size 8{"k-1"} } {1} } {} = akk (do a = d(b))
Do n = bk nên k = logbn và ak = nlogba. Vậy nghiệm riêng là nlogbalogbn và nghiệm này lớn gấp logbn lần nghiệm thuần nhất. Do đó T(n) là O(nlogbalogbn).
Chú ý khi giải một phương trình đệ quy cụ thể, ta phải xem phương trình đó có thuộc dạng phương trình tổng quát hay không. Nếu có thì phải xét xem hàm tiến triển có phải là hàm nhân không. Nếu có thì ta xác định a, d(b) và dựa vào sự so sánh giữa a và d(b) mà vận dụng một trong ba trường hợp nói trên.
Ví dụ 1-14: Giải các phương trình đệ quy sau với T(1) = 1 và
1/- T(n) = 4T( n2 size 12{ { {n} over {2} } } {}) + n
2/- T(n) = 4T( n2 size 12{ { {n} over {2} } } {}) + n2
3/- T(n) = 4T( n2 size 12{ { {n} over {2} } } {}) + n3
Các phương trình đã cho đều có dạng phương trình tổng quát, các hàm tiến triển d(n) đều là các hàm nhân và a = 4, b = 2.
Với phương trình thứ nhất, ta có d(n) = n => d(b) = b = 2 < a, áp dụng trường hợp 1 ta có T(n) = O(nlogba) = O(nlog4) = O(n2).
Với phương trình thứ hai, d(n) = n2 => d(b) = b2 = 4 = a, áp dụng trường hợp 3 ta có T(n) = O(nlogbalogbn) = O(nlog4logn) = O(n2logn).
Với phương trình thứ 3, ta có d(n) = n3 => d(b) = b3 = 8 > a, áp dụng trường hợp 2, ta có T(n) = O(nlogbd(b)) = O(nlog8) = O(n3).
Các hàm tiến triển khác
Trong trường hợp hàm tiến triển không phải là một hàm nhân thì chúng ta không thể áp dụng các công thức ứng với ba trường hợp nói trên mà chúng ta phải tính trực tiếp nghiệm riêng, sau đó so sánh với nghiệm thuần nhất để lấy nghiệm lớn nhất trong hai nghiệm đó làm nghiệm của phương trình.
Ví dụ 1-15: Giải phương trình đệ quy sau :
T(1) = 1
T(n) = 2T() + nlogn
Phương trình đã cho thuộc dạng phương trình tổng quát nhưng d(n) = nlogn không phải là một hàm nhân.
Ta có nghiệm thuần nhất = nlogba = nlog2 = n
Do d(n) = nlogn không phải là hàm nhân nên ta phải tính nghiệm riêng bằng cách xét trực tiếp
Nghiệm riêng = ∑j=0k-1ajdbk-j size 12{ Sum cSub { size 8{j=0} } cSup { size 8{"k-1"} } {a rSup { size 8{j} } d left (b rSup { size 8{"k-j"} } right )} } {} = ∑j=0k-12j2k-jlog2k-j size 12{ Sum cSub { size 8{"j=0"} } cSup { size 8{"k-1"} } {2 rSup { size 8{j} } } 2 rSup { size 8{"k-j"} } "log2" rSup { size 8{"k-j"} } } {} = 2k∑j=0k-1(k-j) size 12{"2k" Sum cSub { size 8{j=0} } cSup { size 8{"k-1"} } { ( "k-j"} ) } {} = 2kk(k+1)2 size 12{2 rSup { size 8{k} } { {k ( k+1 ) } over {2} } } {} = O(2kk2)
Theo giả thiết trong phương trình tổng quát thì n = bk nên k = logbn, ở đây do b = 2 nên 2k = n và k = logn, chúng ta có nghiệm riêng là O(nlog2n), nghiệm này lớn hơn nghiệm thuần nhất do đó T(n) = O(nlog2n).
Trong chương này, chúng ta cần phải nắm vững các ý sau:
1.- Sự phân tích, đánh giá giải thuật là cần thiết để lựa chọn giải thuật tốt, hoặc để cải tiến giải thuật.
2.- Sử dụng khái niệm độ phức tạp và ký hiệu ô lớn để đánh giá giải thuật.
3.- Đối với các chương trình không gọi chương trình con, thì dùng quy tắc cộng, quy tắc nhân và quy tắc chung để phân tích, tính độ phức tạp.
4.- Đối với các chương trình gọi chương trình con, thì tính độ phức tạp theo nguyên tắc “từ trong ra”.
5.- Đối với các chương trình đệ quy thì trước hết phải thành lập phương trình đệ quy, sau đó giải phương trình đệ quy, nghiệm của phương trình đệ quy chính là độ phức tạp của giải thuật.
6.- Khi giải một phương trình đệ quy không thuộc dạng phương trình tổng quát thì sử dụng phương pháp truy hồi hoặc phương pháp đoán nghiệm.
7.- Khi giải một phương trình đệ quy thuộc dạng phương trình tổng quát, nếu hàm tiến triển d(n) là một hàm nhân thì vận dụng công thức nghiệm của môt trong ba trường hợp để xác định nghiệm, còn nếu d(n) không phải là hàm nhân thì phải tính trực tiếp nghiệm riêng và so sánh với nghiệm thuần nhất để chọn nghiệm.
Bài 1: Tính thời gian thực hiện của các đoạn chương trình sau:
a) Tính tổng của các số
{1} Sum := 0;
{2} for i:=1 to n do begin
{3} readln(x);
{4} Sum := Sum + x;
end;
b) Tính tích hai ma trận vuông cấp n C = A*B:
{1} for i := 1 to n do
{2} for j := 1 to n do begin
{3} c[i,j] := 0;
{4} for k := 1 to n do
{5} c[i,j] := c[i,j] + a[i,k] * b[k,j];
end;
Bài 2: Giải các phương trình đệ quy sau với T(1) = 1 và
- T(n) = 3T(n/2) + n
- T(n) = 3T(n/2) + n2
- T(n) = 8T(n/2) + n3
Bài 3: Giải các phương trình đệ quy sau với T(1) = 1 và
- T(n) = 4T(n/3) + n
- T(n) = 4T(n/3) + n2
- T(n) = 9T(n/3) + n2
Bài 4: Giải các phương trình đệ quy sau với T(1) = 1 và
- T(n) = T(n/2) + 1
- T(n) = 2T(n/2) + logn
- T(n) = 2T(n/2) + n
- T(n) = 2T(n/2) + n2
Bài 5: Giải các phương trình đệ quy sau bằng phương pháp đoán nghiệm:
- T(1) = 2 và T(n) = 2T(n-1) + 1 với n > 1
- T(1) = 1 và T(n) = 2T(n-1) + n với n > 1
Bài 6: Cho một mảng n số nguyên được sắp thứ tự tăng. Viết hàm tìm một số nguyên trong mảng đó theo phương pháp tìm kiếm nhị phân, nếu tìm thấy thì trả về TRUE, ngược lại trả về FALSE.
Sử dụng hai kĩ thuật là đệ quy và vòng lặp. Với mỗi kĩ thuật hãy viết một hàm tìm và tính thời gian thực hiện của hàm đó.
Bài 7: Tính thời gian thực hiện của giải thuật đệ quy giải bài toán Tháp Hà nội với n tầng?
Bài 8: Xét công thức truy toán để tính số tổ hợp chập k của n như sau:
- Viết một hàm đệ quy để tính số tổ hợp chập k của n.
- Tính thời gian thực hiện của giải thuật nói trên.