You are on page 1of 11

MÔN TIN HỌC - MÃ CHẤM: Ti06

CÂY TIỀN TỐ (TRIE TREE)

Tác giả: Hà Đại Tôn, Trường THPT Chuyên Hạ Long – Quảng Ninh

A. MỞ ĐẦU
Trie tree hay còn gọi là cây tiền tố, được sử dụng rất nhiều trong các bài toán so sánh chuỗi.
Định nghĩa đơn giản và dễ hiểu nhưng nó có ứng dụng khá rộng.
Bài toán: Cho tập S gồm N xâu 𝑆1 , 𝑆2 , … , 𝑆𝑁 và tập 𝑋 gồm 𝑀 xâu 𝑋1 , 𝑋2 , … , 𝑋𝑀 . Các xâu có độ
dài không vượt quá 𝐿. Hãy kiểm tra xem mỗi xâu thuộc tập X có xuất hiện trong tập 𝑆 hay
không?
Nhận xét: Ta có thể giải bài toán này bằng cách duyệt tất cả các xâu thuộc S để kiểm tra với từng
xâu X. Độ phức tạp của thuật toán nàylà khoảng 𝑂(𝑁. 𝑀. 𝐿). Thuật toán này chỉ có thể chạy
được với các bộ dữ liệu nhỏ. Sau đây chúng ta sẽ xét một cấu trúc dữ liệu rất hiệu quả để giải bài
toán này với độ phức tạp 𝑂(𝑚𝑎𝑥(𝑀, 𝑁). 𝐿). Đó là cây tiền tố Trie Tree.

B. NỘI DUNG
2. Cấu trúc dữ liệu Trie Tree
a) Cách xây dựng Trie Tree:
Để hiểu rõ về cấu trúc cũng như cách xây dựng cây Trie ta sẽ xét một ví dụ cụ thể. Giả sử
ta có xâu S = ‘abc’. Cây trie bắt đầu được xây dựng như sau:
- Lúc đầu khởi tạo cây Trie chỉ có 1 nút duy nhất và ta sẽ bắt đầu xây dựng cây từ nút này

Hình 1. Cây ban đầu chỉ có duy nhất nút 1


- Ta xét kí tự đầu tiên của xâu S, đó là kí tự ‘a’. Do lúc này cây Trie mới chỉ có duy nhất một
nút khởi đầu nên ở bước này ta sẽ thêm vào cây Trie nút 2, đông thời tạo một cạnh có
hướng từ nút 1 đến nút 2 với trọng số là ‘a’.
Hình 2. Cây được thêm nút 2.
- Lúc này ta đang đứng ở nút 2. Xét kí tự tiếp theo của xâu S, kí tự ‘b’. Ta đang đứng ở nút
2 và từ nút 2 cũng không hề có một đường đi nào khác nên ta tiếp tục bổ sung thêm vào
cây Trie nút 3, đồng thời tạo cạnh có hướng từ nút 2 đến nút 3 với trọng số là ‘b’. Tương
tự với kí tự ‘c’. Ta sẽ được cây Trie như sau:

Hình 3. Cây Trie tree của tập xâu {‘abc’}.


