You are on page 1of 25

I.

Lý thuyết
Câu 1: Độ phức tạp thuật toán là gì? Ý nghĩa?
- Độ phức tạp thuật toán không phải là một đại lượng Toán học được nghiên cứu bài bản.
Trong Khoa học máy tính, đây được coi là công cụ để ước lượng độ lớn của hàm thời gian
cũng như so sánh tính hiệu quả giữa các giải thuật.
- Khi đề cập đến độ phức tạp thuật toán, người ta sử dụng các kí hiệu tiệm cận ( O, W, Q)
• Ý nghĩa:
+ Phân lớp cấp độ lớn của hàm thời gian (T(n)) khi n đủ lớn
+ Giải thuật nằm ở phân lớp thấp hơn thì hiệu quả hơn
Câu 2: Bậc tăng trưởng là gì? Ý nghĩa của bậc tăng trưởng?
- “Tốc độ tăng” của 1 hàm số không phải là một đại lượng tính toán được. Người ta chia
“tốc độ tăng” của hàm thành nhiều bậc từ bé đến lớn (tăng tuyến tính, tăng bậc 2, tăng phi
mã, …) và gọi chúng là “bậc tăng trưởng”.
- Bậc tăng trưởng được dựa trên những hàm phổ biến và đã biết trong Toán học. Ví dụ hàm
số 3n2 + 1 có bậc tăng trưởng là n2
• Ý nghĩa:
+ Phân lớp cấp độ lớn của hàm thời gian (T(n)) khi n đủ lớn
+ Hàm số có bậc tăng trưởng cao hơn thì sẽ tăng nhanh hơn và sẽ lớn hơn khi n đủ lớn
Câu 3: So sánh bậc tăng trưởng và độ phức tạp?
• Giống:
+ Đều là công cụ giúp ước lượng cấp độ lớn của hàm thời gian và so sánh giữa các hàm
thời gian hay giải thuật
+ Phân lớp cấp độ lớn của hàm thời gian (T(n)) khi n đủ lớn
• Khác:
+ Nói đến độ phức tạp là nói đến các kí hiệu tiệm cận (tức là nói về giới hạn trên, dưới)
Ví dụ, hàm số 3n2 + 1 có bậc tăng trưởng là n2, nhưng khi nói đến độ phức tạp, cách viết
( )
3n2 + 1 Î O n3 vẫn đúng vì n3 vẫn là một chặn trên của hàm số 3n2 + 1
II. Ký hiệu tiệm cận
Nhắc lại định nghĩa:

( )
f ( n) = O g ( n) Û $c Î  + , n0 Î • : f ( n) £ cg ( n) , " ³ n0 (g(n) là chặn trên của f(n))

( )
f ( n) = W g ( n) Û $c Î  + , n0 Î • : f ( n) ³ cg ( n) , " ³ n0 (g(n) là chặn dưới của f(n))

( )
f ( n) = Q g ( n) Û $c1 , c2 Î  + , n0 Î • : c1g ( n) £ f ( n) £ c2 g ( n) , " ³ n0 (tiệm cận chặt chẽ)
Câu 1: Khẳng định bên dưới là đúng hay sai? Vì sao?

a, ( ) ( ( )) ( )
Nếu f n = Q g n , g n = Q h n thì h n = Q f n ( ( )) ( ) ( ( ))
( )
f ( n) = Q g ( n) Þ $c1 , c2 Î  + , n1 Î • : c1g ( n) £ f ( n) £ c2 g ( n) , "n ³ n1 (1)
g ( n) = Q ( h ( n) ) Þ $c , c Î 
3 4
+
, n2 Î • : c3 h ( n) £ g ( n) £ c4 h ( n) , "n ³ n2 (2)
1 1
Từ (1) ta suy ra f ( n) £ g ( n) £ f ( n) , "n ³ n1
c2 c1
1 1
Từ (2) ta suy ra g ( n) £ h ( n) £ g ( n) , "n ³ n2
c4 c3
ì 1 1 1
ïh ( n) £ c g ( n) £ c . c f ( n) , "n ³ n1 vaøn ³ n2
ï
Þí 3 3 1

ïh ( n) ³ 1 g ( n) ³ 1 . 1 f ( n) ,"n ³ n vaøn ³ n
ïî c4 c4 c2 1 2

1 1
Hay f ( n) £ h ( n ) £ f ( n) , "n ³ max {n1 , n2 }
c2 c4 c1c3
1 1
Chọn c5 = , c6 = , n = max {n1 , n2 } thì c5 f ( n) £ h( n) £ c6 f ( n) , "n ³ n0
c2 c4 c1c3 0
Theo định nghĩa Big- Q , ta suy ra h n Î Q f n ( ) ( ( ))
Vậy khẳng định ban đầu là đúng.

b, ( ) ( )
f ( n) = O g ( n) , g ( n ) = O h ( n ) Þ h ( n ) = W f ( n ) ( )
( )
f ( n) = O g ( n) Þ $c1 Î  + , n1 Î • : f ( n) £ c1g ( n) , "n ³ n1 Û g ( n) ³
1
c1
h ( n) , "n ³ n1

( )
g ( n) = O h ( n) Þ $c2 Î  + , n2 Î • : g ( n) £ c2 h ( n) , "n ³ n2 Û h ( n) ³
1
c2
g ( n) , "n ³ n2

1 1 1
Suy ra h ( n) ³ g ( n) ³ . f ( n) , "n ³ n1 vaøn ³ n2
c2 c2 c1

1
Chọn c = , n = max {n1 , n2 } Þ h ( n) ³ cf ( n) , "n ³ n0
c1c2 0

Theo định nghĩa big- W , ta suy ra h n Î W f n ( ) ( ( ))


Vậy khẳng định ban đầu là đúng.
c, ( ) ( )
f ( n) = O g ( n ) , g ( n ) = O f ( n ) Þ f ( n ) = g ( n )

Ta sẽ chỉ ra 1 phản ví dụ để chứng minh khẳng định trên là sai. Xét f n = 1 và g n = 2 () ()


