Professional Documents
Culture Documents
VÀ THUẬT TOÁN
1.1. Ví dụ mở đầu
Tham khảo tài liệu của PGS. TS. Nguyễn Đức Nghĩa
Ví dụ mở đầu
• Ví dụ: Nếu dãy đã cho là -2, 11, -4, 13, -5, 2 thì cần đưa ra
câu trả lời là 20 (là trọng lượng của dãy con 11, -4, 13)
Tham khảo tài liệu của PGS. TS. Nguyễn Đức Nghĩa
Thuật toán trực tiếp
• Thuật toán đơn giản đầu tiên có thể nghĩ để giải bài toán đặt ra
là: Duyệt tất cả các dãy con có thể
và tính tổng của mỗi dãy con để tìm ra trọng lượng lớn nhất.
• Trước hết nhận thấy rằng, tổng số các dãy con có thể của dãy đã
cho là
Tham khảo tài liệu của PGS. TS. Nguyễn Đức Nghĩa
Thuật toán trực tiếp
• Thuật toán này có thể cài đặt trong đoạn chương trình sau:
int maxSum = 0;
for (int i=0; i<n; i++) {
for (int j=i; j<n; j++) {
int sum = 0;
for (int k=i; k<=j; k++)
sum += a[k];
if sum > maxSum
maxSum = sum;
}
}
Tham khảo tài liệu của PGS. TS. Nguyễn Đức Nghĩa
Thuật toán trực tiếp
Tham khảo tài liệu của PGS. TS. Nguyễn Đức Nghĩa
Thuật toán nhanh hơn
a[k ] a[ j ] a[k ]
k i k i
• Nhận xét này cho phép rút bớt vòng lặp for trong cùng.
Tham khảo tài liệu của PGS. TS. Nguyễn Đức Nghĩa
Thuật toán nhanh hơn
Tham khảo tài liệu của PGS. TS. Nguyễn Đức Nghĩa
Thuật toán nhanh hơn
• Phân tích thuật toán. Ta lại tính số lần thực hiện phép cộng
và thu được kết quả sau:
n 1
n2 n
i 0
(n i ) n (n 1) ... 1
2 2
• Để ý rằng số này là đúng bằng số lượng dãy con. Dường như
thuật toán thu được là rất tốt, vì ta phải xét mỗi dãy con đúng 1
lần.
Tham khảo tài liệu của PGS. TS. Nguyễn Đức Nghĩa
Thuật toán đệ qui
Tham khảo tài liệu của PGS. TS. Nguyễn Đức Nghĩa
Thuật toán đệ qui
• Để tổ hợp lời giải, nhận thấy rằng chỉ có thể xảy ra một
trong 3 trường hợp:
– Dãy con lớn nhất nằm ở dãy con bên trái (nửa trái)
– Dãy con lớn nhất nằm ở dãy con bên phải (nửa phải)
– Dãy con lớn nhất bắt đầu ở nửa trái và kết thúc ở nửa phải (giữa).
• Do đó, nếu ký hiệu trọng lượng của dãy con lớn nhất ở
nửa trái là wL, ở nửa phải là wR và ở giữa là wM thì
trọng lượng cần tìm sẽ là
max(wL, wR, wM).
Tham khảo tài liệu của PGS. TS. Nguyễn Đức Nghĩa
Thuật toán đệ qui
• Việc tìm trọng lượng của dãy con lớn nhất ở nửa trái
(wL) và nửa phải (wR) có thể thực hiện một cách đệ qui
• Để tìm trọng lượng wM của dãy con lớn nhất bắt đầu ở
nửa trái và kết thúc ở nửa phải ta thực hiện như sau:
– Tính trọng lượng của dãy con lớn nhất trong nửa trái kết
thúc ở điểm chia (wML) và
– Tính trọng lượng của dãy con lớn nhất trong nửa phải bắt
đầu ở điểm chia (wMR).
– Khi đó wM = wML + wMR.
Tham khảo tài liệu của PGS. TS. Nguyễn Đức Nghĩa
Thuật toán đệ qui
• m – điểm chia của dãy trái, m+1 là điểm chia của dãy phải
Tính WML của dãy con Tính WMR của dãy con
lớn nhất trong nửa trái lớn nhất trong nửa phải
kết thúc tại am bắt đầu từ am+1
Tham khảo tài liệu của PGS. TS. Nguyễn Đức Nghĩa
Thuật toán đệ qui
• Để tính trọng lượng của dãy con lớn nhất ở nửa trái (từ
a[i] đến a[j]) kết thúc ở a[j] ta dùng thuật toán sau:
MaxLeft(a, i, j);
{
maxSum = -; sum = 0;
for (int k=j; k>=i; k--) {
sum = sum+a[k];
maxSum = max(sum, maxSum);
}
return maxSum;
}
Tham khảo tài liệu của PGS. TS. Nguyễn Đức Nghĩa
Thuật toán đệ qui
• Để tính trọng lượng của dãy con lớn nhất ở nửa phải (từ
a[i] đến a[j]) bắt đầu từ a[i] ta dùng thuật toán sau:
MaxRight(a, i, j);
{
maxSum = -; sum = 0;
for (int k=i; k<=j; k++){
sum = sum+a[k];
maxSum = max(sum, maxSum);
}
return maxSum;
}
Tham khảo tài liệu của PGS. TS. Nguyễn Đức Nghĩa
Thuật toán đệ qui
Tham khảo tài liệu của PGS. TS. Nguyễn Đức Nghĩa
Thuật toán đệ qui
Tham khảo tài liệu của PGS. TS. Nguyễn Đức Nghĩa
Thuật toán đệ qui
• Ta khẳng định rằng T(2k) = k.2k. Ta chứng minh bằng qui nạp
• Cơ sở qui nạp: Nếu k=0 thì T(20) = T(1) = 0 = 0.20.
• Chuyển qui nạp: Nếu k>0, giả sử rằng T(2k-1) = (k-1)2k-1 là
đúng. Khi đó
T(2k) = 2T(2k-1)+2k = 2(k-1).2k-1 + 2k = k.2k.
• Quay lại với ký hiệu n, ta có
T(n) = n log n .
• Kết quả thu được là tốt hơn thuật toán thứ hai !
Tham khảo tài liệu của PGS. TS. Nguyễn Đức Nghĩa
So sánh các thuật toán
Tham khảo tài liệu của PGS. TS. Nguyễn Đức Nghĩa
Thuật toán Quy hoạch động
Việc phát triển thuật toán dựa trên DP bao gồm 3 giai đoạn:
1. Phân rã: Chia bài toán cần giải thành những bài toán con nhỏ
hơn có cùng dạng với bài toán ban đầu.
2. Ghi nhận lời giải: Lưu trữ lời giải của các bài toán con vào một
bảng.
3. Tổng hợp lời giải: Lần lượt từ lời giải của các bài toán con
kích thước nhỏ hơn tìm cách xây dựng lời giải của bài toán
kích thước lớn hơn, cho đến khi thu được lời giải của bài toán
xuất phát (là bài toán con có kích thước lớn nhất).
Tham khảo tài liệu của PGS. TS. Nguyễn Đức Nghĩa
Thuật toán QHĐ
• Phân r·. Gäi si lµ trọng lượng cña d·y con lín nhÊt
trong d·y a1, a2, ..., ai , i = 1, 2, ..., n.
Râ rµng sn lµ gi¸ trÞ cÇn t×m.
• Tæng hîp lêi gi¶i.
– Tríc hÕt, ta cã
s1 = a1.
– Gi¶ sö i > 1 vµ sk lµ ®· biÕt víi k = 1, 2, ..., i-1. Ta cÇn tÝnh si lµ
trọng lượng của d·y con lín nhÊt cña d·y
Tham khảo tài liệu của PGS. TS. Nguyễn Đức Nghĩa
Thuật toán QHĐ
• Do dãy con lớn nhất của dãy này hoặc là có chứa phần tử ai hoặc là không
chứa phần tử ai, nên nó chỉ có thể là một trong hai dãy:
– Dãy con lớn nhất của dãy a1, a2, ..., ai-1
– Dãy con lớn nhất của dãy a1, a2, ..., ai kết thúc tại ai.
• Từ đó suy ra
si = max {si-1, ei}, i = 2, …, n.
trong đó ei là trọng lượng của dãy con lớn nhất của dãy a1, a2, ..., ai kết
thúc tại ai.
• Để tính ei, ta cũng có thể sử dụng công thức đệ qui sau:
– e1 = a1;
– ei = max {ai, ei-1 + ai}, i = 2, ..., n.
Tham khảo tài liệu của PGS. TS. Nguyễn Đức Nghĩa
Thuật toán QHĐ
MaxSub(a);
{
smax = a[1]; (* smax – trọng lượng cña d·y con lín nhÊt *)
maxendhere = a[1]; (* maxendhere – trọng lượng của dãy con lớn nhất kết thúc tại a[i] *)
imax = 1; (* imax - vÞ trÝ kÕt thóc cña d·y con lín nhÊt *)
for i = 2 to n {
u = maxendhere + a[i];
v = a[i];
if (u > v) maxendhere = u
else maxendhere = v;
if (maxendhere > smax)then {
smax := maxendhere;
imax := i;
}
}
}
Phân tích thuật toán:
Dễ thấy số phép toán cộng phải thực hiện trong thuật toán (số lần thực
hiện câu lệnh u = maxendhere + a[i];) là n.
Tham khảo tài liệu của PGS. TS. Nguyễn Đức Nghĩa
NỘI DUNG
1.1. Ví dụ mở đầu
Tham khảo tài liệu của PGS. TS. Nguyễn Đức Nghĩa
Khái niệm bài toán tính toán
• §Þnh nghÜa. Bµi to¸n tÝnh to¸n F lµ ¸nh x¹ tõ tËp c¸c x©u nhÞ ph©n ®é dµi
h÷u h¹n vµo tËp c¸c x©u nhÞ ph©n ®é dµi h÷u h¹n:
F : {0, 1}* {0, 1}*.
• VÝ dô:
– Mçi sè nguyªn x ®Òu cã thÓ biÓu diÔn díi d¹ng x©u nhÞ ph©n lµ c¸ch
viÕt trong hÖ ®Õm nhÞ ph©n cña nã.
– HÖ ph¬ng tr×nh tuyÕn tÝnh Ax = b cã thÓ biÓu diÔn díi d¹ng x©u lµ
ghÐp nèi cña c¸c x©u biÓu diÔn nhÞ ph©n cña c¸c thµnh phÇn cña ma
trËn A vµ vect¬ b.
– §a thøc mét biÕn P(x) = a0 + a1 x + ... + an xn hoµn toµn x¸c ®Þnh bëi
d·y sè n, a0, a1, ..., an, mµ ®Ó biÓu diÔn d·y sè nµy chóng ta cã thÓ sö
dông x©u nhÞ ph©n.
Tham khảo tài liệu của PGS. TS. Nguyễn Đức Nghĩa
Khái niệm thuật toán
• §Þnh nghÜa. Ta hiÓu thuËt to¸n gi¶i bµi to¸n ®Æt ra lµ mét thñ tôc x¸c ®Þnh
bao gåm mét d·y h÷u h¹n c¸c bíc cÇn thùc hiÖn ®Ó thu ®îc ®Çu ra cho
mét ®Çu vµo cho tríc cña bµi to¸n.
• ThuËt to¸n cã c¸c ®Æc trng sau ®©y:
– §Çu vµo (Input): ThuËt to¸n nhËn d÷ liÖu vµo tõ mét tËp nµo ®ã.
– §Çu ra (Output): Víi mçi tËp c¸c d÷ liÖu ®Çu vµo, thuËt to¸n ®a ra c¸c d÷ liÖu
t¬ng øng víi lêi gi¶i cña bµi to¸n.
– ChÝnh x¸c (Precision): C¸c bíc cña thuËt to¸n ®îc m« t¶ chÝnh x¸c.
– H÷u h¹n (Finiteness): ThuËt to¸n cÇn ph¶i ®a ®îc ®Çu ra sau mét sè h÷u h¹n
(cã thÓ rÊt lín) bíc víi mäi ®Çu vµo.
– §¬n trÞ (Uniqueness): C¸c kÕt qu¶ trung gian cña tõng bíc thùc hiÖn thuËt
to¸n ®îc x¸c ®Þnh mét c¸ch ®¬n trÞ vµ chØ phô thuéc vµo ®Çu vµo vµ c¸c kÕt qu¶
cña c¸c bíc tríc.
– Tæng qu¸t (Generality): ThuËt to¸n cã thÓ ¸p dông ®Ó gi¶i mäi bµi to¸n cã d¹ng
®· cho.
Tham khảo tài liệu của PGS. TS. Nguyễn Đức Nghĩa
Giải bài toán là gì?
What is Problem Solving?
• Problem solving
– Là quá trình đặt bài toán và phát triển chương trình máy tính để giải bài
toán đặt ra
Tham khảo tài liệu của PGS. TS. Nguyễn Đức Nghĩa
Độ phức tạp của thuật toán
• Đánh giá độ phức tạp tính toán của thuật toán là đánh giá lượng
tài nguyên các loại mà thuật toán đòi hỏi sử dụng. Có hai loại tài
nguyên quan trọng đó là thời gian và bộ nhớ. Trong giáo trình
này ta đặc biệt quan tâm đến đánh giá thời gian cần thiết để thực
hiện thuật toán mà ta sẽ gọi là thời gian tính của thuật toán.
• Thời gian tính phụ thuộc vào dữ liệu vào.
• §Þnh nghÜa. Ta gäi kÝch thíc d÷ liÖu ®Çu vµo (hay độ dài dữ
liệu vào) lµ sè bÝt cÇn thiÕt ®Ó biÓu diÔn nã.
• Ta sẽ tìm cách đánh giá thời gian tính của thuật toán bởi một hàm
của độ dài dữ liệu vào.
Tham khảo tài liệu của PGS. TS. Nguyễn Đức Nghĩa
Phép toán cơ bản
Tham khảo tài liệu của PGS. TS. Nguyễn Đức Nghĩa
Các loại thời gian tính
– Thêi gian nhiÒu nhÊt cÇn thiÕt ®Ó thùc hiÖn thuËt to¸n víi mäi bé d÷ liÖu
®Çu vµo kÝch thíc n. Thêi gian nh vËy sÏ ®îc gäi lµ thêi gian tÝnh tåi
nhÊt cña thuËt to¸n víi ®Çu vµo kÝch thíc n.
– Thêi gian trung b×nh cÇn thiÕt ®Ó thùc hiÖn thuËt to¸n trªn tËp h÷u h¹n c¸c
®Çu vµo kÝch thíc n. Thêi gian nh vËy sÏ ®îc gäi lµ thêi gian tÝnh trung
b×nh cña thuËt to¸n.
Tham khảo tài liệu của PGS. TS. Nguyễn Đức Nghĩa
NỘI DUNG
1.1. Ví dụ mở đầu
Tham khảo tài liệu của PGS. TS. Nguyễn Đức Nghĩa
Ký hiệu tiệm cận
Asymptotic Notation
, O
Tham khảo tài liệu của PGS. TS. Nguyễn Đức Nghĩa
Ký hiệu
Tham khảo tài liệu của PGS. TS. Nguyễn Đức Nghĩa
Ví dụ
10n2 - 3n = (n2) ?
• Với giá trị nào của các hằng số n0, c1, và c2 thì bất đẳng
thức trong định nghĩa là đúng?
Đối với hàm g(n) cho trước, ta ký hiệu O(g(n)) là tập các hàm
O(g(n)) = {f(n): tồn tại các hằng số dương c và n0 sao cho:
f(n) cg(n) với mọi n n0 }
Tham khảo tài liệu của PGS. TS. Nguyễn Đức Nghĩa
Ký hiệu
Đối với hàm g(n) cho trước, ta ký hiệu (g(n)) là tập các hàm
(g(n)) = {f(n): tồn tại các hằng số dương c và n0 sao cho:
cg(n) f(n) với mọi n n0 }
Tham khảo tài liệu của PGS. TS. Nguyễn Đức Nghĩa
Liên hệ giữa , , O
Tham khảo tài liệu của PGS. TS. Nguyễn Đức Nghĩa
Cách nói về thời gian tính
• Nói “Thời gian tính là O(f(n))” hiểu là: Đánh giá trong tình huống tồi
nhất (worst case) là O(f(n)). Thường nói: “Đánh giá thời gian tính
trong tình huống tồi nhất là O(f(n))”
• Nghĩa là thời gian tính trong tình huống tồi nhất được xác định
bởi một hàm nào đó g(n) (f(n))
• “Thời gian tính là (f(n))” hiểu là: Đánh giá trong tình huống tốt nhất
(best case) là (f(n)). Thường nói: “Đánh giá thời gian tính trong
tình huống tốt nhất là (f(n))”
– Nghĩa là thời gian tính trong tình huống tốt nhất được xác định
bởi một hàm nào đó g(n) (f(n))
Tham khảo tài liệu của PGS. TS. Nguyễn Đức Nghĩa
Ví dụ
• Mọi thuật toán sắp xếp đều đòi hỏi duyệt qua tất
cả các phần tử, vì thế bài toán sắp xếp có thời gian
(n) trong tình huống tốt nhất.
Tham khảo tài liệu của PGS. TS. Nguyễn Đức Nghĩa
Ký hiệu tiệm cận trong các đẳng thức
Tham khảo tài liệu của PGS. TS. Nguyễn Đức Nghĩa
Sự tương tự giữa
so sánh các hàm số và so sánh các số
fg ab
f (n) = O(g(n)) a b
f (n) = (g(n)) a b
f (n) = (g(n)) a = b
Tham khảo tài liệu của PGS. TS. Nguyễn Đức Nghĩa
Liên hệ với khái niệm giới hạn
Tham khảo tài liệu của PGS. TS. Nguyễn Đức Nghĩa
Ví dụ
A B
• 5n2 + 100n 3n2 + 2
• log3(n2) log2(n3)
Tham khảo tài liệu của PGS. TS. Nguyễn Đức Nghĩa
Ví dụ
A B
• 5n2 + 100n 3n2 + 2 A (B)
A (n2), n2 (B) A (B)
• log3(n2) log2(n3)
Tham khảo tài liệu của PGS. TS. Nguyễn Đức Nghĩa
Ví dụ
A B
• 5n2 + 100n 3n2 + 2 A (B)
A (n2), n2 (B) A (B)
Tham khảo tài liệu của PGS. TS. Nguyễn Đức Nghĩa
Ví dụ
A B
• 5n2 + 100n 3n2 + 2 A (B)
A (n2), n2 (B) A (B)
Tham khảo tài liệu của PGS. TS. Nguyễn Đức Nghĩa
Ví dụ
• Với mỗi cặp hàm sau đây, hoặc f(n) = O(g(n)), f(n) = Ω(g(n)),
hoặc f(n) = Θ(g(n)). Hãy xác định quan hệ nào là đúng.
– f(n) = log n2; g(n) = log n + 5 f(n) = (g(n))
– f(n) = n; g(n) = log n2 f(n) = (g(n))
– f(n) = log log n; g(n) = log n f(n) = O(g(n))
– f(n) = n; g(n) = log2 n f(n) = (g(n))
– f(n) = n log n + n; g(n) = log n f(n) = (g(n))
– f(n) = 10; g(n) = log 10 f(n) = (g(n))
– f(n) = 2n; g(n) = 10n2 f(n) = (g(n))
– f(n) = 2n; g(n) = 3n f(n) = O(g(n))
Tham khảo tài liệu của PGS. TS. Nguyễn Đức Nghĩa
Cận trên và cận dưới
Upper Bound and Lower Bound
• Định nghĩa (Upper Bound). Cho bài toán P, ta nói cận trên
cho thời gian tính của P là O(g(n)) nếu để giải P tồn tại thuật
toán giải với thời gian tính là O(g(n)) .
• Định nghĩa (Lower Bound). Cho bài toán P, ta nói cận dưới
cho thời gian tính của P là (g(n)) nếu mọi thuật toán giải P
đều có thời gian tính là (g(n)) .
• Định nghĩa. Cho bài toán P, ta nói thời gian tính của P là
(g(n)) nếu P có cận trên là O(g(n)) và cận dưới là (g(n)) .
Tham khảo tài liệu của PGS. TS. Nguyễn Đức Nghĩa
Một số lớp thuật toán
Tham khảo tài liệu của PGS. TS. Nguyễn Đức Nghĩa
NỘI DUNG
1.1. Ví dụ mở đầu
Tham khảo tài liệu của PGS. TS. Nguyễn Đức Nghĩa
Mô tả thuật toán: giả ngôn ngữ
• Để mô tả thuật toán có thể sử dụng một ngôn ngữ lập trình nào
đó. Tuy nhiên điều đó có thể làm cho việc mô tả thuật toán trở
nên phức tạp đồng thời rất khó nắm bắt.
• Vì thế, để mô tả thuật toán người ta thường sử dụng giả ngôn
ngữ (pseudo language), trong đó cho phép vừa mô tả thuật
toán bằng ngôn ngữ đời thường vừa sử dụng những cấu trúc
lệnh tương tự như của ngôn ngữ lập trình.
• Dưới đây ta liệt kê một số câu lệnh chính được sử dụng trong
giáo trình để mô tả thuật toán.
Tham khảo tài liệu của PGS. TS. Nguyễn Đức Nghĩa
Mô tả thuật toán: phỏng ngôn ngữ
Tham khảo tài liệu của PGS. TS. Nguyễn Đức Nghĩa
Mô tả thuật toán: giả ngôn ngữ
while condition do
dãy câu lệnh
endwhile;
Tham khảo tài liệu của PGS. TS. Nguyễn Đức Nghĩa
Mô tả thuật toán: phỏng ngôn ngữ
repeat
Câu lệnh case:
dãy câu lệnh;
until condition; Case
cond1: stat1;
for i=n1 to n2 [step d] cond2: stat2;
dãy câu lệnh; .
.
endfor; .
condn: stat n;
• Vào-Ra endcase;
read(X); /* X là biến đơn hoặc mảng */
print(data) hoặc print(thông báo)
Tham khảo tài liệu của PGS. TS. Nguyễn Đức Nghĩa
Mô tả thuật toán: giả ngôn ngữ
• Ví dụ: Thuật toán tìm phần tử lớn nhất trong mảng A(1:n)
Function max(A(1:n))
begin
datatype x; /* để giữ giá trị lớn nhất tìm được */
integer i;
x=A[1];
for i=2 to n do
if x < A[i] then
x=A[i];
endif
endfor ;
return (x);
end max;
Tham khảo tài liệu của PGS. TS. Nguyễn Đức Nghĩa
Mô tả thuật toán: giả ngôn ngữ
Procedure swap(x, y)
begin
temp=x;
x = y;
y = temp;
end swap;
Tham khảo tài liệu của PGS. TS. Nguyễn Đức Nghĩa
NỘI DUNG
1.1. Ví dụ mở đầu
Tham khảo tài liệu của PGS. TS. Nguyễn Đức Nghĩa
Các kỹ thuật cơ bản
phân tích độ phức tạp của thuật toán
• Cấu trúc tuần tự. Gi¶ sö P vµ Q lµ hai ®o¹n cña thuËt to¸n, cã
thÓ lµ mét c©u lÖnh nhng còng cã thÓ lµ mét thuËt to¸n con.
Gäi Time(P), Time(Q) lµ thêi gian tÝnh cña P vµ Q t¬ng øng.
Khi ®ã ta cã
Quy t¾c tuÇn tù: Thêi gian tÝnh ®ßi hái bëi “P; Q”, nghÜa lµ P
thùc hiÖn tríc, tiÕp ®Õn lµ Q, sÏ lµ
Time(P; Q) = Time(P) + Time(Q) ,
hoÆc trong ký hiÖu Theta:
Time(P; Q) = (max(Time(P), Time(Q)).
Tham khảo tài liệu của PGS. TS. Nguyễn Đức Nghĩa
Vòng lặp for
for i =1 to m do P(i);
t(i)
i 1
Tham khảo tài liệu của PGS. TS. Nguyễn Đức Nghĩa
Ví dụ: Tính số Fibonaci
• Nếu coi các phép toán số học đòi hỏi thời
function Fibiter(n) gian là hằng số c, và không tính đến chi phí
begin tổ chức vòng lặp for thì thời gian tính của
i=0; j=1; hàm trên là (n).
• Do (công thức Muavre)
for k=2 to n do k k
begin 1 1 5 1 5
fk
5 2 2
j= j+i;
i= j-i; suy ra số bit biểu diễn fk là (k). Do đó thời
end; gian tính của một lần lặp là (k). Vậy thời
gian tính của Fibiter là:
Fibiter= j;
n n
end; n(n 1)
( n 2 )
c.k c k c
k 1 k 1 2
Tham khảo tài liệu của PGS. TS. Nguyễn Đức Nghĩa
Phân tích vòng lặp While và Repeat
• Cần xác định một hàm của các biến trong vòng lặp sao
cho hàm này có giá trị giảm dần trong quá trình lặp. Khi
đó
– Để chứng minh tính kết thúc của vòng lặp ta chỉ cần chỉ ra
giá trị của hàm là số nguyên dương.
– Còn để xác định số lần lặp ta cần phải khảo sát xem giá trị
của hàm giảm như thế nào.
• Việc phân tích vòng lặp Repeat được tiến hành tương tự
như phân tích vòng lặp While.
Tham khảo tài liệu của PGS. TS. Nguyễn Đức Nghĩa
Ví dụ: Tìm kiếm nhị phân (Binary Search)
Tham khảo tài liệu của PGS. TS. Nguyễn Đức Nghĩa
Phân tích Binary-Search
• Gọi d= j-i+1. d – số phần tử của mảng cần tiếp tục khảo sát
• Ký hiệu i, j, i*, j* theo thứ tự là giá trị của i, j trước và sau khi
thực hiện lần lặp. Khi đó
– Nếu x < T[k], thì i*=i, j*=(i+j) div 2 - 1, vì thế d*=j*- i*+1
= (i+j) div 2 - 1- i + 1 (i+j)/2 - i < (j - i + 1)/2 = d/2.
– Nếu x > T[k], thì j*=j, i*=(i+j) div 2 + 1, vì thế d*=j*-
i*+1 = j - (i+j) div 2 j -(i+j-1)/2 - i = (j - i + 1)/2 = d/2.
– Nếu x = T[k], thì d* = 1 còn d 2
Tham khảo tài liệu của PGS. TS. Nguyễn Đức Nghĩa
Binary-Search (tiếp)
Tham khảo tài liệu của PGS. TS. Nguyễn Đức Nghĩa
Các mô tả khác của thuật toán Binary Search
Tham khảo tài liệu của PGS. TS. Nguyễn Đức Nghĩa
Ví dụ: FibIter
function Fibiter(n)
begin
i:=0; j:=1; Câu lệnh đặc
for k:=1 to n do trưng
begin • Số lần thực hiện câu lệnh
j:= j+i; đặc trưng là n.
i:= j-i;
• Vậy thời gian tính của thuật
end;
toán là O(n)
Fibiter:= j;
end;
Tham khảo tài liệu của PGS. TS. Nguyễn Đức Nghĩa
Ví dụ: Thuật toán 1 ví dụ mở đầu
Tham khảo tài liệu của PGS. TS. Nguyễn Đức Nghĩa
Ví dụ: Sắp xếp
• Nhận xét: Khi thuật toán đòi hỏi thực hiện nhiều vòng lặp lồng nhau, thì có
thể lấy câu lệnh nằm trong vòng lặp trong cùng làm câu lệnh đặc trưng.
• Tuy vậy, cũng cần hết sức thận trọng khi sử dụng cách lựa chọn này.
• Ví dụ. Sắp xếp kiểu nhốt chim vào chuồng (Pigeonhole Sorting).
Sắp xếp n số nguyên dương có giá trị nằm trong khoảng 1..s.
Đầu vào: T[1..n]: mảng chứa dãy cần sắp xếp.
Đầu ra: T[1..n]: mảng chứa dãy được sắp xếp không giảm.
Thuật toán bao gồm 2 thao tác:
– Đếm chim: Xây dựng mảng U[1..s], trong đó phần tử U[k] đếm số
lượng số có giá trị là k trong mảng T.
– Nhốt chim: Lần lượt nhốt U[1] con chim loại 1 vào U[1] lồng đầu tiên,
U[2] con chim loại 2 vào U[2] lồng tiếp theo, ...
Tham khảo tài liệu của PGS. TS. Nguyễn Đức Nghĩa
Sắp xếp kiểu nhốt chim
procedure Pigeonhole-Sorting;
begin
(* đếm chim *)
for i:=1 to n do inc(U[T[i]]);
(* Nhốt chim *)
i:=0;
for k:=1 to s do
while U[k]<>0 do
begin
i:=i+1;
T[i]:=k;
U[k]:=U[k]-1;
end;
end;
Tham khảo tài liệu của PGS. TS. Nguyễn Đức Nghĩa
Sắp xếp kiểu nhốt chim
• Nếu chọn câu lệnh bất kỳ trong vòng lặp while làm câu lệnh
đặc trưng, thì rõ ràng với mỗi k, câu lệnh này phải thực hiện
U[k] lần. Do đó số lần thực hiện câu lệnh đặc trưng là
s
U [k ] n
k 1
Tham khảo tài liệu của PGS. TS. Nguyễn Đức Nghĩa
Sắp xếp kiểu nhốt chim
Khi đó s = n2. Rõ ràng thời gian tính của thuật toán không phải
là O(n), mà phải là O(n2).
• Lỗi ở đây là do ta xác định câu lệnh đặc trưng chưa đúng, các
câu lệnh trong thân vòng lặp while không thể dùng làm câu lệnh
đặc trưng trong trường hợp này, do có rất nhiều vòng lặp rỗng
(khi U[k] = 0).
• Nếu ta chọn câu lệnh kiểm tra U[k] <> 0 làm câu lệnh đặc
trưng thì rõ ràng số lần thực hiện nó sẽ là n + s. Vậy thuật toán
có thời gian tính O(n+s) = O(n2).
Tham khảo tài liệu của PGS. TS. Nguyễn Đức Nghĩa
Chương 2: Thuật toán đệ quy
6 Tổng kết
Định nghĩa
Các hàm đệ qui được xác định bởi số nguyên không âm n theo sơ đồ
Bước cơ sở (Basic step) : Xác định giá trị hàm tại thời điểm n = 0
hay f (0)
Bước đệ qui (Recursive step) : Cho giá trị của hàm f (k) tại k ≤ n
đưa ra qui tắc tính giá trị của f (n + 1).
Pn
VD3 : Định nghĩa đệ qui tổng sn = i=1 ai
s 1 = a1
sn = sn−1 + an
Định nghĩa
Tập hợp cũng có thể được xác định đệ qui theo sơ đồ tương tự như hàm
đệ qui
Bước cơ sở : Định nghĩa tập cơ sở.
Bước đệ qui : Xác định các qui tắc để sản sinh tập mới từ các tập
đã có.
6 Tổng kết
Phương pháp chia-để-trị đc trình bày trong thủ tục đệ qui sau đây
Procedure D-and-C(n)
if n ≤ n0 then
Giải bài toán một cách trực tiếp
else
Chia bài toán thành a bài toán con kích thước n/b;
for (mỗi bài toán trong a bài toán con) do D-and-C(n/b) endfor
Tổng hợp lời giải của a bài toán con để thu được lời giải của
bài toán gốc;
endif
End
Phương pháp chia để trị đc trình bày trong thủ tục đệ qui (tiếp)
Các thông số quan trọng của thuật toán
n0 kích thước nhỏ nhất của bài toán con (còn gọi là neo đệ qui). Bài
toán con với kích thước n0 sẽ được giải trực tiếp.
a - số lượng bài toán con cần giải.
b - liên quan đến kích thước của bài toán con được chia.
6 Tổng kết
Ví dụ 1 : Tính n!
Hàm f(n) = n! được định nghĩa đẹ qui như sau
Bước cơ sở : f(0) = 0! = 1
Bước đệ qui : f(n) = n f(n-1), với n>0
Hàm đệ qui viết bằng ngôn ngữ C
int Fact(int n){
if(n==0) return 1;
else return n*Fact(n-1);
}
Ví dụ 2 : Tính số Fibonacci
Dãy số Fibonacci đc định nghĩa như sau :
Bước cơ sở : F(0) = 1, F(1) = 1;
Bước đệ qui : F(n) = F(n-1) + F(n-2) với n ≥ 2
Hàm đệ qui viết bằng ngôn ngữ C
int FibRec(int n){
if(n<=1) return 1;
else return FibRec(n-1) + FibRec(n-2);
}
h1 = 1,
h2 = 2h1 + 1, ...
hn = 2hn−1 + 1, n ≥ 2
hn = 2 n − 1
6 Tổng kết
6 Tổng kết
Vậy để chừng minh tính đúng đắn của thuật toán đệ qui thông thường ta
sử dụng qui nạp toán học. Ngược lại, cách chứng minh bằng đệ qui cũng
thường là cơ sở để xây dựng nhiều thuật toán đệ qui.
VD1 Chứng minh Fact(n) = n! vậy ta sẽ chứng minh bằng qui nạp
toán học
Bước cơ sở qui nạp : Ta có Fact(0) = 1 = 0!
Bước chuyển qui nạp : Giả sử Fact(n-1) cho giá trị của (n-1)! ta phải
chứng minh Fact(n) cho giá trị n!. Thật vậy, do giá trị trả lại của
Fact(n)
n ∗ Fact(n − 1) ⇒
|{z} n ∗ (n − 1)! = n!
theo giả thiết qui nạp
Chứng minh tính đúng đắn của thuật toán đệ qui (tiếp)
VD2 : Cho mặt phẳng trên đó vẽ n đường thẳng. Chứng minh mệnh
đề sau bằng qui nạp : P(n) luôn có thể tô các phần được chia bởi n
đường thẳng bởi chỉ hai mầu : xanh và đỏ (Xem chứng minh trong
sách).
Mã giả giải thuật tô hai mầu mặt phẳng như sau
Procedure PaintColor(n,A,B)
<xét đường thẳng n>
<phân chia các phần mặt phẳng thành hai tập A,B>
if (n=1) then
A ← Xanh; B ← Đỏ;
else
PaintColor(n-1,A,B)
<đảo mầu các phần mặt phẳng thuộc A>
endif
End
Trịnh Anh Phúc ( Bộ môn Khoa Học Máy Tính, ViệnCấu
CNTT
trúc&dữTT,
liệu Trường
và giải thuật
Đại Học Bách Khoa
NgàyHà9 Nội.
tháng) 7 năm 2021 37 / 38
Tổng kết
Tổng kết
Rõ ràng không có cách biểu diễn dữ liệu chung cho dữ liệu trừu tượng
A B
Khi vẽ cấu trúc dữ liệu sử dụng con trỏ, để thể hiện ô A trỏ sang ô B, ta
sẽ sử dụng mũi tên hướng từ A đến B.
Ví dụ : Để khai báo con trỏ ptr trong C trỏ đến ô có kiêu dữ liệu cho
trước tên là celltype
celltype *ptr
Mảng
Mảng là dữ liệu trừu tượng
Tập các giá trị : tập các cặp < index, value > trong đó với mỗi giá trị
của index có một giá trị từ tập các giá trị.
Các thao tác cơ bản :
I Khởi tạo
I Chèn phần tử
I Xóa bỏ một phần tử
Ví dụ
# include <stdio.h>
# include <conio.h>
int main(){
int one[] = {0,1,2,3,4};
int *ptr; int rows = 5;
/* in địa chỉ của mảng một chiều dùng con trỏ */
int i; ptr = one;
for(i=0;i<rows;i++)
printf("%8u %5d",ptr+i,*(ptr+i));
}
Tuy vậy, khi dùng hai trình dịch DEVC và TC lại cho kết quả khác nhau vì
kích thước của dữ liệu int lần lượt là : 4, 2.
Ví dụ :
Danh sách các sinh viên được sắp theo tên
Danh sách các cầu thủ có số áo tăng dần
Xóa bỏ phần tử
Mã giả của thao tác loại phần tử tại vị trí p trong danh sách L
Procedure Delete(p,L)
1 if (p>L.last + 1) or (p<1) then <Báo lỗi vị trí p không tồn tại>
2 else
3 L.last ← L.last - 1
4 for q ← p to L.last do /* Đẩy các phần tử dưới p lên 1 vị trí */
5 L.elements[q] ← L.elements[q+1]
6 endfor
7 endif
End
Cài đặt danh sách tuyến tính : dùng danh sách móc nối (linked list)
Nhược điểm của việc sử dụng mảng là
Lưu trữ hay bổ sung một phần tử tốn kém thời gian.
Lãng phí khoảng bộ nhớ rỗng không dùng đến.
Phải duy trì một khoảng không gian lưu trữ liên tục trong bộ nhớ.
Để khắc phục nó ta có thể dùng danh sách móc nối sử dụng con trỏ
(pointer), ta xét cách tổ chức móc nối sau
Danh sách móc nối đơn (singly linked list)
Danh sách nối kép (doubly linked list)
element pointer
Có một ô đặc biệt gọi là ô header để trỏ ra ô chứa phần tử đều tiên trong
danh sách, ô này không chứa phần tử nào cả.
pointer
Ngược lại, ô cuối cùng trong danh sách lại có con trỏ trỏ vào giá trị NULL.
element NULL
a1 a2 an null
Mối nối chỉ ra địa chỉ bộ nhớ của nút tiếp theo trong danh sách
Prev
... Inf Inf ... null
Inf
TempNode
Trịnh Anh Phúc ( Bộ môn Khoa Học Máy Tính, ViệnCấu
CNTT
trúc&dữTT,
liệu Trường
và giải thuật
Đại Học Bách Khoa
NgàyHà
13Nội.
tháng
) 7 năm 2021 24 / 71
Danh sách
Danh sách móc nối đơn : thao tác xóa
ElementType Delete(PointerType *Prev){
ElementType X;
PointerType *TempNode;
TempNode = Prev->Next; Prev->Next = Prev->Next->Next;
X = TempNode->Inf;
free(TempNode);
return X
}
Prev
... Inf Inf ... null
Inf
TempNode
Trịnh Anh Phúc ( Bộ môn Khoa Học Máy Tính, ViệnCấu
CNTT
trúc&dữTT,
liệu Trường
và giải thuật
Đại Học Bách Khoa
NgàyHà
13Nội.
tháng
) 7 năm 2021 25 / 71
Danh sách
Danh sách móc nối đơn : chèn một nút vào đầu danh sách
PointerType *InsertToHead(PointerType *First, ElementType X){
PointerType *TempNode;
TempNode = (PointerType *) malloc(sizeof(PointerType));
TempNode->Inf = X;
TempNode->Next = First;
First = TempNode;
return First;
}
First
Inf Inf ... null
Inf
TempNode
Trịnh Anh Phúc ( Bộ môn Khoa Học Máy Tính, ViệnCấu
CNTT
trúc&dữTT,
liệu Trường
và giải thuật
Đại Học Bách Khoa
NgàyHà
13Nội.
tháng
) 7 năm 2021 26 / 71
Danh sách
Danh sách móc nối đơn : chèn một nút vào cuối danh sách
PointerType *InsertToLast(PointerType *First, ElementType X){
PointerType *NewNode; PointerType *TempNode;
NewNode = (PointerType *)malloc(sizeof(PointerType));
NewNode->Inf = X; TempNode = First;
while(TempNode->Next!=NULL)
TempNode = TempNode->Next;
TempNode->Next = NewNode;
return First;
}
First TempNode
Inf ... Inf
NewNode
Inf null
Trịnh Anh Phúc ( Bộ môn Khoa Học Máy Tính, ViệnCấu
CNTT
trúc&dữTT,
liệu Trường
và giải thuật
Đại Học Bách Khoa
NgàyHà
13Nội.
tháng
) 7 năm 2021 27 / 71
Danh sách
Danh sách móc nối đơn : xóa nút đầu danh sách
PointerType *DeleteHead(PointerType *First){
PointerType *TempNode;
TempNode = First->Next;
free(First);
return TempNode;
}
First
Inf ... null
Danh sách móc nối đơn : kiểm tra danh sách rỗng
int IsEmpty(PointerType *First)
{
return !First;
}
Danh sách móc nối đơn : in ra tất cả các phần tử trong danh sách
void Print(PointerType *First){
PointerType *TempNode;
TempNode = First; int count = 0;
while(TempNode){
printf("%6d",TempNode->Inf); count++;
TempNode = TempNode->Next;
}
printf("\n");
}
Cài đặt ngăn xếp sử dụng con trỏ : Hủy ngăn xếp
void StackDestroy(Stack *s){
while(!StackEmpty(s)){
StackPop(s);
}
free(s);
}
Cài đặt ngăn xếp sử dụng con trỏ : các hàm bổ trợ
int StackEmpty(const Stack *s){
return (s->top==NULL);
}
int StackFull(){
printf("\n Khong con bo nho");
getch();
return 1;
}
Cài đặt ngăn xếp sử dụng con trỏ : đẩy phần tử vào ngăn xếp
int StackPush(Stack *s, float *item){
StackNode *node;
node = (StackNode *)malloc(sizeof(StackNode));
if(node==NULL){
StackFull();
return 1;
}
node->item = item;
node->next = s->top;
s->top = node;
return 0;
}
Cài đặt ngăn xếp sử dụng con trỏ : lấy phần tử từ ngăn xếp
float StackPop(Stack *s){
float item;
StackNode *node;
if(StackEmpty(s)){
printf("\n Ngan xep rong");
return NULL;
}
node = s->stop;
item = node->item;
s->top = node->next;
free(node);
return item;
}
3
7 7
3 3 3
5 5 5 5
Ứng dụng 2 : Bài toán tính giá trị biểu thức số học
Thông thường các công thức toán học được biểu diễn dạng trung tố (infix
notation), trình tự thực hiện các phép toán trong đó được ưu tiên bởi dấu
ngoặc hay các phép toán - nhân chia trước cộng trừ sau.
Ví dụ, công thức số học trung tố
(25 − 14) ∗ 2 + 65 = 87
25 14 − 2 ∗ 65+
Ta cũng có thể dùng ngăn xếp để tính giá trị biểu thức hậu tố này
Ứng dụng 2 : Bài toán tính giá trị biểu thức số học (tiếp)
Giải thuật tính giá trị biểu thức hậu tố sử dụng ngăn xếp như sau - giả
thuyết ta đã có biểu thức hậu tố cho trước
1 Duyệt biểu thức dạng hậu tố từ trái qua phải
2 Nếu gặp số hạng thì đẩy nó vào ngăn xếp
3 Nếu găp phép toán (+,-,*,/) thì thực hiện phép toán với hai số hạng
được lấy ra đầu ngăn xếp rồi đẩy kết quả lại vào ngăn xếp
4 Tiếp tục duyệt hết biểu thức cho đến khi ngăn xếp chỉ còn giá trị duy
nhất, đây chính là kết quả của biểu thức.
Ứng dụng 2 : Bài toán tính giá trị biểu thức số học (tiếp)
Minh họa ngăn xếp khi tính biểu thức dạng hậu tố 25 14 − 2 ∗ 65+ khi
duyệt từ trái sang phải
Số hạng 25 được đẩy vào ngăn xếp
Số hạng 14 được đẩy vào ngăn xếp
Với toán hạng - thì lấy hai số hạng 25-14 = 11 sau đó đẩy lại vào
ngăn xếp
14
25 25 11
Ứng dụng 2 : Bài toán tính giá trị biểu thức số học (tiếp tục)
Minh họa ngăn xếp khi tính biểu thức dạng hậu tố 25 14 − 2 ∗ 65+ khi
duyệt từ trái sang phải
Số hạng 2 được đẩy vào ngăn xếp
Với toán hạng * thì lấy hai số hạng 11*2 = 22 sau đó lại đẩy vào
ngăn xếp
Số hạng 65 được đẩy vào ngăn xếp
Với toán hạng + thì ta lấy hai số hạng 22+65 = 87 sau đó lại đẩy
vào ngăn xếp (kết thúc duyệt biểu thức dạng hậu tố)
2 65
11 22 22 87
Ứng dụng 3 : Chuyển biểu thức dạng trung tố về dạng hậu tố (tiếp)
Minh họa ngăn xếp khi chuyển biểu thức dạng trung tố (25 − 14) ∗ 2 + 65
sang dạng hậu tố, duyệt từ trái qua phải
Gặp dấu mở ngoặc (, nạp nó vào ngăn xếp
Gặp số hạng 25, đưa ra tức thì ⇒ 25
Gặp dấu - đưa vào ngăn xếp
Gặp số hạng 14, đưa ra tức thì ⇒ 25 14
-
( (
Ứng dụng 3 : Chuyển biểu thức dạng trung tố về dạng hậu tố (tiếp
tục)
Minh họa ngăn xếp khi chuyển biểu thức dạng trung tố (25 − 14) ∗ 2 + 65
Gặp dấu đóng ngoặc ), đẩy - ra khỏi ngăn xếp cho đến khi gặp dấu
mở ngoặc ) ⇒ 25 14 -
Gặp dấu * thì đẩy vào ngăn xếp
Găp số 2 thì đưa ra tức thì ⇒ 25 14 -2
Gặp dấu + thì đẩy vào ngăn xếp, đưa * ra do * có thứ tự ưu tiên cao
hơn ⇒ 25 14 -2*
-
( * -
Ứng dụng 3 : Chuyển biểu thức dạng trung tố về dạng hậu tố (tiếp
tục)
Minh họa ngăn xếp khi chuyển biểu thức dạng trung tố (25 − 14) ∗ 2 + 65
Gặp số hạng 65 thì đưa ra tức thì ⇒ 25 14 - 2*65
Đã duyệt hết và lấy hết phép toán trong ngăn xếp đưa ra ⇒ 25 14 -
2*65+
f r
Q
Cấu hình bình thường
r f
Q
Cấu hình xoay vòng tròn
Cài đặt hàng đợi bằng danh sách móc nối (tiếp)
Các toán tử được khai báo trong ngôn ngữ C như sau :
// Các hàm thực hiện các toán tử
queue *create();
int isEmpty(queue *q);
int size(queue *q);
void enqueue(queue *q, node *newNode);
node *dequeue(queue *q);
// In ra các phần tử trong hàng đợi
void print(queue *q)
Cài đặt hàng đợi bằng danh sách móc nối (tiếp)
Các toán tử với mã nguồn C tương ứng
queue *create(){
queue *q;
q = (queue *)malloc(sizeof(queue));
if(q==NULL) return NULL;// Không còn bộ nhớ
q->front = NULL; q->rear = NULL;
return q;
}
Cài đặt hàng đợi bằng danh sách móc nối (tiếp)
Các toán tử với mã nguồn C tương ứng
int isEmpty(queue *q){
return ((q->front==NULL)&&(q->rear==NULL));
}
int size(queue *q){
queue *ptr=q->front; int count=0;
while(ptr!=NULL){
ptr = ptr->next; count++;
}
return count;
}
Cài đặt hàng đợi bằng danh sách móc nối (tiếp)
Các toán tử với mã nguồn C tương ứng
void enqueue(queue *q, node *newNode){
/* trong trường hợp ta đã dùng hàm malloc để tạo newNode */
if(!isEmpty()){/* nối vào đuôi hàng đợi */
q->rear->next = *newNode;
q->rear = *newNode;
}
else
{/*nút dữ liệu đầu tiên của hàng đợi*/
q->rear = *newNode;
q->front = *newNode;
}
}
Cài đặt hàng đợi bằng danh sách móc nối (tiếp)
Các toán tử với mã nguồn C tương ứng
node *dequeue(queue *q){
node *ptr = q->front;
if(ptr!=NULL){/* có nút dữ liệu trong hàng đợi */
q->front = q->front->next;
if(q->front==NULL) /* là nút dữ liệu cuối cùng */
q->rear = NULL;
ptr->next = NULL;/* ko trỏ kế tiếp vào front nữa */
}
return ptr;/* trả lại con trỏ chưa free bộ nhớ */
}
n ← n × 10 + giá trị ký tự số
Phân biệt được dữ liệu và cấu trúc dữ liệu (ô dữ liệu + liên kết)
Hiểu được ý nghĩa và các phép toán của các cấu trúc dữ liệu : danh
sách, ngăn xếp, hàng đợi
Các ứng dụng của danh sách, ngăn xếp, hàng đợi
r1 r2 ... rk
T1 T2 ... Tk
b c d
e f g h
i j
Chú thích : Nút gốc mầu xanh thẫm, nút lá mầu xanh lá cây còn nút
trong mầu trắng.
Trịnh Anh Phúc ( Bộ môn Khoa Học Máy Tính, ViệnCấu
CNTT
trúc&dữTT,
liệu Trường
và giải thuật
Đại Học Bách Khoa
NgàyHà
13Nội.
tháng
) 7 năm 2021 10 / 59
Cây
Các nút cùng cha gọi là các nút anh&em. Trong hình là ba nút b,c,d có
cùng nút cha là a, được đánh dấu bởi hình elíp đỏ.
b c d
e f g h
i j
b c d
e f g h
i j
Chú thích : Vòng tròn bao mầu đỏ chỉ ra một cây con của nút gốc a.
Trịnh Anh Phúc ( Bộ môn Khoa Học Máy Tính, ViệnCấu
CNTT
trúc&dữTT,
liệu Trường
và giải thuật
Đại Học Bách Khoa
NgàyHà
13Nội.
tháng
) 7 năm 2021 12 / 59
Cây
Đường đi trên cây từ nút gốc a đến các nút lá i và h (gạch nét dứt mầu
đỏ). Đường thứ nhất {a,b,f,i} và đường thứ hai là {a,d,h}.
b c d
e f g h
i j
a Mức=1
b c d
e f g h
i j
k Mức=5
a Bậc=3
b c d
e f g h Bậc=0
i j
Cây có thứ tự
Thứ tự của các nút trên cây : Các nút con của một nút thường được
sắp xếp theo thứ tự từ trái sang phải
a a
b c c b
Như vậy rõ ràng hai cây trên khác nhau do thứ tự nút con của nút a là
khác nhau. Hay nút b được xếp trước nút c trong cây bên trái, trong khi
nó được xếp sau nút c trong cây bên phải.
r1 r2 ... rk
...
T1 T2 Tk
r1 r2 ... rk
...
T1 T2 Tk
r1 r2 ... rk
...
T1 T2 Tk
r1 r2 ... rk
...
T1 T2 Tk
+ -
a c
b a
Biểu thức : (a+b)*(a-c)
4 Tổng kết
a a
b
b b
c d
c d c d
e e e
b c
d g
e f
b c
d g
e f
h
i k
j
b a
a
c b c
b c
d e d
d
e f e f
b c
d g
e f
h
i k
j
a b c d e f g h i j k
Biểu diễn cây nhị phân hoàn chỉnh với mảng (tiếp)
Các phép toán trên cây nhị phân hoàn chỉnh biểu diễn bằng mảng A[i] với
chỉ số i = 1, 2, · · · , n với n là số nút trên cây.
Biểu diễn cây nhị phân bằng con trỏ với phép toán tạo nút mới
treeNode *makeTreeNode(int x){
treeNode *newNode = NULL;
newNode = (treeNode*)malloc(sizeof(treeNode));
if(newNode==NULL){
printf("Het bo nho \n");
exit(1);
}else{
newNode->left = NULL;
newNode->right = NULL;
newNode->Item = x;
}
return newNode;
}
Biểu diễn cây nhị phân bằng con trỏ với phép toán đếm số nút
int countNodes(treeNode *tree){
if(tree==NULL) return 0;
else{
int ld = countNodes(tree->left);
int rd = countNodes(tree->right);
return 1+ld+rd;
}
}
Biểu diễn cây nhị phân bằng con trỏ với phép toán tính độ sâu
int depth(treeNode *tree){
if(tree==NULL) return 0;
int ld = depth(tree->left);
int rd = depth(tree->right);
return 1+(ld>rd ? ld : rd);
}
Biểu diễn cây nhị phân bằng con trỏ với phép toán loại bỏ cây
void freeTree(treeNode *tree){
if(tree=NULL) return;
freeTree(tree->left);
freeTree(tree->right);
free(tree);
return;
}
Minh họa ba phép duyệt (trước, giữa, sau) của cây nhị phân dưới đây
Duyệt trước : a,b,d,h,i,e,j,k,c,f,g
Duyệt giữa : h,d,i,b,j,e,k,a,f,c,g
Duyệt sau : h,i,d,j,k,e,b,f,g,c,a
b c
d g
e f
h
i k
j
4 Tổng kết
Ứng dụng 1 : cây nhị phân biểu thức - expression binary tree
Cây biểu thức nhị phân trong đó :
mỗi nút lá chứa một toán hạng
mỗi nút trong chứa phép toán một ngôi
Các cây con trái và các cây con phải chứa hai vế của biểu thức phép
toán hai ngôi.
Các mức thể hiện mức độ ưu tiên của phép toán :
Mức của các nút trên cây cho biết trình tự thực hiện các phép toán
(lưu ý, không sử dụng dấu ngoặc trong cây nhị phân biểu thức)
Các phép toán mức cao được thực hiện trước
Phép toán ở gốc được thực hiện sau cùng
Ứng dụng 1 : cây nhị phân biểu thức - expression binary tree (tiếp)
Duyệt cây nhị phân biểu thức có thể cho ta các biểu thức dưới dạng
trung tố, tiền tố và hậu tố
Duyệt cây biểu thức theo thứ tự trước (preorder) cho ta biểu thức
dưới dạng tiền tố.
Duyệt cây biểu thức theo thứ tự giữa (inorder) cho ta biểu thức dưới
dạng trung tố.
Duyệt cây biểu thức theo thứ tự sau (postorder) cho ta biểu thức
dưới dạng hậu tố.
+ -
a c
b a
x<11?
S
Đ
x<5? x<17?
S S
Đ Đ
S S S S
Đ Đ Đ Đ
Mã Huffman (tiếp)
Mỗi mã phi tiền tố có thể biểu diễn bởi một cây nhị phân T mà mỗi lá
của nó tương ứng với một chữ cái và cành của nó được gán cho một trong
hai số 1 và 0. Mã của một chữ cái c là một dãy nhị phân gồm các số gán
cho các cạnh trên đường đi từ gốc đến là tương ứng với c.
Bài toán
Tìm cách mã hóa tối ưu, tức cây nhị phân T làm tối thiểu hóa tổng độ
dài có trọng số X
B(T ) = f (c) × depth(c)
c∈C
Mã Huffman (tiếp)
X
B(T ) = f (c) × depth(c)
c∈C
Lý do để tối thiểu hóa hàm trên đơn giản là làm giảm số bit cần mã hóa
khi truyền thông tin bảng chữ C lên đường truyền.
Mã Huffman (tiếp)
Thuật toán
Ý tưởng thuật toán : chữ cái có tầm suất nhỏ hơn cần được gán cho lá có
khoảng cách đến gốc là lớn hơn. Ngược lại, chữ cái có tần suất xuất hiện
lớn hơn cần gần được gán gần nút gốc hơn.
Mã Huffman (tiếp) x,y ← 2 chữ cái có tần xuất nhỏ nhất trong Q;
/*Tạo nút p là nút cha của x,y với */
f(p) ← f(x) + f(y);
Q ← Q loại bỏ {x,y};Q ← Q thêm p;
endfor
End
Mã xây dựng theo thuật toán trên gọi là mã Huffman. Giải thuật có thởi
gian cài là O(n log n) vởi n là số chữ cái trong C.
Mã Huffman (tiếp)
Ví dụ về mã Huffman
Cho xâu chữ không dấu : "Đai hoc Bach khoa Hanoi" có được bảng chữ
có cả tần số xuất hiện giảm dần của ký tự như sau
Ký tự ’a’ ’ ’ ’h’ ’o’ ’c’ ’i’ ’Đ’ ’K’ ’H’ ’B’ ’n’
f(c) 4 4 3 3 2 2 1 1 1 1 1
23 1
9 0 14
1 1
0 0
5 4 6 8
1 1 1 1
0 0 0 0
2 3 ’c’ ’i’ ’h’ ’o’ ’a’ ’ ’
1 1
0 0
2021-07-13
9 0 14
1 1
Mã Huffman 2
0
3 ’c’
0
’i’ ’h’
0
’o’ ’a’
0
’ ’
Mã Huffman (tiếp) 0
1
0
1
Mã hóa Huffman cho mỗi ký tự (nút lá) chính là mã nhị phân đường đi
từ gốc đến lá. Các nút trong có số chỉ chính tần số của nút p được thêm
vào tập Q trong giải thuật mã hóa Huffman. Mã hóa của mỗi ký tự là dãy
bit {0,1} nằm trên đường đi từ gốc đến lá, tương ứng từng ký tự.
Mã Huffman (tiếp)
Giải mã Huffman
Procedure Huffman-Decode(B)
/* Trong đó B là xâu nhị phân mã hóa văn bản theo mã hóa Huffman */
<Khởi động p là gốc của cây Huffman>
while <chưa đạt đến kết thúc của xâu B> do
x ← bit tiếp theo trong xâu B
if (x=0) then p ← con trái của p else p ← con phải của p endif
if (p là nút lá) then
<hiển thị ký tự tương ứng nút lá>
<đặt lại p làm gốc của cây Huffman>
endif
endwhile
End
FibRec(3) FibRec(2)
FibRec(1) FibRec(0)
Cây gọi đệ qui tính số Fibonnacci với lần gọi đầu FibRec(4)
Định nghĩa đệ qui và các thuật ngữ cấu trúc dữ liệu cây
Các phép toán trên cấu trúc dữ liệu cây
Cách biểu diễn CTDL cây trong máy tính
Cây nhị phân định nghĩa và tính chất
Các ứng dụng của cây nhị phân