Như vậy nếu ta đi theo đường đi 1 → 2 → 3 → 4 rồi ghép tất cả các trọng số trên đường đi này
lại ta sẽ được xâu S. Nút 4 là nút kết thúc việc tạo xâu nên ta cần đặt một dấu hiệu ở đây, cho
biết nút 4 là kết thúc của 1 đường đi tạo xâu.
Bây giờ ta sẽ thêm vào xâu S = ‘abd’. Vậy cây Trie sẽ thay đổi như thế nào? Ta lại tiếp tục xuất
phát từ nút gốc của cây, nút 1. Xét kí tự đầu tiên của xâu S. Ta thấy từ nút 1 đang đứng ta có thể
đi đến đỉnh 2 theo cạnh có trọng số bằng kí tự thứ nhất của xâu S. Vì vậy ta sẽ đi đến đỉnh 2 mà
không tạo thêm nút cho cây Trie. Tiếp tục xét đến kí tự tiếp theo, kí tự ‘b’. Lúc này từ nút 2, ta
cũng có thể đi theo 1 cạnh có trọng số bằng kí tự thứ 2 của xâu S, vì vậy ta sẽ đi theo cạnh này
đến nút 3. Xét đến kí tự cuối cùng của xâu S, kí tự ‘d’. Lúc này từ nút 3, ta chỉ có duy nhất 1
cạnh nối, tuy nhiên cạnh nối này lại có trọng số khác với kí tự đang xét (kí tự ‘d’). Vì vậy ta phải
tạo thêm nút 5 và một cạnh có hướng từ nút 3 đến nút 5 với trọng số là ‘d’. Lúc này nút 5 cũng
cần đặt một dấu hiệu kết thúc. Cây trie của ta sẽ có dạng như sau:
Hình 4. Cây Trie tree của tập xâu {‘abc’, ‘abd’}
b) Tìm kiếm trên Trie Tree:
Sau khi đã xây dựng xong Trie Tree theo cách trên ta sẽ có thể kiểm tra xem 1 xâu thuộc tập X
có xuất hiện trong tập S hay không. Cách làm cũng tương tự như việc xây dựng cây Trie. Ta xuất
phát từ nút 1. Sau đó xét từng kí tự của xâu cần kiểm tra. Nếu có cạnh nối từ đỉnh đang đứng với
trọng số bằng kí tự đang xét thì ta tiếp tục đi theo cạnh đó đến nút tiếp theo, ngược lại, nếu ở 1 kí
tự nào đó ta không thể đi được tiếp thì xâu này không xuất hiện trong tập S. Khi đã đi đến hết
xâu cần kiểm tra. Giả sử ta đang đứng ở nút p, nếu ở nút p có dấu hiệu kết thúc thì xâu này có
xuất hiện trong S và ngược lại. Như vậy độ phức tạp của việc tìm kiếm này chỉ là O(L).
3. Cài đặt thuật toán
Const
fi= 'input.txt';
fo= 'output.txt';
maxn= 5000;
Type
Trie = record
next: array['a'..'z'] of longint; {kiểm tra xem có đường đi với trọng số ‘char’ hay
không. Nếu next[‘char’]= 0 là không có cạnh nối với trọng số ‘char’ còn nếu
next[‘char’]<> 0 thì có cạnh trọng số ‘char’ đến nút next[‘char’] }
finish: boolean; { dấu hiệu kết thúc }
end;
Var
n, m, top: longint;
s, x: array[1..maxn] of ansistring;
tree: array[1..2000000] of trie;
Procedure reads;
Var
i: longint;
Begin
readln(n,m);
for i:= 1 to n do readln(s[i]);
for i:= 1 to m do readln(x[i]);
End;
Procedure Insert(x: ansistring); // Thêm xâu X vào Trie Tree
Var
p, i: longint;
Begin
p:= 1; // Bắt đầu từ nút 1
for i:= 1 to length(x) do
begin
I if tree[p].next[x[i]]= 0 then // nếu không có đường đi
begin
inc(top); // tăng số nút lên 1
tree[p].next[x[i]]:= top; // thêm cạnh có hướng
end;
p:= tree[p].next[x[i]]; // đi đến nút tiếp theo
end;
tree[p].finish:= true; // thêm dấu hiệu kết thúc
End;
Function Check(x: ansistring): boolean; // Kiểm tra xâu X
Var
p, i: longint;
Begin
p:= 1; // xuất phát từ nút 1
for i:= 1 to length(x) do
begin
if tree[p].next[x[i]]= 0 then exit(false); // không có đường
p:= tree[p].next[x[i]]; // đến nút tiếp theo
end;
exit(tree[p].finish); // kiểm tra dấu hiệu kết thúc
End;
Procedure Process;
Var
i, j: longint;
Begin
top:= 1;
for i:= 1 to 4*maxn do tree[i].finish:= false;
for i:= 1 to n do Insert(s[i]);
for i:= 1 to m do
if Check(x[i]) then writeln('YES') else writeln('NO');
End;
Begin
assign(input,fi); reset(input);
assign(output,fo); rewrite(output);
reads;
process;
close(input); close(output);
End.
4. Một số bài tập áp dụng.
Bài 1. Cho tập S gồm N xâu S1, S2,…SN và tập X gồm M xâu X1, X2,…XM. Các xâu có độ
dài không vượt quá 100. Hãy kiểm tra xem mỗi xâu thuộc tập X có xuất hiện trong tập S
hay không?
Input:
- Dòng đầu tiên chứa 2 số nguyên N, M (1 ≤ N, M ≤ 5000)
- N dòng tiếp theo, mỗi dòng chứa một xâu thuộc tập S
- M dòng tiếp theo, mỗi dòng chứa một xâu thuộc tập X
Output:
- Ghi ra M dòng, dòng thứ i ghi ra thông báo ‘YES’ nếu xâu Xi xuất hiện trong tập
hợp S, ngược lại ghi ra thông báo ‘NO’
Example:
Input Output
23 NO
abcacba
abcbbbb YES
abcbbba YES
abcbbbb
abcacba
Test và solution: http://www.mediafire.com/download/xdm17g17i9j8wea
Bài 2. Bạn được cho một danh sách các số điện thoại. Một danh sách được gọi là hợp lệ
nếu như không có số điện thoại nào là tiền tố của một số điện thoại khác ở trong dãy. Nhiệm
vụ của bạn là xác định xem danh sách đã cho có hợp lệ hay không?
Input
- Dòng đầu tiên chứa số nguyên t (1 ≤ t ≤ 40) là số lượng bộ test
- Mỗi bộ test được ghi trên N+1 dòng: Dòng đầu tiên chứa số nguyên N (1 ≤ N ≤
10000). N dòng sau mỗi dòng ghi một xâu (có độ dài không vượt quá 10) là một số
điện thoại trong danh sách
Output
- Ghi ra t dòng tương ứng với kết quả của t bộ test: in ra “YES” nếu danh sách là hợp
lệ và in ra “NO” trong trường hợp ngược lại
Example
Input Output
2 NO
3 YES
911
97625999
91125426
5
113
12340
123440
12345
98346
Test và solution: http://www.mediafire.com/download/4wme1du3j5jbh5y
Bài 3. Bessie định dẫn đàn bò đi trốn. Để đảm bảo bí mật, đàn bò liên lạc với nhau bằng
cách tin nhắn nhị phân.
Từng là một nhân viên phản gián thông minh, John đã thu được M (1 ≤ M ≤ 50,000) tin
nhắn mật, tuy nhiên với tin nhắn i John chỉ thu được bi (1 ≤ bi ≤ 10,000) bit đầu tiên.
John đã biên soạn ra 1 danh sách N (1 ≤ N ≤ 50,000) các từ mã hóa mà đàn bò có khả năng
đang sử dụng. Thật không may, John chỉ biết được cj (1 ≤ cj ≤ 10,000) bit đầu tiên của từ
mã hóa thứ j.
Với mỗi từ mã hóa j, John muốn biết số lượng tin nhắn mà John thu được có khả năng là
từ mã hóa j này. Tức là với từ mã hóa j, có bao nhiêu tin nhắn thu được có phần đầu giống
với từ mã hóa j này. Việc của bạn là phải tính số lượng này.
Tổng số lượng các bit trong dữ liệu đầu vào (tổng các bi và cj) không quá 500000.
Input:
- Dòng 1: 2 số nguyên: M và N
- Dòng 2..M+1: Dòng i+1 mô tả tin nhắn thứ i thu được, đầu tiên là bi sau đó là bi bit
cách nhau bởi dấu cách, các bit có giá trị 0 hoặc 1.
- Dòng M+2..M+N+1: Dòng M+j+1 mô tả từ mã hóa thứ j, đầu tiên là cj sau đó là cj
bit cách nhau bởi dấu cách.
Output:
- Dòng 1..N: Dòng j: Số lượng tin nhắn mà có khả năng là từ mã hóa thứ j
Example:
Input Output
45 1
3010 3
11 1
3100 1
3110 2
10
11
201
501001
211