()
Ta có: g n = 2 £ 2.1 = 2 f n , "n ³ 0 ()
() ()
Chọn c1 = 2, n1 = 0 thì g n £ c1 f n , "n ³ n0 nên ta có g n = O f n ( ) ( ( ))
()
Lại có f n = 1 £ 2 = g n , "n ³ 0 ()
() ()
Chọn c2 = 2, n2 = 0 thì f n £ c2 g n , "n ³ n2 nên f n = O g n ( ) ( ( ))
( ) ( ( )) ( )
Như vậy, ta có f n = O g n và g n = O f n , nhưng rõ ràng f n ¹ g n ( ( )) ( ) ( )
Vậy, khẳng định ban đầu là sai.

d, ( ) (
Q a g ( n) = Q g ( n) , "a > 0 )
(
ìQ a g ( n) Í Q g ( n)
ï
Ta sẽ chứng minh "a > 0 : í
) ( )
(
ïîQ g ( n) Í Q a g ( n) ) ( )
• Chứng minh "a > 0 : Q a g n Í Q g n ( ( )) ( ( ))
( )
Chọn hàm f n bất kì sao cho f ( n ) Î Q (a g ( n ) )

Þ $c1 , c2 Î  + , n0 Î • : c1a g( n) £ f ( n) £ c2a g( n) , "n ³ n0

()
() ()
Chọn c3 = a c1 , c4 = a c2 Þ c3 g n £ f n £ c4 g n , "n ³ n0

Theo định nghĩa Big- Q ta suy ra f ( n) Î Q ( g ( n) )

Hay "f ( n) Î Q (a g ( n) ), ta có f ( n) Î Q ( g ( n) )

Suy ra Q (a g ( n) ) Í Q ( g ( n) ) (2)

• Chứng minh "a > 0 : Q ( g ( n) ) Í Q (a g ( n) )

Chọn hàm f ( n) bất kì sao cho f ( n) Î Q ( g ( n) )

Þ $c , c Î  , n Î • : c g( n) £ f ( n) £ c g( n) , "n ³ n
1 2
+
0 1 2 0
(a g( n)) £ f ( n) £ a (a g( n)) , "n ³ n
c1 c2
Û
a 0

( ) ( )
c1 c2
Chọn c3 = , c4 = Þ c3 a g ( n) £ f ( n) £ c4 a g ( n) , "n ³ n0
a a
Theo định nghĩa Big- Q ta suy ra f ( n) Î Q a g ( n) ( )
( )
Hay "f ( n) Î Q g ( n) , ta có f ( n) Î Q a g ( n) ( )
( ( ))
Suy ra Q g n Í Q a g n ( ( )) (1)

(
Từ (1) và (2) ta suy ra Q a g ( n) = Q g ( n) , "a > 0 ) ( )
III. Sắp xếp theo thứ tự tăng dần của “order of growth”
Nhắc lại, tăng dần big-O theo thứ tự như sau:
c, log(n), n1/3, n1/2, …, n, n3/2, n2, n3, …, 2n, 3n, …, cn , n!, nn
Khi so sánh Big-O, log(n) = O(nc) trong đó c rất bé, nên nếu gặp O(n1/2) và O(n1/2+c) thì 2 cái xấp xỉ nhau

f1 ( n) = 22 f2 ( n) = 2100000 n f3 ( n) = Cn2 f4 ( n) = n n
1000000
Group 1:

f1 ( n) = 22 = O (1)
1000000

f2 ( n) = 2100000 n = O cn (c > 1) ( )
n ( n - 1)
f3 ( n) = Cn2 =
2
= O n2 ( )
f4 ( n) = n n = n3/ 2 = O n3/ 2 ( )
() ()
Vậy f1 n < f4 n < f3 n < f2 n () ()
f1 ( n) = n4Cn2 f2 ( n) = n ( log n) f3 ( n) = n5log n ;
4
Group 2:

n
f4 ( n) = 4 log n + log ( log n) f5 ( n) = å i
i =1

n ( n + 1)
f1 ( n) = n4 .
2
= O n6( )
f2 ( n) = n ( log n) = O n1/ 2 + 4 c ( )
4
f3 ( n) = n5log n = O nn ( ) c

f4 ( n) = 4 log n + log log n < 4 log n + log n = 5 log n = O nc ( )


n ( n + 1)
( )
n
f 5 ( n) = å i = = O n2
i =1 2

() ()
Vậy f4 n < f2 n < f5 n < f1 n < f3 n () () ()
( ) f7 ( n) = nlog n f8 ( n) = 2n/2 f9 ( n) = 3 f10 ( n) = 4n
1/4
Group 3: f6 n = n n n

f 6 ( n) = 2
log n ( ) =2
n
n log n
=2
(
O nc+1/2 )

f7 ( n) = nlog n = 2
(
log nlog n ) = 2(log n)log n = 2O( n ) 2c

f 8 ( n ) = 2 n/ 2 = 2 ( )
On

f 9 ( n) = 3 n
=2
( ) =2
log 3 n
n log3
=2
( )
O n1/2

( )
1

f10 ( n) = 4 n = 22 n = 2
1/4 4 O n1/4

() ()
Vậy f7 n < f10 n < f9 n » f6 n < f8 n () () ()
( ) ( ) ()
Group 4: f1 n = n0.999999 log n ; f2 n = 10000000n; f3 n = 1.000001n; f4 n = n2 () ( )
f1 ( n) = n0.999999 log n = O n0.999999 + c ( )
f2 ( n) = 10000000n = O( n)

f3 ( n) = 1.000001n = O cn (c > 1) ( )
f4 ( n) = n2 = O n2 ( )
()
Vậy f1 n » f2 n < f4 n < f3 n () () ()
Group 5:

f1 ( n) = ( n - 2 )! f2 ( n) = 5log ( n + 100 ) f3 ( n) = 22 n
10
f4 ( n) = 0,001n4 + 3n3 + 1 f5 ( n) = ln2 n f6 ( n) = 3 n f7 ( n) = 3n

f1 ( n) = ( n - 2 )! = O( n!)

( )
f2 ( n) = 5log ( n + 100 ) = 50 log ( n + 100 ) = O nc (c > 0, c rất bé)
10

( )
f3 ( n ) = 2 2 n = 4 n = O 4 n

f4 ( n) = 0, 001n4 + 3n3 + 1 = O n4 ( )
( )
f5 ( n) = ln 2 n = O n2 c

1
æ 1ö
f6 ( n) = 3 n = n3 = O ç n3 ÷
ç ÷
è ø

( )
f 7 ( n ) = 3n = O 3n

() () () () ()
Vậy, f2 n » f5 n < f6 n < f4 n < f7 n < f3 n < f1 n () ()
IV. Lập và giải phương trình đệ quy
Câu 1: Lập phương trình đệ quy
a,

( )
Đặt T n là tổng chi phí khi thực hiện tính g(n)

() () (
Dễ thấy T 1 = c1.Với n > 1, ta có: g n = 3g n / 2 + g n / 2 + 5) ( )
Trong công thức trên, ta gọi đệ quy 2 lần hàm g với giá trị n/2

Þ T ( n) = 2T ( n / 2 ) + c2

ìïT (1) = c1
Phương trình đệ quy: í
ïîT ( n) = 2T ( n / 2 ) + c2 , n > 1
b,

Giải:

( ) ( )
Đặt T n là tổng chi phí khi gọi xn(n). Dễ thấy T 0 = c1

Với n > 0, hàm thực hiện gọi đệ quy cho các giá trị (n-i) với i chạy từ 1 đến n, tức là gọi đệ
quy hàm xn cho các giá trị từ 0 đến n-1.

Đồng thời ta cũng tốn một chi phí là c2 n cho vòng lặp của i từ 1 đến n

n-1
Þ T ( n) = c2 n + c3 + å T ( i )
i =0

ìT ( 0 ) = c1
ï
Phương trình đệ quy: í n-1

ïT ( n) = c2 n + c3 + å T ( i ), n > 0
î i =0

c, waste(n)
{ if (n==0) return 0;
[ for (i=1 to n)
for (j=1 to i)
print i,j,n; ] // Tạm gọi đoạn chương trình này là P
for (i=1 to 3)
waste(n/2);
}
Giải

( ) ( )
Đặt T n là tổng chi phí khi thực hiện waste(n). Dễ thấy T 0 = c1 (hằng số)

Với n > 0, trước hết ta xét đoạn đoạn chương trình P.


Ứng với mỗi giá trị của i, vòng lặp của j lặp i lần.
Coi rằng lệnh “print i,j,n” tốn 1 chi phí (với a là hằng số) thì tổng chi phí cho P là:

ì n
ïsosanh ( n) = n + 1 + å ( i + 1) =
( n + 1)( n + 4 )
ï i =1 2
à TP = sosanh(n) + print(n)
í
ï n n ( n + 1)
ï print ( n) = å i = 2
î i =1

Ta chỉ xem xét đến bậc cao nhất của n, do đó có thể viết TP = c2 n2

(Chỉ cần ghi c2n2 thôi để lúc giải nó dễ hơn, ghi đầy đủ thì khó giải)

( )
Đối với vòng lặp cuối, waste n / 2 được gọi đệ quy tổng cộng 3 lần.

() (
Từ đó ta suy ra: T n = 3T n / 2 + c2 n2 )
ìïT ( 0 ) = c1
Phương trình đệ quy: í
ïîT ( n) = 3T ( n / 2 ) + c2 n > 0
2

d, Lập phương trình truy hồi cho T(n) (là tổng số phép cộng cần thực hiện khi gọi Zeta(n))
Zeta(n)
{ if (n==0) Zeta = 6;
else
{ k = 0; Ret = 0;
while ( k £ n - 1 )
{ Ret = Ret + Zeta(k);
k = k + 1;
}
Zeta = Ret;
}
}
Giải:

Dễ thấy với n = 0, ta có T 0 = 0( )
Với n > 0, hàm thực hiện gọi đệ quy cho các giá trị Zeta từ 0 đến n-1
Bên cạnh đó, ta đếm nhanh được tổng số phép cộng cho vòng lặp while ( k £ n - 1 ) (không
tính Zeta(k)) là 2n

ìT ( 0 ) = 0
ï
Phương trình truy hồi cho T(n): í n-1

ïT ( n) = å T ( i ) + 2n, n ³ 1
î i =0

Câu 2: Giải phương trình đệ quy


A. Phương pháp truy hồi (thay thế)

+ Tổng cấp số cộng: u1 + u2 + ... + un =


(un
+ u1 ) n
2
q.un - u1
+ Tổng cấp số nhân: u1 + u2 + ... + un = với q là công bội ( q.u = u2 ; q.u2 = u3 ,...)
q -1

ìT (1) = 1
a, ï
í ænö
ïT ( n ) = 2T ç ÷+n
î è2ø

é æ n ö nù
T ( n ) = 2 ê 2T ç ÷ + ú + n
ë è 4 ø 2û
æ nö
T ( n) = 2 2 T ç 2 ÷ + 2 n
è2 ø
é æ n ö nù
T ( n ) = 22 ê 2T ç ÷ + ú + 2n
ë è 8 ø 4û
ænö
T (n) = 23 T ç 3 ÷ + 3n
è2 ø
ænö
T (n) = 2i T ç i ÷ + in
è2 ø
n
Quá trình sẽ dừng lại khi đạt tới T(1) Þ i
= 1 Û n = 2i Û i = log 2 n
2
Khi đó: T (n) = nT (1) + n log 2 n. Hay T ( n ) = n + n log 2 n

ìT (1) = 1
b, ï
í ænö 2
ïT (n) = 2T ç 2 ÷ + n
î è ø

é æ n ö æ n ö2 ù
T ( n ) = 2 ê2T ç ÷ + ç ÷ ú + n2
ëê è 4 ø è 2 ø ûú
ænö n2
T (n) = 22 T ç ÷ + n2 +
è4ø 2
é æ n ö æ n ö2 ù 2 n 2
T ( n ) = 2 ê 2T ç ÷ + ç ÷ ú + n +
2

ëê è 8 ø è 4 ø ûú 2
2 2
ænö 2 n n
T (n) = 2 T ç 3 ÷ + n + + 2
3

è2 ø 2 2
i -1 k
ænö æ1ö
T (n) = 2 T ç i ÷ + n 2 å ç ÷
i

è2 ø k =0 è 2 ø

æ nö æ 1ö
T ( n) = 2i T ç i ÷ + n2 .2. ç 1 - i ÷
è2 ø è 2 ø
n
Quá trình sẽ dừng lại khi đạt tới T(1) Þ i
= 1 Û 2i = n
2
Khi đó: T (n) = nT (1) + 2n 2 æç1 - ö÷. Hay T n = 2n2 - n
1
()
è nø

ìT (1) = 1
c, ï
í ænö 3
ïT (n) = 8T ç 2 ÷ + n
î è ø

é ænö ænö ù
3

T (n) = 23 ê23 T ç ÷ + ç ÷ ú + n3
êë è 4 ø è 2 ø úû
æ nö
T (n) = 22.3 T ç 2 ÷ + 2n3
è2 ø
é 3 æ n ö æ n ö3 ù
T (n) = 2 ê2 T ç ÷ + ç 2 ÷ ú + 2n3
2.3

ëê è 8 ø è 2 ø ûú
ænö
T (n) = 23.3 T ç 3 ÷ + 3n3
è2 ø
ænö
T (n) = 23.i T ç i ÷ + in3
è2 ø
n
Quá trình sẽ dừng lại khi tới T(1) Þ i
= 1 Û n = 2i Û i = log 2 n
2
Khi đó: T (n) = n T (1) + n log 2 n. Hay T ( n) = n3 (1 + log2 n)
3 3

B. Phương trình đặc trưng

ìïT ( 0 ) = 1, T (1) = 2
a, í
ïîT ( n) = 4T ( n - 1) - 3T ( n - 2 )
T ( n) = 4T ( n - 1) - 3T ( n - 2 ) Û T ( n) - 4T ( n - 1) - 3T ( n - 2 ) = 0

()
Đặt xn = T n , ta có xn - 4 xn-1 + 3xn-2 = 0

Phương trình đặc trưng: x2 - 4 x + 3 = 0

Phương trình đặc trưng có 2 nghiệm đơn là x1 = 1 và x2 = 3

()
Do đó, T n = c1 x1n + c2 x2 n = c1 + c2 .3n

ì 1
ìïc1 + c2 = 1 ïïc1 = 2
Với T ( 0 ) = 1 và T (1) = 3, ta có hệ phương trình: í Þí
îïc1 + 3c2 = 2 ïc = 1
ïî 2 2

Vậy T ( n) =
1 1 n 1 n
(
+ .3 = 3 + 1 , "n ³ 0
2 2 2
)
ìïT ( 0 ) = 0, T (1) = 1, T ( 2 ) = 2
b, í
ïîT ( n) = 4T ( n - 1) - 5T ( n - 2 ) + 2T ( n - 3 )

T ( n) = 4T ( n - 1) - 5T ( n - 2 ) + 2T ( n - 3 )
Û T ( n) - 4T ( n - 1) + 5T ( n - 2 ) - 2T ( n - 3 ) = 0

()
Đặt xn = T n , ta có xn - 4 xn-1 + 5xn-2 - 2 xn-3 = 0

( )( )
2
Phương trình đặc trưng: x3 - 4 x2 + 5x - 2 = 0 Û x - 2 x - 1 = 0

Phương trình đặc trưng có nghiệm đơn là x1 = 2 và nghiệm kép x2 = 1

() ( )
Do đó, T n = c1 x1n + c2 n + c3 x2 n = c1 .2n + c2 n + c3

ìT ( 0 ) = 0 ìc + 0 + c = 0 ìc1 = 0
ïï ï
1 3
ï
Ta có hệ phương trình: íT (1) = 1 Þ í2c1 + c2 + c3 = 1 Þ íc2 = 1
ï ï ï
ïîT ( 2 ) = 2 î4c1 + 2c2 + c3 = 2 îc3 = 0

()
Vậy, T n = n, "n ³ 0
C. Hàm sinh

1. an = 1 Þ G ( x ) = å xn = 1
n= 0 1- x

2. an = n Þ G ( x ) = å nxn = x
(1 - x )
2
n= 0


1
3. an = n + 1 Þ G ( x ) = å ( n + 1) xn =
(1 - x )
2
n= 0


4. an = ( -1) Þ G ( x ) = å ( -1) xn = 1
n n

n= 0 1+ x

1
5. an = ( n + 1) a n Þ G ( x ) = å xna n ( n + 1) =
(1 - a x )
2
n= 0

ìïT ( 0 ) = C1
a, í
ïîT ( n) = T ( n - 1) + C2 , n > 0

Hàm sinh của dãy vô hạn T ( n) { }



là:
n= 0

+¥ +¥
f ( x ) = å T ( n) xn = T ( 0 ) + å T ( n) xn
n= 0 n=1


f ( x ) = C1 + å éëT ( n - 1) + C2 ùû xn
n=1

+¥ +¥
f ( x ) = å T ( n - 1) xn + C2 å xn + C1
n=1 n=1


æ +¥ ö
f ( x ) = xå T ( n - 1) xn-1 + C2 ç å xn - 1÷ + C1
n=1 è n=0 ø

æ 1 ö
f ( x ) = xf ( x ) + C2 ç - 1 ÷ + C1
è 1- x ø

1
f ( x ) = C2
x
+ C1 .
(1 - x )
2
(1 - x )
+¥ +¥ +¥
f ( x ) = C2 å nxn + C1 å xn = å (C2 n + C1 ) xn
n= 0 n= 0 n= 0

()
Vậy, T n = C1 + nC2 , "n ³ 0
ìïT ( 0 ) = 1, T (1) = 2
b, í
ïîT ( n) = 7T ( n - 1) - 12T ( n - 2 ) , n ³ 2

Hàm sinh của dãy vô hạn T ( n) { }



là:
n= 0

+¥ +¥
f ( x ) = å T ( n) xn = T ( 0 ) + xT (1) + å T ( n) xn
n= 0 n=2


f ( x ) = 2 x + 1 + å éë7T ( n - 1) - 12T ( n - 2 )ùû xn
n=2

+¥ +¥
f ( x ) = 2 x + 1 + 7å T ( n - 1) xn - 12å T ( n - 2 ) xn
n=2 n=2

æ +¥ ö +¥
f ( x ) = 2 x + 1 + 7 ç å T ( n - 1) xn - xT ( 0 ) ÷ - 12 x2 å T ( n - 2 ) xn-2
è n=1 ø n= 2

( )
f ( x ) = 2 x + 1 + 7 xf ( x ) - x - 12 x2 f ( x )

1 - 5x 2 1
f ( x) = = -
12 x - 7 x + 1 1 - 3 x 1 - 4 x
2

+¥ +¥ +¥
f ( x ) = 2å ( 3x ) - å ( 4 x ) = å 2.3n - 4n xn ( )
n n

n= 0 n= 0 n= 0

()
Vậy, T n = 2.3n - 4n , "n ³ 0

ìïT ( 0 ) = 3
c, í
ïîT ( n + 1) = T ( n) + 2 ( n + 2 ) , n ³ 0

T ( n + 1) = T ( n) + 2 ( n + 2 ) , n ³ 0 suy ra: T ( n) = T ( n - 1) + 2 éë( n - 1) + 2 ùû , n ³ 1

() (
Hay T n = T n - 1 + 2n + 2, n ³ 1 )
Hàm sinh của dãy vô hạn T ( n) { }

là:
n= 0

+¥ +¥
f ( x ) = å T ( n) xn = T ( 0 ) + å T ( n) xn
n= 0 n=1


f ( x ) = 3 + å éëT ( n - 1) + 2n + 2 ùû xn
n=1
+¥ +¥ +¥
f ( x ) = 3 + å T ( n - 1) xn + 2å nxn + 2å xn
n=1 n=1 n=1

æ 1 ö
f ( x ) = 3 + xf ( x ) + 2
x
+ 2ç - 1÷
(1 - x ) è 1- x ø
2

2x 2x 3
f ( x) = + +
(1 - x ) (1 - x ) 1- x
3 2

2x - 2 + 2 2x 3
f ( x) = + +
(1 - x )
3
(1 - x )
2
(1 - x )
-2 2 2x 3
f ( x) = + + +
(1 - x ) (1 - x ) (1 - x ) 1- x
2 3 2

2 2x - 2 3
f ( x) = + +
(1 - x ) (1 - x )
3 2
(1 - x )
2 2 3
f ( x) = - +
(1 - x ) 1- x 1- x
3

2 1
f ( x) = +
(1 - x ) 1- x
3

+¥ +¥
f ( x ) = å ( n + 1)( n + 2 ) xn + å xn
n= 0 n= 0

(
f ( x ) = å n2 + 3n + 3 xn
n= 0
)
()
Vậy, T n = n2 + 3n + 3, "n ³ 0

1
Trong trường hợp không biết công thức của thì đi tìm dựa trên công thức đã có
(1 - x )
3


1 1 1
Tương tự với sẽ là . Ta có = å ( n + 1) xn . Đạo hàm 2 vế:
(1 - x ) (1 - x ) (1 - x )
3 2 2
n= 0

+¥ +¥ +¥
2
= å n ( n + 1) x n-1
. Đổi biến m = n - 1à å n n + 1 xn-1 = ( ) å ( m+ 1)( m+ 2 ) x m

(1 - x )
3
n= 0 n= 0 m=-1
+¥ +¥
m = -1 Þ ( m + 1)( m + 2 ) xm = 0 ® å ( m+ 1)( m+ 2 ) xm = å ( m+ 1)( m+ 2 ) xm
m=-1 m= 0


2
Suy ra = å ( n + 1)( n + 2 ) xn
(1 - x )
3
n= 0

V. Thiết kế thuật toán


A. Quay lui (backtracking)
Câu 1: Cho 1 số tự nhiên N £ 9. Tìm các cách đặt dấu +, - hoặc không đặt dấu giữa các số từ 1 đến
N sao cho kết quả thu được bằng 0. Viết mã giả và minh họa thuật toán để giải quyết bài toán trên.
Ví dụ: input N = 4.
Output: 1
1-2-3+4
Giải
- Sử dụng giải thuật backtracking, thử tất cả các cách đặt dấu có thể có và chọn ra cách đặt
dấu phù hợp yêu cầu bài toán.
- Kết quả được lưu trữ ở dạng mảng / vector x, gồm có n – 1 phần tử, trong đó x[i] là cách
đặt dấu giữa số i và i+1.
* Mã giả cho bài toán:
int calc(N, x); // hàm trả về kết quả nhận được dựa theo cách đặt dấu được truyền vào
print(N, x); // hàm xuất ra biểu thức dựa theo cách đặt dấu được truyền vào
Find(N, i, count) // tại bước i, đi tìm phần tử x[i], biến count để đếm số cách đặt dấu phù hợp
{ d[3] = { ‘’ , ‘+’, ‘-‘ } // các cách đặt dấu
For (j=1 à 3)
{ x[i] = d[j]
If (i = N-1)
{ if (calc(N,x) = 0) // tìm thấy cách đặt phù hợp
{ count++
print(N,x) // xuất biểu thức
}
}
else Find(N,i+1,count)
x[i] = ‘’ // trả lại trạng thái cũ
} // kết thúc vòng lặp của j
} // kết thúc hàm
main()
{
// Nhập N
x = [‘’] * (N-1) // khởi tạo mảng x ban đầu là không đặt dấu tại mọi vị trí
count = 0 // khởi tạo biến đếm
Find(N,1,count)
print(count) // xuất số cách đặt dấu phù hợp
}
Minh họa: N = 3
a, Mã giả:
// Khởi tạo
T = {} // tập chứa các cạnh của cây khung
value = 0 // tổng trọng số của cây khung tối tiểu
Min_T // cây khung tối tiểu
Backtrack(E, n, i, T, sum) // tại bước thứ i, tìm cạnh thứ i của cây khung
// biến sum để tính giá trị của cây khung
{
For each j in E // với mỗi cạnh j thuộc tập cạnh hiện hành
{
T = T.insert(j) // thêm cạnh j vào cây khung hiện có
If T.valid // nếu cây khung hiện tại không có chu trình
{
i++; // tăng số lượng cạnh trong cây khung
E.pop(j) // xóa cạnh j khỏi tập cạnh
sum = sum + weight(j) // cộng vào sum trọng số của cạnh j
if (i = n-1) // hoàn tất cây khung
{
if (sum < value) // tìm được cây khung có trọng số nhỏ hơn
{ // cập nhật trọng số và cây khung tối tiểu
value = sum; Min_T = T;
}
}
else Backtrack(E,n,i,T,sum) // tiếp tục tìm kiếm
// trả lại trạng thái cũ
i--;
E.insert(j)
sum = sum – weight(j)
}
T.pop(j)
}
}
main()
{
Nhập V, E, n;
Backtrack(E,n,1,T,0);
// Xuất kết quả
Print(value, Min_T);
}
b,
- Sắp xếp các cạnh trong tập cạnh theo thứ tự trọng số tăng dần
- Lần lượt duyệt qua các cạnh trong tập cạnh, nếu cạnh j không tạo chu trình với cây khung
hiện có thì thêm j vào cây khung và cập nhật trọng số. Ngược lại thì bỏ qua và tiếp tục duyệt
đến cạnh tiếp theo
- Quá trình dừng lại khi đã chọn đủ n – 1 cạnh, trong đó n là số lượng đỉnh của đồ thị.
- Số lượng đỉnh: n = 8
- Sắp xếp tập cạnh: V = {AB, CE, CD, EF, AC, EG, AF, DE, BC, FH}
- Lần lượt duyệt qua các cạnh của V
+ Xét cạnh AB, thỏa mãn, chọn à T = {AB}, v = 3
+ Xét cạnh CE, thỏa mãn, chọn à T = {AB, CE}, v = 8
+ Xét cạnh CD, thỏa mãn, chọn à T = {AB, CE, CD}, v = 14
(Tương tự, các cạnh EF, AC, EG đều thỏa mãn)
T = {AB, CE, CD, EF, AC, EG}, v = 38
+ Xét cạnh AF, tạo chu trình với T (ACEFA) (loại)
Tương tự, cạnh DE, BC cũng tạo chu trình với T nên không chọn
+ Xét cạnh FH, thỏa mãn, chọn à T = {AB, CE, CD, EF, AC, EG, FH}, v = 53
Số cạnh trong T bằng 7, quá trình kết thúc

B. Quy hoạch động (dynamic programming)


Câu 1: Bài toán dãy con tăng dài nhất.
Input:
+ 1 số nguyên dương n là số lượng phần tử của dãy a
+ n số nguyên dương là các phần tử của dãy a
Output:
+ 1 số nguyên dương là độ dài của dãy con tăng dài nhất của a
(Quy ước dãy phải là tăng ngặt, tức phần tử sau phải lớn hơn phần tử trước đó)
Giải
Ta quy ước nếu bài toán có nhiều dãy con có cùng độ dài lớn nhất thì ta chỉ cần in ra 1 dãy
Bước 1: Phân tích đặc trưng Optimal Substructure
- Xét ví dụ sau đây: dãy 7 3 5 3 6 2 9, giả sử ta xét đến a[6] = 9
+ Chưa xét đến tính tối ưu, bài toán đặt ra là tìm dãy con kết thúc bởi giá trị 9
+ Muốn vậy, ta phải tìm các dãy con mà phần tử kết thúc bé hơn 9.
+ Mỗi lần tìm các dãy con thỏa mãn như vậy ta có thể coi là một bài toán con của bài toán
được đặt ra ở trên
Nhận xét 1: lời giải của bài toán gốc có sự kết hợp lời giải của các bài toán con
Như vậy độ dài lớn nhất của dãy con tăng kết thúc bởi 9 là: max {2,2,3,4,3} = 4
- Cũng với ví dụ trên, bây giờ ta xét đến tính tối ưu.
+ Muốn tìm được dãy con tăng dài nhất kết thúc bởi 9, ta phải tìm được dãy con tăng có
độ dài lớn nhất trong số các dãy con mà phần tư kết thúc bé hơn 9
+ Giả sử ta đã biết kết quả của ví dụ trên là 3 5 6 9, vậy thì do đặc điểm vừa nói trên, dãy 3
5 6 là dãy có độ dài lớn nhất kết thúc bởi giá trị 6
Nhận xét 2: lời giải tối ưu của bài toán gốc chứa đựng trong đó lời giải tối ưu của bài toán
con
- Nhận xét 3: mỗi một số trong dãy đều có thể coi là một dãy con tăng với độ dài là 1
Bước 2: Lập phương trình quy hoạch động
Quy ước m[i] là độ dài của dãy con tăng dài nhất của dãy A1 A2 A3 … Ai (1 <=i <= n)
- Nếu a[i] không lập được thành dãy tăng với bất kì phần tử nào khác trước đó, ta có m[i]=1
(1 <=i <= n)
- Ngược lại, ta lần lượt xét các dãy con tăng mà phần tử kết thúc a[j] của nó bé hơn a[i] và j
< i. Để tìm m[i], ta phải xét dãy có độ dài lớn nhất trong số các dãy này. Do đó, ta có m[i]
= max{m[j]} với mỗi 1<= j < i thỏa mãn a[j] < a[i]

ìm[i ] = 1, if there is no j < i that a[j] < a[i]


í
îm[i ] = max{m[ j ] + 1}, with each 1 £ j < n and a[j] < a[i] (2 £ i £ n)
Kết quả của bài toán sẽ là max{m[i ]}, with 1 £ i £ n

Bước 3: Lập bảng lưu trữ kết quả cho bài toán ban đầu.
Xét ví dụ: dãy 4 3 1 2 6 3 4 (n = 7)
i 1 2 3 4 5 6 7
a 4 3 1 2 6 3 4
m 1

- Xét i = 1 à m[1] = 1
- Xét i = 2, a[2] = 3 không lập được thành dãy tăng với bất kì phần tử nào khác trước đó
à m[2] = 1
- Xét i = 3, a[3] = 1 không lập được thành dãy tăng với bất kì phần tử nào khác trước đó
à m[3] = 1
- Xét i = 4, a[4] = 2
j = 3, a[j] = 1 < 2
à m[4] = m[3] + 1 = 2
- Xét i = 5, a[5] = 6
+ j = 1, a[j] = 4 < 6 à m[1] + 1 = 2
+ j = 2, a[j] = 3 < 6 à m[2] + 1 = 2
+ j = 3, a[j] = 1 < 6 à m[3] + 1 = 2
+ j = 4, a[j] = 2 < 6 à m[4] + 1 = 3
m[5] = max {2,2,2,3} = 3
Thực hiện tương tự cho i = 6 và i = 7
i 1 2 3 4 5 6 7
a 4 3 1 2 6 3 4
m 1 1 1 2 3 3 4

Bước 4: Xác định kết quả cho bài toán ban đầu.

{ }
longest = max m[i ] = max {1,1,1,2,3,3,4} = 4
1£i £ n

Trong trường hợp cần tìm dãy con tăng, ta sử dụng thêm 1 mảng để lưu vết kết quả.
i 1 2 3 4 5 6 7
a 4 3 1 2 6 3 4
m 1 1 1 2 3 3 4
v -1 -1 -1 3 4 4 6

v[i] là chỉ số của phần tử cuối cùng trong dãy tăng kết thúc bởi phần tử a[i]
Trong trường hợp dãy chỉ có 1 phần tử, mặc định v[i] = -1
Ở trên, index của phần tử cuối cùng trong dãy tăng dài nhất là 7, từ đó ta có:
v[7] = 6 à v[6] = 4 à v[4] = 3 à v[3] = -1 (kết thúc)
Dãy cần tìm: a[3] , a[4], a[6], a[7] cũng chính là 1 2 3 4
Câu 2: bài toán chuỗi con chung dài nhất
Input: 2 chuỗi a và b có độ dài lần lượt là m và n
Output: 1 số nguyên dương x cho biết độ dài của chuỗi con chung dài nhất và chuỗi con chung dài
nhất đó
Giải
Gọi c là chuỗi con chung được tạo thành từ a và b
a(x,y) là chuỗi con được tạo thành bằng các kí tự từ vị trí x đến y trong chuỗi a
Bước 1: phân tích đặc trưng Optimal substructure
- Chưa xét đến chuỗi con chung tìm được có phải là dài nhất hay không, ta xem xét đến việc
tìm chuỗi con chung giữa 2 chuỗi a và b
Giả sử rằng ta tìm chuỗi con chung giữa 2 chuỗi a(1,i) và b(1,j) với 1 £ i £ m,1 £ j £ n
+ Nếu kí tự a[i] và b[j] giống nhau, ta biết kí tự này sẽ nằm trong c. Như vậy, chuỗi c sẽ
được ghép từ kí tự a[i] với chuỗi chung tìm được từ a(1,i-1) và b(1,j-1). Ta sẽ tiếp tục xem
xét 2 chuỗi này.
+ Nếu a[i] # b[j], a[i] hoặc b[j] không được chọn, ta sẽ xem xét chuỗi a(1,i-1) với b(1,j),
hoặc chuỗi a(1,i-1) với b(1,j-1)
Các việc xem xét giữa các cặp chuỗi như trên đều là bài toán con của việc tìm chuỗi chung
giữa 2 chuỗi a(1,i) và b(1,j)
Nhận xét 1: lời giải của bài toán gốc có sự kết hợp lời giải của các bài toán con
- Khi xét đến yêu cầu chuỗi chung phải là dài nhất, việc xét các cặp chuỗi theo từng trường
hợp như trên đều phải chọn ra chuỗi chung dài nhất.
Ví dụ, với chuỗi a = “abcde” (m=5) và b = “adef” (n=4)
Giả sử ta đã biết chuỗi con chung dài nhất của 2 chuỗi này là c = “ade”
Khi xét a(1,5) = “abcde” và b(1,3) = “ade”, ta có a[5] = b[3] = “e”, “ad” chính là chuỗi
con chung dài nhất khi xét a(1,4) = “abcd’ và b(1,2) = “ad”
Nhận xét 2: lời giải tối ưu của bài toán gốc chứa đựng trong đó lời giải tối ưu của bài toán
con
Bước 2: Lập phương trình quy hoạch động
- Xuất phát từ việc xét các trường hợp như đã nói ở trên
- Gọi l[i][j] là độ dài của chuỗi con chung dài nhất khi xét a(1,i) và b(1,j)
- Nếu a[i] = b[j], ta sẽ có l[i][j] = 1 + l[i-1][j-1] (do l[i-1][j-1] lúc này là độ dài của chuỗi
con chung dài nhất khi xét a(1,i-1) và b(1,j-1))
- Nếu a[i] # b[j], ta sẽ xem xét chuỗi a(1,i-1) với b(1,j) hoặc a(1,i) với b(1,j-1) và a(1,i-1).
l[i][j] lúc này chính bằng chuỗi con chung dài hơn khi xét chuỗi con chung dài nhất của 2
cặp chuỗi trên. Nói cách khác, l[i][j] = max{l[i][j-1]; l[i-1][j]}
- Phương trình quy hoạch động:
+ Nếu tồn tại giá trị 1<= i <= m sao cho a[i] = b[1] và i phải nhỏ nhất, ta có

ì1, if j ³ i
l [ j ][1] = í . Ngược lại, l[i][1] = 0 với 1<= i <= m
î0, if j < i
+ Nếu tồn tại giá trị 1<= i <= n sao cho a[1] = b[i] và i phải nhỏ nhất, ta có

ì1, if j ³ i
l [1][ j ] = í . Ngược lại, l[1][i] = 0 với 1<= i <= n
î 0, if j < i

ìl [i - 1][ j - 1] + 1 , if a[i] = b[j]


+ l [i ][ j ] = ïí ( 2 £ i £ m,2 £ j £ n)
ïîmax {l [i - 1][ j ], l [i ][ j - 1]} , otherwise

- Kết quả bài toán sẽ là l[m][n]


Bước 3: Lập bảng lưu trữ kết quả cho bài toán ban đầu
- Xét ví dụ a = “abcdef” (m=6) và b = “acmfh” (b=5)
1 (a) 2 (c) 3 (m) 4 (f) 5 (h)
1 (a) 1 1 1 1 1
2 (b) 1
3 (c) 1
4 (d) 1
5 (e) 1
6 (f) 1
- Dễ dàng điền được các giá trị l[1][i] và l[j][1]
- Xét i = 2,j = 2 ta thấy a[2] = ‘b’, b[1] = ‘a’ à l[2][2] = max{ l[1][2], l[2][1]}
Hay l[2][2] = max{1,1} = 1, chuỗi con chung là c = “a”
- Thực hiện tương tự cho các giá trị của j với i = 2
1 (a) 2 (c) 3 (m) 4 (f) 5 (h)
1 (a) 1 1 1 1 1
2 (b) 1 1 1 1 1
3 (c) 1
4 (d) 1
5 (e) 1
6 (f) 1
- Xét i = 3, j = 2, ta có a[3] = b[2] = ‘c’ à l[3][2] = 1+ l[2][1] = 1 + 1 = 2
Chuỗi con chung: c = “ac”
- Thực hiện tương tự cho các giá trị còn lại của i và j
1 (a) 2 (c) 3 (m) 4 (f) 5 (h)
1 (a) 1 1 1 1 1
2 (b) 1 1 1 1 1
3 (c) 1 2 2 2 2
4 (d) 1 2 2 2 2
5 (e) 1 2 2 2 2
6 (f) 1 2 2 3 3
- Bước 4: Xác định kết quả của bài toán ban đầu
+ Độ dài của chuỗi con chung dài nhất: 3
+ Chuỗi con chung dài nhất: “acf”
Câu 3: Wecode – Đường đi an toàn
Giải
- Giả sử bản đồ được biểu diễn bởi ma trận 2 chiều a, kích thước n x n
Bước 1: phân tích đặc trưng Optimal substructure
- Giả sử a[i][j] (1 <= i,j <= n) là 1 ô an toàn. Ta chưa xét đến việc có tồn tại đường đi an toàn
đến ô a[i][j], nhưng để đến được ô này, ta phải xét đến việc đi đến ô a[i][j-1] hoặc ô a[i-1][j].
Khi đó, việc tìm đường đi đến 2 ô này là 1 bài toán con khi tìm đường đến ô a[i][j]
Nhận xét 1: lời giải của bài toán gốc có sự kết hợp lời giải của các bài toán con
- Xét yêu cầu đường đi phải là an toàn. Nếu giả sử có đường đi an toàn đến ô a[i][j] thì chắc
chắn phải tồn tại đường đi an toàn đến ô a[i][j-1] hoặc a[i-1][j]
Nhận xét 2: lời giải tối ưu của bài toán gốc chứa đựng trong đó lời giải tối ưu của bài toán
con
Nhận xét 3: số lượng đường đi an toàn đến ô a[i][j] bằng tổng số lượng đường đi an toàn
đến ô a[i-1][j] và ô a[i][j-1]
Bước 2: Lập phương trình quy hoạch động
Gọi m[i][j] là số lượng đường đi an toàn đến ô a[i][j] (1 <= i,j <= n)
Ta chỉ có thể đi sang phải hoặc xuống dưới các bài toán cơ sở sẽ là tìm đường đi an toàn đến
các a[1][i] và a[j][1] (với mỗi i và mỗi j từ 1 đến n)
+ Nếu tồn tại giá trị 1<= i <= n mà ô a[i][1] không an toàn và i phải nhỏ nhất, ta có
ì0, if j ³ i
m[ j ][1] = í . Ngược lại, m[i][1] = 1 với 1<= i <= n
î1, if j < i

+ Nếu tồn tại giá trị 1<= i <= n mà ô a[1][i] không an toàn và i phải nhỏ nhất, ta có

ì0, if j ³ i
m[1][ j ] = í . Ngược lại, m[1][i] = 1 với 1<= i <= n
î1, if j < i

ì0 , if a[i][j] is not safe


+ m[i ][ j ] = í (2 <= i,j <= n)
î m[i - 1][ j ] + m[i ][ j - 1], otherwise

- Kết quả bài toán là m[n][n]


Bước 3: Lập bảng lưu trữ kết quả cho bài toán ban đầu

- Xét ví dụ n = 4
1 2 3 4
1 1 1 1 1
2 1
3 1
4 0
- Với i = 2, j = 2, ô a[2][2] không an toàn nên m[2][2] = 0
- Với i = 2, j = 3, ô a[2][3] an toàn, ta có m[2][3] = m[2][2] + m[1][3] = 0 + 1 = 1.
Tức là chỉ có 1 đường đi an toàn đến ô a[2][3]
- Thực hiện tương tự cho các ô còn lại
1 2 3 4
1 1 1 1 1
2 1 0 1 2
3 1 1 2 0
4 0 1 3 3
Bước 4: Xác định kết quả của bài toán ban đầu
- Kết quả bài toán là m[4][4] = 3, tức là có 3 đường đi an toàn đến đích

You might also like