Giáo trình Toán rời rạc
Trang 1II CÂY (TREE)
1 Các khái niệm cơ bản:
Định nghĩa: Đồ thị vô hướng, liên thông và không chứa chu trình đơn nào gọi là cây.
Vì cây không chứa chu trình đơn nên nó không thể có cạnh bội và vòng Vì vậy cây là một đồ thị đơn
Ví dụ:
(hình 1) Nếu một đồ thị vô hướng, không chứa chu trình đơn nào nhưng không liên thông thì đồ thị đó phải chứa các thành phần liên thông Ta sẽ gọi loại đồ thị đó là rừng
Ví dụ:
(Hình 2: Một rừng với 5 thành phần liên thông)
Định lý: Một đồ thị vô hướng là một cây nếu và chỉ nếu tồn tại một đường đi duy nhất
giữa mọi cặp đỉnh bất kì của nó
Chứng minh:
- Giả sử đồ thị vô hướng T là một cây Cho x và y là hai đỉnh bất kì của T Vì T liên thông nên phải có một đường đi α từ x đến y Nếu có một đường đi β (khác α) từ x đến y thì
cặp (α,β) sẽ tạo thành một chu trình đơn Mâu thuẩn với giả thiết T là một cây.
- Ngược lại giả sử đồ thị vô hướng T thỏa tính chất tồn tại một đường đi duy nhất giữa mọi cặp đỉnh bất kì của nó Ta suy ra được:
1 T liên thông
2 Nếu tồn tại một chu trình đơn trong T Chẳng hạn chu trình đơn chứa hai đỉnh x, y nào đó của T Khi đó ta có hai đường đi phân biệt tách ra được từ chu trình đơn nói trên và nối x với
y Mâu thuẩn với giả thiết về tính chất của T Vậy T không thể chứa chu trình đơn được
Vậy T là một cây
Trang 2QED Thuật ngữ:
Ta qui ước gọi cây không có đỉnh nào là cây rỗng (empty tree) Trong phần lớn các ứng
dụng về cây, một đỉnh đặc biệt của cây được gọi là gốc (roof | root) Khi đã xác đỉnh một đỉnh
của cây là gốc thì ta có thể xác định hướng cho từng cạnh như sau: Vì có một đường đi duy nhất từ gốc đến mọi đỉnh của đồ thị nên ta định hướng cho từng cạnh theo hướng từ gốc đi ra Đỉnh
nào từ đó không có một cạnh đi ra nữa thì gọi là một lá Các đỉnh của cây không phải là lá gọi là đỉnh trong
Nếu v là một đỉnh của cây T mà không phải là gốc thì cha (parent) của v là đỉnh (duy nhất) u sao cho cho có cạnh nối từ u đến v (có vẻ như gọi v là con (child) của u cũng hợp lí !) Các đỉnh có cùng một cha sẽ được gọi là các đỉnh anh em(siblings) Tổ tiên (ancestors) của
một đỉnh là mọi đỉnh trên đường đi từ gốc (kể cả gốc) đến đỉnh đó loại trừ đỉnh đó Nếu b là con của a thì đồ thị con của cây đó gồm b và mọi con cháu của b cùng tất cả các cạnh liên hệ
với các con cháu của b gọi là một cây con ứng với đỉnh a.
Cây mà mỗi đỉnh trừ các lá đều có đúng m con gọi là cây m-phân chính xác Cây 2-phân chính xác gọi là cây nhị 2-phân (binary tree) Trong cây nhị 2-phân thì trong hai đỉnh anh em bất kì sẽ có một đỉnh gọi là em bên trái (left sibling), đỉnh còn lại gọi là em bên phải (right sibling)
Định lý: Một cây có n đỉnh sẽ có (n-1) cạnh.
Chứng minh
(tầm thường)
QED
Định lý: Một cây m-phân chính xác với i đỉnh trong có mi+1 đỉnh.
Chứng minh:
Vì đỉnh nào trong số i đỉnh trong cũng có đúng m đỉnh con Suy ra cây có mi đỉnh không phải là đỉnh gốc Vậy tổng số đỉnh của cây m-phân chính xác làø mi+1.
QED
o Một số ứng dụng:
Cây được sử dụng trong rất nhiều lĩnh vực, đặc biệt là trong các mô hình liên quan đến cấu trúc tổ chức của các đối tượng đang được xét đến:
Trang 3o Hệ thống folder trong các máy tính:
Người ta tổ chức quản lí tài nguyên của hệ thống máy tính theo các folder Mỗi folder có thể chứa các folder con hoặc không Loại folder không thể chứa các folder con gọi là các file
Một Folder gốc chứa toàn bộ các folder khác gọi là Desktop Trong hình trên là một phần hình ảnh trích từ giao diện của Windows Explorer (MicroSoft Windows 98) Một nhánh của cây quản lí tài nguyên trên là:
Desktop
o Cây tìm kiếm nhị phân.
Bài toán tìm kiếm các mục thông tin trong một danh sách là một trong các bài toán quan trọng của tin học Thuật toán tìm kiếm sẽ có hiệu quả nhất khi các mục thông tin đó được sắp xếp có thứ tự Trong trường hợp các mục thông tin đó được sắp xếp theo các đỉnh của của
Trang 4một cây nhị phân thì mỗi đỉnh sẽ được gán cho một khóa sao cho khóa của mỗi đỉnh đều lớn hơn bất kì khóa nào của các đỉnh thuộc cây con bên trái của đỉnh đó và nhỏ hơn bất kì khóa nào của các đỉnh thuộc cây con bên phải của đỉnh đó
Giải thuật xây dựng các mục thông tin trên cây nhị phân có thể được phát biểu một cách đệ qui như sau:
Bắt đầu với một cây chỉ chứa một đỉnh (tức là gốc) Mục thứ nhất trong danh sách sẽ được gán làm khóa cho gốc đó Để bổ sung một mục thông tin mới, trước hết so sánh nó với các khóa của các đỉnh đã có trên cây, bắt đầu từ gốc đi sang trái nếu mục thông tin đó có khóa bé hơn khóa của khóa của đỉnh tương ứng khi đỉnh này có con bên trái, hoặc đi sang phải nếu mục thông tin đó có khóa lớn hơn khóa của khóa của đỉnh tương ứng khi đỉnh này có con bên phải Khi mục thông tin đó có khóa bé hơn khóa của đỉnh tương ứng và đỉnh đó không có con bên trái thì một con bên trái của đỉnh này được đưa vào (với khóa của mục thông tin này) Tương tự, khi mục thông tin đó có khóa lớn hơn khóa của đỉnh tương ứng và đỉnh đó không có con bên phải thì một con bên phải của đỉnh này được đưa vào (với khóa của mục thông tin này)
Ví dụ:
Ví dụ trên, theo thứ tự từ trái qua phải và từ trên xuống, cho thấy quá trình xây dựng cây nhị phân tìm kiếm cho các từ khóa Huong, Bang, Luu, Dung, Kiet, Hung, Quan, Thuy đã diễn ra như thế nào
Giải thuật sau đây cho phép ta định vị một mục thông tin nếu nó đã có mặt trong cây tìm kiếm nhị phân hoặc bổ sung một đỉnh mới có khóa là mục thông tin này nếu nó chưa có mặt trên cây này:
Trang 5Procedure BinarySearch (T: cây tìm kiếm nhị phân; X: khóa của mục thông tin);
{Đỉnh không có trong T có giá trị Null}
v:= Gốc của T
While (v ≠ Null) và (Khóa(v)≠X)
BEGIN
IF x < Khóa(v) THEN
IF (Con bên trái của v ≠ Null) THEN v := Con bên trái của v ELSE Thêm đỉnh mới u là con bên trái của v và gán v:= Null ELSE
IF (Con bên phải của v ≠ Null) THEN v:= Con bên phải của v ELSE Thêm đỉnh mới u là con bên phải của v và gán v:= Null END
IF (Gốc cuả T = Null) THEN Thêm đỉnh r vào cây và gán cho nó khóa X
ELSE IF (Khóa(v)≠x) THEN Gán cho đỉnh mới u khóa X
ELSE Return(v)
o Cây quyết định.
Cây có gốc, trong đó mỗi đỉnh tương ứng với một quyết định và mỗi cây con của cây này tương ứng với một phương án có thể có của một quyết định Những lời giải có thể có của bài toán tương ứng với các đường đi tới các lá
o Mã Huffman.
Giả sử ta muốn gởi một thông báo bao gồm một dãy các kí tự Trong mỗi thông báo các
kí tự là độc lập với nhau và xuất hiện với xác suất đã biết tại bất kì vị trí nào của thông báo Chẳng hạn, ta muốn gởi một thông báo chỉ gồm 5 kí tự a, b, c, d, e với xác suất xuất hiện lần lượt là 0.12, 0.4, 0.15, 0.08, 0.25 Ta muốn mã hóa các kí tự đó bằng mã nhị phân 0 và 1 sao cho không có mã hóa của kí tự nào là phần đầu (ie: trùng với) của mã hóa của các kí tự còn lại (Gọi là điều kiện tiền tố)
Ví dụ1 ta có hai cách mã hóa sau đây:
Kí tự Xác suất Code 1 Code 2
Với Code 1 để thông báo chuỗi bcd ta cần gởi đi 001 010 011 còn đối với Code 2 ta chỉ
cần gởi đi 1101001 (ngắn hơn!) Tuy nhiên khác với dùng bộ mã Code 1, ở nơi nhận ta không
thể xắt lát chuỗi 1101001 để được bảng rõ bcd nếu việc truyền nhận chưa kết thúc trừ khi Code
2 thỏa điều kiện đã nêu!
1 Ví dụ này dẫn từ: Data Structures and Algorithms - ALFRED V AHO; JOHN E HOPCROFT; JEFFREY D ULLMAN - Addison Wesley Publishing Company (Corrections April, 1987) - pp 94-98.
Trang 6Bài toán đặt ra là: Cho một tập hợp các kí tự và xác suất xuất hiện của chúng trong văn bản Tìm cách mã hóa bộ kí tự đó sao cho chúng thỏa điều kiện tiền tố và độ dài trung bình của mã là nhỏ nhất (tức là thời gian truyền của thông báo sẽ nhỏ nhất) Trong ví dụ trên Code
1 và code 2 đều thỏa điều kiện tiền tố nhưng độ dài trung bình của mã trong Code 1 là 3 trong khi độ dài trung bình của mã trong Code 2 chỉ có (3*0.12+2*0.4+2*0.15+3*0.08+2*0.25) = 2.2 Vậy Code 2 phải là tối ưu chưa? Nếu xem mỗi kí tự là một nút trong một cây có trọng số, ta sẽ xây dựng một bộ mã mà độ dài trung bình của bộ mã chỉ là 2.15 như sau:
Giải thuật Huffman:
Lấy u là kí tự có xác xuất xuất hiện nhỏ nhất
WHILE (còn kí tự chưa xét đến) DO
Begin Lấy v là kí tự có xác suất xuất hiện nhỏ nhất trong các kí tự còn lại
Tạo ra một nút mới x với con bên trái là u, con bên phải là v Thay u bởi x với trọng số là tổng trọng số của u và v
Xóa v End Quá trình xây dựng cây mã Huffman của bộ mã trong ví dụ trên:
Với cách mã hóa này ta được:
Trang 7Kí tự Xác suất Code 3
Độ dài trung bình của bộ mã là: (4*0.12+1*0.40+3*0.15+4*0.08+2*0.25)=2.15
Khi đó thông điệp bcd được truyền nhận là: 011011112
o Duyệt cây:
Xét biểu thức số học: (x -y)*z+(x-t)+x*z/t Biểu thức số học này có thể được biểu diễn
dưới dạng một cây nhị phân như sau:
Theo cách biểu diễn này thì
ta xử lý cây như sau: Nếu nút là toán hạng thì ta sẽ xác định giá trị toán hạng đó, nếu nút là toán tử thì
ta thực hiện phép toán tương ứng với giá trị của các cây con tương ứng với nút đó Ta thấy ngay rằng thứ tự xử lí các nút của cây là rất quan trọng vì lẽ ta không thể xử lý một nút nếu chưa xử lý các cây con tương ứng với nút đó Phép xử lí các nút của cây sao cho mỗi nút đều được “thăm” qua một lần (và chỉ một lần thôi!) một cách có hệ thống được gọi là phép duyệt cây
Giải thuật:
Sau đây là ba cách duyệt cây được định nghĩa một cách đệ qui:
Duyệt theo thứ tự trước NLR (Preorder traversal: Node - Left -Right)
- Thăm gốc
2 Trong trường hợp này độ dài của thông báo cũng không khác với trường hợp dùng bộ mã Code 2 Tuy
nhiên nên lưu ý là độ dài trung bình của bộ mã là độ dài tính theo xác suất, đúng với trường hợp độ dài của
thông báo là khá lớn (luật số lớn).
Trang 8- Duyệt cây con trái theo thứ tự trước.
- Duyệt cây con phải theo thứ tự trước
Duyệt theo thứ tự giữa LNR (Inorder traversal: Left - Node -Right)
- Duyệt cây con trái theo thứ tự giữa
- Thăm gốc
- Duyệt cây con phải theo thứ tự giữa
Duyệt theo thứ tự sau LRN (Postorder traversal: Left - Right-Node)
- Duyệt cây con trái theo thứ tự sau
- Duyệt cây con phải theo thứ tự sau
- Thăm gốc Khi gặp cây rỗng thì “thăm” có nghĩa là không làm gì cả
Sau đây là mã giả tương ứng với phép duyệt cây theo thứ tự giữa:
Procedure INORDER (T: Cây nhị phân)
IF T ≠ Null THEN
Begin
INORDER(Cây con bên trái của T)
In ra giá trị của nút T INORDER(Cây con bên phải của T) End
ELSE Return()
Mã giả đối với hai phép duyệt còn lại đề nghị người đọc tự xây dựng
Vậy đối với cây nhị phân tương ứng với biểu thức số học (x -y)*z+(x-t)+x*z/t thứ tự các
nút được thăm trong các phép duyệt là:
a) Theo NLR:
+ + * - x y z - x t / * x z t (Biểu thức dạng tiền tố.) b) Theo LNR:
x - y * z + x - t + x * z / t (Biểu thức dạng trung tố) c) Theo LRN:
x y - z * x t - + x z * t / + (Biểu thức dạng hậu tố) Dạng biểu thức trung tố là dạng hay dùng nhất trong toán học Tuy nhiên để tính biểu thức viết theo dạng này ta cần phải bổ sung vào biểu thức các cặp dấu ngoặc và phải qui định thứ tự ưu tiên thực hiện các toán tử:
* Các biểu thức con trong cặp ngoặc trong cùng được thực hiện trước tiên
* Các phép toán * và / có cùng mức ưu tiên
* Các phép toán + và - có cùng mức ưu tiên
* Trên một dãy các phép tính có cùng mức ưu tiên thì việc thực hiện các phép tính được thực hiện từ trái qua phải
Ví dụ:
Xử lí biểu thức dạng trung tố: 7 - 2 * 3 + 7 - 4 + 7 * 3 / 3
Việc bổ sung các cặp ngoặc để làm rõ thứ tự các cây con là cần thiết Chẳng hạn:
Trang 9(7 - 2) * 3 + (7 - 4) + 7 * 3 / 3 Kết quả sau các phép tính lần lượt là:
5*3 + 3 + 7*3/3
15 + 3 + 7*3/3
15 + 3 + 21 /3
15 + 3 + 7
18 + 7 25 Việc qui định thứ tự ưu tiên khi thực hiện các phép tính trong một biểu thức như vậy làm cho việc xử lí biểu thức trở nên hết sức nhiêu khê!
Dạng biểu thức hậu tố là dạng biểu thức mà việc thực hiện các phép tính dễ dàng hơn: Cứ đọc các kí hiệu (token) từ trái sang phải Khi nào gặp token là toán hạng thì ghi nhớ3 và đọc token kế tiếp Khi nào gặp token là toán tử thì thực hiện phép tính cho hai toán hạng vừa duyệt qua gần nhất tương ứng với toán tử đó Cách xử lí theo phương pháp này không cần bổ sung các cặp ngoặc lẫn qui định về mức ưu tiên của các toán tử
Ví dụ:
Xử lí biểu thức dạng hậu tố: 7 2 - 3 * 7 4 - + 7 3 * 3 / +
Ta lần lượt thực hiện (từ trái qua phải):
5 3 * được kết quả 15
Do quá trình xử lí dạng hậu tố thuận tiện như vậy nên đây là dạng biểu thức sẽ được dùng chủ yếu để lưu trữ các biểu thức số học và xử lí các biểu thức đó bên trong các máy tính
Có thể thấy ngay rằng xử lí biểu thức dạng tiền tố cũng tương tự như xử lí biểu thức dạng hậu tố chỉ khác ở chổ phải duyệt biểu thức từ phải sang trái
Phần trình bày trên cho thấy cách duyệt một cây nhị phân Trong cả ba cách đó ta đều
lần đến các nút của cây dọc theo chiều sâu của cây và do đó được gọi là phép duyệt theo chiều
sâu (Depth-First traversal) Trường hợp một đồ thị nói chung cũng đặt ra vấn đề tương tự như
vậy:
Procedure DepthFirstTraversal (G: Dothi; v:một nút của G)
IF (v≠Null) THEN
Begin Thăm(v);
FOR (Mỗi đỉnh u lân cận của v) DO
DepthFirstTraversal (G, u)
3 Để thực hiện việc ghi nhớ này thông thường người ta dùng cấu trúc dữ liệu kiểu STACK - Sinh viên sẽ được nghiên cứu về cấu trúc này trong môn Cấu trúc dữ liệu và giải thuật.
Trang 10End Else Return()
Giải thuật này đảm bảo thăm được tất cả các đỉnh của G với tới được từ v (ie: liên thông với v) Vậy giải thuật có thể dùng để xác định một đồ thị G có liên thông hay không và xác định được các thành phần liên thông của G
o Giải thuật quay lui (BackTracking):
Thuật toán quay lui liên quan đến việc tìm kiếm đường đi trên một đồ thị Thực chất
của phương pháp này là vét dần4 các khả năng có thể có ở mỗi bước tiếp cận với đường đi Ở mỗi bước tiếp cận ta sẽ đánh dấu đoạn đường vừa đi qua được Nếu ở bước nào đó bị dẫn vào ngỏ cụt thì ta đánh dấu vị trí đó và quay lại vị trí trước đó một bước để lựa chọn phương án khả
dĩ khác còn chưa xét đến
Hãy xét bài toán tìm đường đi trong một mê cung như sau:5
Mê cung được biểu diễn dưới dạng một đồ thị Mỗi nút là một ngã rẽ Các ngã rẽ được đánh chỉ số để dễ phân biệt Để tìm đường đi bắt đầu từ “Cửa vào” cứ mỗi lần đứng trước một ngã
rẽ chúng ta sẽ đánh dấu ngã rẽ đó và chọn một trong những con đường có khả năng đi đến ngã rẽ kế tiếp Thao tác
này luôn dẫn chúng
ta đến hoặc “Cửa ra” hoặc một ngã rẽ kế tiếp mà chưa đi qua hoặc dẫn chúng
ta đến một ngỏ cụt (là ngã rẽ mà không cho phép đi
tiếp) Nếu thao tác dẫn chúng ta đến một ngỏ cụt thì
chúng ta sẽ lùi lại ngãõ rẽ vừa đi qua trước đó và lại lựa chọn một trong số các ngã rẽ còn lựa chọn được từ ngã rẽ này
Như vậy để băng qua mê cung chúng ta cần phải lần lượt đi qua một con đường gồm một số các ngã rẽ:
x=(x0,x1,x2,x3, ,xn) trong đó x0=”Cửa vào” và “xn=”Cửa ra”
4 Hãy phân biệt thuật ngữ “vét dần” với thuật ngữ “vét cạn” Trong trường hợp này có khả năng ta chưa phải vét hết các con đường khi đã tìm được rồi đường đi thích hợp Mặt khác với một số điều kiện thích hợp có thể không cần xét đến một số ngã rẽ (loại bỏ nhánh cận).
5 Ví dụ này dẫn từ: Giải một bài toán trên máy tính như thế nào - HOÀNG KIẾM - Nxb Giáo dục (tái bản lần 2 - 2003) - Tập 2: Chương 4: Phương pháp thử sai - 1.3 Nguyên lí mê cung (từ trang 20 đến trang 28).