0 chỉ có khả năng là 010 -> 1 tin nhắn. 1 chỉ có khả năng là 1, 100, hoặc 110 -> 3 tin
nhắn. 01 chỉ có thể là 010 -> 1 tin nhắn. 01001 chỉ có thể là 010 -> 1 tin nhắn. 11 chỉ có
thể là 1 hoặc 110 -> 2 tin nhắn.
Hướng dẫn: Trước tiên ta sẽ Insert tất cả các xâu trong tập M xâu thu được từ vào Trie Tree
Ta sẽ sử dụng 2 mảng như sau:
 sl[p] là số lượng các từ kết thúc tại nút p
 sl2[p] là số lượng các từ đi qua nút p
Với mỗi từ j trong tập N xâu, ta sẽ làm như sau:
 Khởi tạo số lượng từ có thể mã hóa được từ xâu j: cnt== 0
 Duyệt trên cây Trie, với mỗi đỉnh p trong quá trình duyệt ta sẽ cộng kết quả thêm lượng là
sl[p]: cnt+= sl[p]. Đây chính là bước tính số lượng các từ có chiều dài ≤ chiều dài từ j mà
mã hóa được từ j
 Trong quá trình duyệt, nếu ta không thể đi được hết xâu j thì ngay lập tức đưa ra kết quả
