Chủ đề 2: Ký hiệu “ O lớn” và khái niệm độ phức
tạp của thuật toán
I. Khái niệm cơ sở:
1. Định nghĩa “O lớn” :
Cho 2 hàm f , g :
→Ν
R
Ta nói
gf
nếu tồn tại M > 0 và
Ν∈
0
n
sao cho
0
,)(.)( nnngMnf ≥∀≤
2. Lưu ý :
Ý nghĩa
g
f
bị chặn khi n đủ lớn
Cũng có thể xem
Ν∈
M
, nhiều khi cũng không cần thiết
Nn ∈
0
Thông thường về mặt áp dụng thì f , g xác định trên khoảng liên tục (a,
+
∞
)
R⊂
3. Ký hiệu
Với mọi hàm g, ta định nghĩa
O(g) =
{ }
gff /
Ví dụ 1:
g(n) =
2
2000
1
n
f
1
(n) = n
f
2
(n) = 3n
2
Ta có
{ }
gf
1
bởi vì:
2000,)(.1)(
1
≥∀≤ nngnf
Hay vì
1,)(.2000)(
1
≥∀≤ nngnf
Như vậy:
)(
1
gOf ∈
Tương tự:
)(
2
gOf ∈
Ví dụ 2:
g(n) = (n+1)
2
f
1
(n) = n
2
(chọn M =4 , n
0
= 1)
Ví dụ 3:
g(n) = 3n
3
+ 2n
2
f
1
(n) = n
3
(chọn M =5 , n
0
= 0)
1
4. Định nghĩa độ phức tạp thuật toán :
Gọi f là độ phức tạp của g, ký hiệu f =
gΘ
khi
=
=
)(
)(
fOg
gOf
Ví dụ n
2
=
)
2000
1
(
2
nΘ
• Mệnh đề
o Nếu
L
xg
xf
Lim
x
=
∞→
)(
)(
thì f = O(g)
Nếu L = 0 thì g
)( fO≠
Nếu L
0≠
thì f =
)(gΘ
5. Kỷ thuật “Bỏ bớt phân nửa” :
Kỷ thuật thông dụng thường dùng trong khoa học máy tính
Ví dụ: f(n) = 1
k
+2
k
+3
k
+…+n
k
Hiển nhiên
1
)(
+
=++≤
kkk
nnnnf
Như vậy f = O(n
k+1
)
Chưa biết f =
)(
1+
Θ
k
n
(hay n
k+1
= O(f))
Bỏ bớt phân nửa:
f(n)
k
lan
n
k
nnnn
n
n
nf
=
++
≥++
≥
222
2
2
)(
2
222
II. Cách tính O lớn trong đoạn chương trình cụ thể:
1. Qui tắc cộng :
Nếu T1(n) và T2(n) là thời gian thực hiện của hai đoạn chương trình P1 và P2;
và T1(n)=O(f(n)), T2(n)=O(g(n) thì thời gian thực hiện của đoạn hai chương
trình đó nối tiếp nhau là T(n)=O(max(f(n),g(n)))
2. Qui tắc nhân :
Nếu T1(n) và T2(n) là thời gian thực hiện của hai đoạn chương trình P1và P2
và T1(n) = O(f(n)), T2(n) = O(g(n) thì thời gian thực hiện của đoạn hai đoạn
chương trình đó lồng nhau là T(n) = O(f(n).g(n))
3. Qui tắc tổng quát:
a. Phép gán, cin, cout : O(1)
2
Nhận xét:
Ký hiệu thường dùng f = O(g) khi muốn nói
)(gOf ∈
(đôi
khi dấu = lại gây hiểu nhầm)
Không dùng cách ghi O(g) = n
Nhận xét:
• O(cf(n)) = O(f(n))
• O(c) = O(1)
b. Các chuỗi lệnh tuần tự : Qui tắc cộng
c. Cấu trúc if : thời gian lớn nhất giữa các lệnh sau THEN và sau ELSE
d. Cấu trúc swich/case : thời gian lớn nhất trong các trường hợp case và
default (nếu có)
e. Cấu trúc lặp :
i. là tổng (trên tất cả các lần lặp) thời gian thực hiện thân vòng
lặp
ii. Nếu thời gian thực hiện thân vòng lặp không đổi => tích
của số lần lặp với thời gian thực hiện thân vòng lặp
4. Ví dụ :
void Bubble (int a[], int n)
{
int i, j, temp;
{1} for (i=1; i<n; i++)
{2} for (j=n; j<=i+1; j )
{3} if (a[j-1]>a[j])
{
{4} temp:=a[j-1];
{5} a[j-1]:=a[j];
{6} a[j]:=temp;
}
}
Cả ba lệnh đổi chỗ {4} {5} {6} tốn O(1) thời gian, do đó lệnh {3} tốn O(1).
Vòng lặp {2} thực hiện (n-i) lần, mỗi lần O(1) do đó vòng lặp {2} tốn
O((n-i).1)=O(n-i).
Vòng lặp {1} lặp (n-1) lần vậy độ phức tạp của giải thuật là:
3