là cnt
Nếu xâu j có thể đi được hết trên Trie Tree, ta cần phải đếm thêm các từ có độ dài lớn hơn độ
dài xâu j mà có phần đầu trùng với xâu j, số lượng các xâu này chính là: sl2[p]- sl[p] với p là
nút cuối cùng trong quá trình duyệt xâu j trên Trie Tree.
Test và solution: http://www.mediafire.com/download/wthl1f8ctm9520f
Bài 4. Andrew và Alex là những nhà sáng tạo trò chơi, ngày hôm nay họ sáng tạo ra trò
chơi mới với xâu dành cho hai người
Giả sử ta có một tập hợp gồm n xâu khác rỗng. Với mỗi lượt chơi, 2 người chơi cùng nhau
xây dựng 1 xâu, lúc đầu xâu được khởi tạo rỗng. Mỗi người chơi sẽ đi theo lượt, đến lượt
của mình, người chơi phải thêm vào cuối xâu một kí tự duy nhất sao cho xâu mới tạo thành
là tiền tố của ít nhất 1 xâu trong tập hợp n xâu ban đầu. Một người sẽ thua nếu như người
đó không thể thực hiện được việc thêm kí tự vào xâu.
Andrew và Alex quyết định sẽ chơi k ván. Người thua cuộc ở ván thứ i sẽ là người đi trước
trong ván thứ (i+1). Người chiến thắng chung cuộc sẽ là người thắng ở ván đấu thứ k (tức
là ván đấu cuối cùng) chứ không phải là người thắng nhiều ván hơn. Giả sử nếu hai người
đều chơi theo chiến thuật tối ưu thì sau k ván ai sẽ là người chiến thắng?
Input:
- Dòng đầu tiên chứa 2 số nguyên n và k (1 ≤ n ≤ 105; 1 ≤ k ≤ 109).
- Mỗi dòng trong số n dòng tiếp theo, mỗi dòng chứa một xâu khác rỗng nằm trong
tập hợp xâu ban đầu. Tổng số kí tự của tất cả các xâu trong tập hợp không vượt quá
105, xâu chỉ bao gồm các chữ cái tiếng Anh in thường
Output:
- Gồm một dòng duy nhất: ghi ra ‘First’ nếu người đi trước ở ván đầu tiên chiến thắng
chung cuộc, ghi ra ‘Second’ trong trường hợp người đi sau ở ván đầu tiên chiến
thắng chung cuộc
Example:
Input Output
23 First
a
b
31 First
a
b
c
12 Second
Ab

Hướng dẫn:
Đây là một bài toán trò chơi. Một trong những kĩ thuật cơ bản nhất để giải các bài toán trò chơi
đó là xác định các vị trí thắng và vị trí thua trong bài toán. Và điểm mấu chốt của bài toán này
chính là xác định các vị trí đó.
Trước hết, ta sẽ xây dựng cây tiền tố Trie cho tập gồm n xâu ban đầu. Sau khi đã có cây Trie, ta
mô tả lại các bước đi của trò chơi như sau: lúc đầu người đi trước sẽ đứng ở đỉnh 1 của cây Trie,
sau đó đi theo 1 cạnh của cây để đến 1 đỉnh khác, sau đó người thứ 2 lại tiếp tục như vậy, đến khi
nào một người chơi đi đến một nút lá trên cây, tức là người sau không thể đi tiếp được nữa thì
người đó sẽ là người chiến thắng của ván đấu.
Đến đây ta sẽ định nghĩa vị trí thắng, vị trí thua như sau: “ Một nút trên cây là vị trị có thể thắng
nếu người chơi đứng ở nút đó với cách chơi tối ưu luôn dành chiến thắng, một nút trên cây là vị
trí có thể thua nếu người chơi đứng ở nút đó, với cách chơi của mình sẽ luôn thua “. Vậy tại sao
ta lại dùng từ “có thể”? Bởi vì người chiến thắng là người thắng được ván cuối cùng chứ không
phải là người thắng nhiều ván hơn, vậy nên có thể có người chơi CỐ TÌNH THUA ở một số ván
để giành được chiến thắng ở ván cuối cùng. Ta có thêm 1 số định nghĩa như sau: “ Một nút là nút
thắng HOÀN TOÀN, nếu từ nút đó ta chỉ có thể đi được đến các nút thua HOÀN TOÀN. Một nút
là nút thua HOÀN TOÀN nếu từ nút đó ta chỉ có thể đi được đến các nút thắng HOÀN TOÀN. Một
nút là nút tiềm năng nếu từ đó ta có thể đến được cả nút THẮNG HOÀN TOÀN và THUA HOÀN
TOÀN. Một nút là nút không xác định nếu từ nút đó ta chỉ đến được các nút tiềm năng”. Như vậy
với mỗi nút trên cây Trie, ngoài mảng next, ta có thêm 2 biến boolean là win(W), lose(L). Nếu
(W== True && L== False) thì nút là thắng HOÀN TOÀN, nếu (W==F && L== T) thì nút là
thua HOÀN TOÀN, nếu (W== T && L== F) thì nút là tiềm năng, đặc điểm của nút không xác
định ta sẽ xét phía dưới ( W= win, L= lose, T= true, F= false)
Như vậy, từ những định nghĩa ban đầu như trên ta thấy rằng, mọi nút lá của Trie đều là nút thua
HOÀN TOÀN. Bây giờ ta cần tiến hành xây dựng trạng thái cho tất cả các nút. Ở đây ta sử dụng
quy hoạch động trên cây như sau: Nếu nút u có thuộc tính (W== F) thì nút cha trực tiếp của u
phải có thuộc tính (W== T). Nếu nút u có thuộc tính (L==F) thì nút cha trực tiếp của u phải có
thuộc tính (L==T). Lúc đầu các nút đều có trạng thái là (W== F && L== F) , trừ nút lá có
(W==F && L==T). Như đã nói ở phần trên, nút không xác định là nút mà chỉ đi đến được các
nút tiềm năng, theo cách qhđ của ta như trên thì nút không xác định sẽ có thuộc tính là (W==F
&& L== F), và cũng theo cách qhđ trên thì cha trực tiếp của nút không xác định chính là nút tiềm
năng.
Sau khi thực hiện xong quy hoạch động ta sẽ tìm kết quả của bài toán:
- Nếu nút 1 là nút thua HOÀN TOÀN thì kết quả rõ ràng là người thứ 2 luôn thắng (vì người
thua lại phải đi trước ở ván sau)
- Nếu nút 1 là nút thắng HOÀN TOÀN thì nếu số ván là lẻ thì người 1 sẽ thắng, ngược lại
người 2 sẽ thắng
- Nếu nút 1 là nút tiềm năng thì người 1 luôn luôn thắng, vì nút 1 là tiềm năng tức là nó có
thể đến được nút thua hoàn toàn hoặc thắng hoàn toàn nên chiến thuật của người 1 sẽ là:
k-1 ván đầu tiên đi vào nút thắng HOÀN TOÀN để người 2 thắng ván đó, đến ván thứ k thì
người 1 sẽ đi vào nút thua HOÀN TOÀN và như vậy người 2 sẽ thua ván cuối cùng. Còn
một trường hợp nữa là trong các con của nút 1 có nút không xác định, thì người 1 chỉ cần
đi vào nút không xác định này, đến bước tiếp theo người 2 sẽ buộc phải đi vào 1 nút tiềm
năng và người 1 lại áp dụng chiến thuật như trên (do ta vừa nói ở trên, nút không xác định
chỉ đến được các nút tiềm năng)
- Nếu nút 1 là nút không xác định thì người 2 luôn thắng, bởi lẽ từ nút không xác định chỉ
có thể đi đến được các nút tiềm năng, và cũng như ở trường hợp trên, lần này người thứ 2
sẽ thắng.
Nhận xét: Đây là một bài tập khó đối với các bạn mới lần đầu tiếp xúc với bài toán trò chơi trong
tin học. Vậy kinh nghiệm để giải các bài toán dạng này đó chính là sử dụng thuật toán quy hoạch
động trên tập hợp các vị trí thắng và thua, từ đó tìm ra kết quả bài toán
Test và solution: http://www.mediafire.com/download/plse3vdflx6ogds
Bài 5. Chuỗi từ có độ dài n là một dãy các từ w1, w2, ..., wn sao cho với mọi 1 ≤i < n, từ
wi là tiền tố của từ wi+1. Nhắc lại từ u có độ dài k là tiền tố của từ v có độ dài l nếu l > k
và các ký tự đầu tiên của v trùng với từ u. Cho tập hợp các từ S = {s1, s2, ..., sm}. Tìm
chuỗi từ dài nhất có thể xây dựng được bằng cách dùng các từ trong tập hợp S (có thể
không sử dụng hết các từ).
Input
Dòng đầu tiên chứa số nguyên m (1 ≤m ≤250000). Mỗi dòng trong số m dòng sau
chứa một từ trong tập S. Biết rằng mỗi từ có độ dài không quá 250000 ký tự và tổng độ dài
của các từ không vượt quá 250000 ký tự.
Output
In ra một số duy nhất là độ dài của chuỗi từ dài nhất xây dựng được từ các từ trong
tập đã cho.
Example:
Input Output
3 3
a
ab
abc
5 2
a
ab
bc
bcd
add
Test và solution: http://www.mediafire.com/download/p27d3jao0tvee0f
C. THẢO LUẬN VÀ KẾT LUẬN

Người ta đã chứng minh được rằng số lượng nút trên cây Trie Tree không vượt quá 4*L
với L là tổng số kí tự của các xâu mà ta thực hiện thao tác Insert. Vì vậy trong các bài toán về
xâu, nếu muốn sử dụng Trie Tree cần phải chú ý đến giới hạn này. Bởi vì khi L quá lớn, ta sẽ
không đủ bộ nhớ để lưu Trie Tree, những bài toán như vậy ta không nên sử dụng Trie. Cũng cần
lưu ý thêm rằng, 4L là số lượng tính toán trên lý thuyết, trên thực tế các bài toán ta chỉ cần cung
cấp bộ nhớ L là đủ.
Tôi đã trình bày một cấu trúc dữ liệu đặc biệt, TRIE TREE. Cây tiền tố được định nghĩa và
cài đặt không quá phức tạp. Tuy nhiên, cây tiền tố lại có ứng dụng rộng rãi trong lĩnh vực khoa
học máy tính nói chung và trong các bài thi ôn thi VNOI nói riêng. Một số bài toán kinh điển ứng
dụng của TRIE TREE ví dụ: Bài toán tìm kiếm xâu, xâu con lặp dài nhất, xâu con chung dài nhất,
xâu đối xứng,… Trong thời gian tới Tôi sẽ tiếp tục xây dựng nhiều hơn nữa các bài tập (test và
solution) ứng dụng của cây tiền tố.
Trên đây là bài viết của Tôi về cây tiền tố, rất mong sự đóng góp ý kiến của quỹ Thầy Cô để
chuyên đề được tốt hơn.
Tôi xin chân thành cảm ơn!

You might also like