• Mọi khoá nằm trong cây con phải của nút đó đều lớn hơn khoá ứng với nút đó 4 9 Hình 17: Cây nhị phân tìm kiếm Thuật toán tìm kiếm trên cây có thể mô tả chung như sau: Trước hết, khoá t
Trang 1§8 TÌM KIẾM (SEARCHING)
I BÀI TOÁN TÌM KIẾM
Cùng với sắp xếp, tìm kiếm là một đòi hỏi rất thường xuyên trong các ứng dụng tin học Bài toántìm kiếm có thể phát biểu như sau:
Cho một dãy gồm n bản ghi r1, r2, , rn Mỗi bản ghi ri (1 ≤ i ≤ n) tương ứng với một khoá ki Hãytìm bản ghi có giá trị khoá bằng X cho trước
X được gọi là khoá tìm kiếm hay đối trị tìm kiếm (argument)
Công việc tìm kiếm sẽ hoàn thành nếu như có một trong hai tình huống sau xảy ra:
• Tìm được bản ghi có khoá tương ứng bằng X, lúc đó phép tìm kiếm thành công (successful)
• Không tìm được bản ghi nào có khoá tìm kiếm bằng X cả, phép tìm kiếm thất bại(unsuccessful)
Tương tự như sắp xếp, ta coi khoá của một bản ghi là đại diện cho bản ghi đó Và trong một sốthuật toán sẽ trình bày dưới đây, ta coi kiểu dữ liệu cho mỗi khoá cũng có tên gọi là TKey
const
n = ; {Số khoá trong dãy khoá, có thể khai dưới dạng biến số nguyên để tuỳ biến hơn}
type
TKey = ; {Kiểu dữ liệu một khoá}
TArray = array[1 n] of TKey;
var
k: TArray; {Dãy khoá}
II TÌM KIẾM TUẦN TỰ (SEQUENTIAL SEARCH)
Tìm kiếm tuần tự là một kỹ thuật tìm kiếm đơn giản Nội dung của nó như sau: Bắt đầu từ bản ghiđầu tiên, lần lượt so sánh khoá tìm kiếm với khoá tương ứng của các bản ghi trong danh sách, chotới khi tìm thấy bản ghi mong muốn hoặc đã duyệt hết danh sách mà chưa thấy
{Tìm kiếm tuần tự trên dãy khoá k 1 , k 2 , , k n ; hàm này thử tìm xem trong dãy có khoá nào = X không, nếu thấy nó trả về chỉ số của khoá ấy, nếu không thấy nó trả về 0 Có sử dụng một khoá phụ k n+1 được gán giá trị = X}
function SequentialSearch(X: TKey): Integer;
III TÌM KIẾM NHỊ PHÂN (BINARY SEARCH)
Phép tìm kiếm nhị phân có thể áp dụng trên dãy khoá đã có thứ tự: k1≤ k2≤ ≤ kn
Giả sử ta cần tìm trong đoạn kinf, kinf+1, , ksup với khoá tìm kiếm là X, trước hết ta xét khoá nằmgiữa dãy kmedian với median = (inf + sup) div 2;
• Nếu kmedian < X thì có nghĩa là đoạn từ kinf tới kmedian chỉ chứa toàn khoá < X, ta tiến hành tìmkiếm tiếp với đoạn từ kmedian + 1 tới ksup
• Nếu kmedian > X thì có nghĩa là đoạn từ kmedian tới ksup chỉ chứa toàn khoá > X, ta tiến hành tìmkiếm tiếp với đoạn từ kinf tới kmedian - 1
• Nếu kmedian = X thì việc tìm kiếm thành công (kết thúc quá trình tìm kiếm)
Trang 2Quá trình tìm kiếm sẽ thất bại nếu đến một bước nào đó, đoạn tìm kiếm là rỗng (inf > sup).
{Tìm kiếm nhị phân trên dãy khoá k 1 ≤ k 2 ≤ ≤ k n ; hàm này thử tìm xem trong dãy có khoá nào = X không, nếu thấy nó trả về chỉ số của khoá ấy, nếu không thấy nó trả về 0}
function BinarySearch(X: TKey): Integer;
if k median < X then inf := median + 1
else sup := median - 1;
IV CÂY NHỊ PHÂN TÌM KIẾM (BINARY SEARCH TREE - BST)
Cho n khoá k1, k2, , kn, trên các khoá có quan hệ thứ tự toàn phần Cây nhị phân tìm kiếm ứng vớidãy khoá đó là một cây nhị phân mà mỗi nút chứa giá trị một khoá trong n khoá đã cho, hai giá trịchứa trong hai nút bất kỳ là khác nhau Đối với mọi nút trên cây, tính chất sau luôn được thoả mãn:
• Mọi khoá nằm trong cây con trái của nút đó đều nhỏ hơn khoá ứng với nút đó
• Mọi khoá nằm trong cây con phải của nút đó đều lớn hơn khoá ứng với nút đó
4
9
Hình 17: Cây nhị phân tìm kiếm
Thuật toán tìm kiếm trên cây có thể mô tả chung như sau:
Trước hết, khoá tìm kiếm X được so sánh với khoá ở gốc cây, và 4 tình huống có thể xảy ra:
• Không có gốc (cây rỗng): X không có trên cây, phép tìm kiếm thất bại
• X trùng với khoá ở gốc: Phép tìm kiếm thành công
• X nhỏ hơn khoá ở gốc, phép tìm kiếm được tiếp tục trong cây con trái của gốc với cách làmtương tự
Trang 3• X lớn hơn khoá ở gốc, phép tìm kiếm được tiếp tục trong cây con phải của gốc với cách làmtương tự
Giả sử cấu trúc một nút của cây được mô tả như sau:
type
PNode = ^TNode; {Con trỏ chứa liên kết tới một nút}
TNode = record {Cấu trúc nút}
Info: TKey; {Trường chứa khoá}
Left, Right: PNode; {con trỏ tới nút con trái và phải, trỏ tới nil nếu không có nút con trái (phải)}
end;
G ốc của cây được lưu trong con trỏ Root Cây rỗng thì Root = nil
Thuật toán tìm kiếm trên cây nhị phân tìm kiếm có thể viết như sau:
{Hàm tìm kiếm trên BST, nó trả về nút chứa khoá tìm kiếm X nếu tìm thấy, trả về nil nếu không tìm thấy}
function BSTSearch(X: TKey): PNode;
if X = p^.Info then Break;
else {X ≠ p^.Info thì cho p chạy sang nút con, q^ luôn giữ vai trò là cha của p^}
if X < p^.Info then p := p^.Left
else p := p^.Right;
end;
if p = nil then {Khoá X chưa có trong BST}
begin
p^.Info := X; {Đưa giá trị X vào nút mới tạo ra}
p^.Left := nil; p^.Right := nil; {Nút mới khi chèn vào BST sẽ trở thành nút lá}
if Root = nil then Root := NewNode {BST đang rỗng, đặt Root là nút mới tạo}
if X < q^.Info then q^.Left := NewNode
else q^.Right := NewNode;
end;
end;
Phép loại bỏ trên cây nhị phân tìm kiếm không đơn giản như phép bổ sung hay phép tìm kiếm.Muốn xoá một giá trị trong cây nhị phân tìm kiếm (Tức là dựng lại cây mới chứa tất cả những giátrị còn lại), trước hết ta tìm xem giá trị cần xoá nằm ở nút D nào, có ba khả năng xảy ra:
Trang 4• Nút D là nút lá, trường hợp này ta chỉ việc đem mối nối cũ trỏ tới nút D (từ nút cha của D) thaybởi nil, và giải phóng bộ nhớ cấp cho nút D.
4 2
7 9
• Nút D có cả hai nhánh con trái và phải, khi đó có hai cách làm đều hợp lý cả:
♦ Hoặc tìm nút chứa khoá lớn nhất trong cây con trái, đưa giá trị chứa trong đó sang nút D, rồixoá nút này Do tính chất của cây BST, nút chứa khoá lớn nhất trong cây con trái chính là nútcực phải của cây con trái nên nó không thể có hai con được, việc xoá đưa về hai trường hợptrên
♦ Hoặc tìm nút chứa khoá nhỏ nhất trong cây con phải, đưa giá trị chứa trong đó sang nút D, rồixoá nút này Do tính chất của cây BST, nút chứa khoá nhỏ nhất trong cây con phải chính lànút cực trái của cây con phải nên nó không thể có hai con được, việc xoá đưa về hai trườnghợp trên
5 2
7 9
Như vậy trong trường hợp nút D có hai con, ta đem giá trị chứa ở một nút khác chuyển sang cho
D rồi xoá nút đó thay cho D Cũng có thể làm bằng cách thay một số mối nối, nhưng làm như thếnày đơn giản hơn nhiều
{Thủ tục xoá khoá X khỏi BST}
procedure BSTDelete(X: TKey);
Trang 5p, q, Node, Child: PNode;
begin
p := Root; q := nil; {Về sau, khi p trỏ sang nút khác, ta cố gắng giữ cho q^ luôn là cha của p^}
while p ≠ nil do {Tìm xem trong cây có khoá X không?}
if p = nil then Exit; {X không tồn tại trong BST nên không xoá được}
if (p^.Left ≠ nil) and (p^.Right ≠ nil) then {p^ có cả con trái và con phải}
begin
Node := p; {Giữ lại nút chứa khoá X}
q := p; p := p^.Left; {Chuyển sang nhánh con trái để tìm nút cực phải}
while p^.Right ≠ nil do
{Nút bị xoá giờ đây là nút p^, nó chỉ có nhiều nhất một con}
{Nếu p^ có một nút con thì đem Child trỏ tới nút con đó, nếu không có thì Child = nil }
if p^.Left ≠ nil then Child := p^.Left
else Child := p^.Right;
if p = Root then Root := Child; {Nút p^ bị xoá là gốc cây}
else {Nút bị xoá p^ không phải gốc cây thì lấy mối nối từ cha của nó là q^ nối thẳng tới Child}
if q^.Left = p then q^.Left := Child
else q^.Right := Child;
Dispose(p);
end;
Trường hợp trung bình, thì các thao tác tìm kiếm, chèn, xoá trên BST có độ phức tạp là O(log2n).Còn trong trường hợp xấu nhất, cây nhị phân tìm kiếm bị suy biến thì các thao tác đó đều có độphức tạp là O(n), với n là số nút trên cây BST
Nếu ta mở rộng hơn khái niệm cây nhị phân tìm kiếm như sau: Giá trị lưu trong một nút lớn hơn
hoặc bằng các giá trị lưu trong cây con trái và nhỏ hơn các giá trị lưu trong cây con phải Thì chỉ
cần sửa đổi thủ tục BSTInsert một chút, khi chèn lần lượt vào cây n giá trị, cây BST sẽ có n nút (cóthể có hai nút chứa cùng một giá trị) Khi đó nếu ta duyệt các nút của cây theo kiểu trung thứ tự(inorder traversal), ta sẽ liệt kê được các giá trị lưu trong cây theo thứ tự tăng dần Phương pháp sắpxếp này người ta gọi là Tree Sort Độ phức tạp tính toán trung bình của Tree Sort là O(nlog2n).Phép tìm kiếm trên cây BST sẽ kém hiệu quả nếu như cây bị suy biến, người ta có nhiều cách xoay
xở để tránh trường hợp này Đó là phép quay cây để dựng cây nhị phân cân đối AVL, hay kỹ thuậtdựng cây nhị phân tìm kiếm tối ưu Những kỹ thuật này ta có thể tham khảo trong các tài liệu khác
về cấu trúc dữ liệu và giải thuật
V PHÉP BĂM (HASH)
Tư tưởng của phép băm là dựa vào giá trị các khoá k1, k2, , kn, chia các khoá đó ra thành các
nhóm Những khoá thuộc cùng một nhóm có một đặc điểm chung và đặc điểm này không có
trong các nhóm khác Khi có một khoá tìm kiếm X, trước hết ta xác định xem nếu X thuộc vào dãykhoá đã cho thì nó phải thuộc nhóm nào và tiến hành tìm kiếm trên nhóm đó
Trang 6Một ví dụ là trong cuốn từ điển, các bạn sinh viên thường dán vào 26 mảnh giấy nhỏ vào các trang
để đánh dấu trang nào là trang khởi đầu của một đoạn chứa các từ có cùng chữ cái đầu Để khi tra từchỉ cần tìm trong các trang chứa những từ có cùng chữ cái đầu với từ cần tìm
A B Z
Một ví dụ khác là trên dãy các khoá số tự nhiên, ta có thể chia nó là làm m nhóm, mỗi nhóm gồmcác khoá đồng dư theo mô-đun m
Có nhiều cách cài đặt phép băm:
• Cách thứ nhất là chia dãy khoá làm các đoạn, mỗi đoạn chứa những khoá thuộc cùng một nhóm
và ghi nhận lại vị trí các đoạn đó Để khi có khoá tìm kiếm, có thể xác định được ngay cần phảitìm khoá đó trong đoạn nào
• Cách thứ hai là chia dãy khoá làm m nhóm, Mỗi nhóm là một danh sách nối đơn chứa các giá trịkhoá và ghi nhận lại chốt của mỗi danh sách nối đơn Với một khoá tìm kiếm, ta xác định đượcphải tìm khoá đó trong danh sách nối đơn nào và tiến hành tìm kiếm tuần tự trên danh sách nốiđơn đó Với cách lưu trữ này, việc bổ sung cũng như loại bỏ một giá trị khỏi tập hợp khoá dễdàng hơn rất nhiều phương pháp trên
• Cách thứ ba là nếu chia dãy khoá làm m nhóm, mỗi nhóm được lưu trữ dưới dạng cây nhị phântìm kiếm và ghi nhận lại gốc của các cây nhị phân tìm kiếm đó, phương pháp này có thể nói làtốt hơn hai phương pháp trên, tuy nhiên dãy khoá phải có quan hệ thứ tự toàn phần thì mới làmđược
VI KHOÁ SỐ VỚI BÀI TOÁN TÌM KIẾM
Mọi dữ liệu lưu trữ trong máy tính đều được số hoá, tức là đều được lưu trữ bằng các đơn vị Bit,Byte, Word v.v Điều đó có nghĩa là một giá trị khoá bất kỳ, ta hoàn toàn có thể biết được nó được
mã hoá bằng con số như thế nào Và một điều chắc chắn là hai khoá khác nhau sẽ được lưu trữ bằnghai số khác nhau
Đối với bài toán sắp xếp, ta không thể đưa việc sắp xếp một dãy khoá bất kỳ về việc sắp xếp trênmột dãy khoá số là mã của các khoá Bởi quan hệ thứ tự trên các con số đó có thể khác với thứ tựcần sắp của các khoá
Nhưng đối với bài toán tìm kiếm thì khác, với một khoá tìm kiếm, Câu trả lời hoặc là "Không tìmthấy" hoặc là "Có tìm thấy và ở chỗ " nên ta hoàn toàn có thể thay các khoá bằng các mã số của
nó mà không bị sai lầm, chỉ lưu ý một điều là: hai khoá khác nhau phải mã hoá thành hai số khácnhau mà thôi
Nói như vậy có nghĩa là việc nghiên cứu những thuật toán tìm kiếm trên các dãy khoá số rất quantrọng, và dưới đây ta sẽ trình bày một số phương pháp đó
VII CÂY TÌM KIẾM SỐ HỌC (DIGITAL SEARCH TREE - DST)
Xét dãy khoá k1, k2, , kn là các số tự nhiên, mỗi giá trị khoá khi đổi ra hệ nhị phân có z chữ số nhịphân (bit), các bit này được đánh số từ 0 (là hàng đơn vị) tới z - 1 từ phải sang trái
Trang 7So sánh cây tìm kiếm số học với cây nhị phân tìm kiếm, chúng chỉ khác nhau về cách chia hai câycon trái/phải Đối với cây nhị phân tìm kiếm, việc chia này được thực hiện bằng cách so sánh vớikhoá nằm ở nút gốc, còn đối với cây tìm kiếm số học, nếu nút gốc có mức là d thì việc chia cây conđược thực hiện theo bít thứ d tính từ trái sang (bít z - d) của mỗi khoá.
Ta nhận thấy rằng những khoá bắt đầu bằng bít 0 chắc chắn nhỏ hơn những khoá bắt đầu bằng bít 1,
đó là điểm tương đồng giữa cây nhị phân tìm kiếm và cây tìm kiếm số học: Với mỗi nút nhánh: Mọigiá trị chứa trong cây con trái đều nhỏ hơn giá trị chứa trong cây con phải
Hình 18: Cây tìm kiếm số học
Giả sử cấu trúc một nút của cây được mô tả như sau:
type
PNode = ^TNode; {Con trỏ chứa liên kết tới một nút}
TNode = record {Cấu trúc nút}
Info: TKey; {Trường chứa khoá}
Left, Right: PNode; {con trỏ tới nút con trái và phải, trỏ tới nil nếu không có nút con trái (phải)}
end;
G ốc của cây được lưu trong con trỏ Root Ban đầu nút Root = nil (cây rỗng)
Với khoá tìm kiếm X, việc tìm kiếm trên cây tìm kiếm số học có thể mô tả như sau: Ban đầu đứng ởnút gốc, xét lần lượt các bít của X từ trái sang phải (từ bít z - 1 tới bít 0), hễ gặp bít bằng 0 thì rẽsang nút con trái, nếu gặp bít bằng 1 thì rẽ sang nút con phải Quá trình cứ tiếp tục như vậy cho tớikhi gặp một trong hai tình huống sau:
• Đi tới một nút rỗng (do rẽ theo một liên kết nil), quá trình tìm kiếm thất bại do khoá X không cótrong cây
• Đi tới một nút mang giá trị đúng bằng X, quá trình tìm kiếm thành công
{Hàm tìm kiếm trên cây tìm kiếm số học, nó trả về nút chứa khoá tìm kiếm X nếu tìm thấy, trả về nil nếu không tìm thấy z là độ dài dãy bít biểu diễn một khoá}
function DSTSearch(X: TKey): PNode;
var
b: Integer;
p: PNode;
begin
b := z; p := Root; {Bắt đầu với nút gốc}
while (p ≠ nil) and (p^.Info ≠ X) do{Chưa gặp phải một trong 2 tình huống trên}
begin
Trang 8b := b - 1; {Xét bít b của X}
if <Bít b c ủa X là 0> then p := p^.Left {Gặp 0 rẽ trái}
else p := p^.Right; {Gặp 1 rẽ phải}
procedure DSTInsert(X: TKey);
q := p; {Khi p chạy xuống nút con thì q^ luôn giữ vai trò là nút cha của p^}
if <Bít b c ủa X là 0> then p := p^.Left {Gặp 0 rẽ trái}
else p := p^.Right; {Gặp 1 rẽ phải}
end;
if p = nil then {Giá trị X chưa có trong cây}
begin
New(p); {Tạo ra một nút mới p^}
p^.Info := X; {Nút mới tạo ra sẽ chứa khoá X}
p^.Left := nil; p^.Right := nil; {Nút mới đó sẽ trở thành một lá của cây}
if Root = nil then Root := p {Cây đang là rỗng thì nút mới thêm trở thành gốc}
else {Không thì móc p^ vào mối nối vừa rẽ sang từ q^}
if <Bít b c ủa X là 0> then q^.Left := p
đó sang nút D rồi xoá nút lá
{Thủ tục xoá khoá X khỏi cây tìm kiếm số học}
procedure DSTDelete(X: TKey);
q := p; {Mỗi lần p chuyển sang nút con, ta luôn đảm bảo cho q^ là nút cha của p^}
if <Bít b c ủa X là 0> then p := p^.Left
else p := p^.Right;
end;
if p = nil then Exit; {X không tồn tại trong cây thì không xoá được}
Node := p; {Giữ lại nút chứa khoá cần xoá}
while (p^.Left ≠ nil) or (p^.Right ≠ nil) do {chừng nào p^ chưa phải là lá}
begin
Trang 9q := p; {q chạy đuổi theo p, còn p chuyển xuống một trong 2 nhánh con}
if p^.Left ≠ nil then p := p^.Left
else p := p^.Right;
end;
NodệInfo := p^.Info; {Chuyển giá trị từ nút lá p^ sang nút Nodê}
if Root = p then Root := nil {Cây chỉ gồm một nút gốc và bây giờ xoá cả gốc}
else {Cắt mối nối từ q^ tới p^}
if q^.Left = p then q^.Left := nil
else q^.Right := nil;
Dispose(p);
end;
Về mặt trung bình, các thao tác tìm kiếm, chèn, xoá trên cây tìm kiếm số học đều có độ phức tạp làO(log2n) còn trong trường hợp xấu nhất, độ phức tạp của các thao tác đó là O(z), bởi cây tìm kiếm
số học có chiều cao không quá z + 1
VIIỊ CÂY TÌM KIẾM CƠ SỐ (RADIX SEARCH TREE - RST)
Trong cây tìm kiếm số học, cũng như cây nhị phân tìm kiếm, phép tìm kiếm tại mỗi bước phải sosánh giá trị khoá X với giá trị lưu trong một nút của câỵ Trong trường hợp các khoá có cấu trúclớn, việc so sánh này có thể mất nhiều thời gian
Cây tìm kiếm cơ số là một phương pháp khắc phục nhược điểm đó, nội dung của nó có thể tóm tắtnhư sau:
Trong cây tìm kiếm cơ số là một cây nhị phân, chỉ có nút lá chứa giá trị khoá, còn giá trị chứa trongcác nút nhánh là vô nghĩạ Các nút lá của cây tìm kiếm cơ số đều nằm ở mức z + 1
Đối với nút gốc của cây tìm kiếm cơ số, nó có tối đa hai nhánh con, mọi khoá chứa trong nút lá củanhánh con trái đều có bít cao nhất là 0, mọi khoá chứa trong nút lá của nhánh con phải đều có bítcao nhất là 1
Đối với hai nhánh con của nút gốc, vấn đề tương tự với bít thứ z - 2, ví dụ với nhánh con trái củanút gốc, nó lại có tối đa hai nhánh con, mọi khoá chứa trong nút lá của nhánh con trái đều có bít thứ
z - 2 là 0 (chúng bắt đầu bằng hai bít 00), mọi khoá chứa trong nút lá của nhánh con phải đều có bítthứ z - 2 là 1 (chúng bắt đầu bằng hai bít 01)
Tổng quát với nút ở mức d, nó có tối đa hai nhánh con, mọi nút lá của nhánh con trái chứa khoá cóbít z - d là 0, mọi nút lá của nhánh con phải chứa khoá có bít thứ z - d là 1
2――=―0010 7――=―0111
8――=―1000 10―=―1010 12―=―1100 11―=―1011
4――=―0100 5
7 4
0
0
0
0 8
Hình 19: Cây tìm kiếm cơ số
Khác với cây nhị phân tìm kiếm hay cây tìm kiếm số học Cây tìm kiếm cơ số được khởi tạo gồm
có một nút gốc, và nút gốc tồn tại trong suốt quá trình sử dụng: nó không bao giờ bị xoá đi cả.
Để tìm kiếm một giá trị X trong cây tìm kiếm cơ số, ban đầu ta đứng ở nút gốc và duyệt dãy bít của
X từ trái qua phải (từ bít z - 1 đến bít 0), gặp bít bằng 0 thì rẽ sang nút con trái còn gặp bít bằng 1thì rẽ sang nút con phải, cứ tiếp tục như vậy cho tới khi một trong hai tình huống sau xảy ra:
Trang 10• Hoặc đi tới một nút rỗng (do rẽ theo liên kết nil) quá trình tìm kiếm thất bại do X không cótrong RST
• Hoặc đã duyệt hết dãy bít của X và đang đứng ở một nút lá, quá trình tìm kiếm thành công vìchắc chắn nút lá đó chứa giá trị đúng bằng X
{Hàm tìm kiếm trên cây tìm kiếm cơ số, nó trả về nút lá chứa khoá tìm kiếm X nếu tìm thấy, trả về nil nếu không tìm thấy z là độ dài dãy bít biểu diễn một khoá}
function RSTSearch(X: TKey): PNode;
if <Bít b c ủa X là 0> then p := p^.Left {Gặp 0 rẽ trái}
else p := p^.Right; {Gặp 1 rẽ phải}
rẽ theo một liên kết nil (đi tới nút rỗng) thì lập tức tạo ra một nút mới, và nối vào theo liên kết đó để
có đường đi tiếp Sau khi duyệt hết dãy bít của X, ta sẽ dừng lại ở một nút lá của RST, và công việccuối cùng là đặt giá trị X vào nút lá đó
Ví dụ:
2=010 5=101 4=100
5 2
0
5 2
7 4
0
2=010 5=101 4=100 7=111
Hình 20: Với độ dài dãy bít z = 3, cây tìm kiếm cơ số gồm các khoá 2, 4, 5 và sau khi thêm giá trị 7
{Thủ tục chèn khoá X vào cây tìm kiếm cơ số}
procedure RSTInsert(X: TKey);
q := p; {Khi p chạy xuống nút con thì q^ luôn giữ vai trò là nút cha của p^}
if <Bít b c ủa X là 0> then p := p^.Left {Gặp 0 rẽ trái}
else p := p^.Right; {Gặp 1 rẽ phải}
if p = nil then {Không đi được thì đặt thêm nút để đi tiếp}
begin
New(p); {Tạo ra một nút mới và đem p trỏ tới nút đó}
p^.Left := nil; p^.Right := nil;
if <Bít b của X là 0> then q^.Left := p {Nối p^ vào bên trái q^}
else q^.Right := p; {Nối p^ vào bên phải q^}
end;
Trang 115 2
0 5
7 4
0
2=010 5=101 4=100 7=111
Hình 21: RST chứa các khoá 2, 4, 5, 7 và RST sau khi loại bỏ giá trị 7
Ta lặp lại quá trình tìm kiếm giá trị khoá X, quá trình này sẽ đi từ gốc xuống lá, tại mỗi bước đi,mỗi khi gặp một nút ngã ba (nút có cả con trái và con phải - nút cấp hai), ta ghi nhận lại ngã ba đó
và hướng rẽ Kết thúc quá trình tìm kiếm ta giữ lại được ngã ba đi qua cuối cùng, từ nút đó tới nút láchứa X là con đường độc đạo (không có chỗ rẽ), ta tiến hành dỡ bỏ tất cả các nút trên đoạn đườngđộc đạo khỏi cây tìm kiếm cơ số Để không bị gặp lỗi khi cây suy biến (không có nút cấp 2) ta coigốc cũng là nút ngã ba
{Thủ tục xoá khoá X khỏi cây tìm kiếm cơ số}
procedure RSTDelete(X: TKey);
q := p; {Mỗi lần p chuyển sang nút con, ta luôn đảm bảo cho q^ là nút cha của p^}
if <Bít b c ủa X là 0> then p := p^.Left
if p = nil then Exit; {X không tồn tại trong cây thì không xoá được}
{Trước hết, cắt nhánh độc đạo ra khỏi cây}
if TurnNodệLeft = Child then TurnNodệLeft := nil
else TurnNodệRight := nil
p := Child; {Chuyển sang đoạn đường độc đạo, bắt đầu xoá}
repeat
q := p;
{Lưu ý rằng p^ chỉ có tối đa một nhánh con mà thôi, cho p trỏ sang nhánh con duy nhất nếu có}
if p^.Left ≠ nil then p := p^.Left
Trang 12Đối với cây tìm kiếm cơ số, độ phức tạp tính toán cho các thao tác tìm kiếm, chèn, xoá trong trườnghợp xấu nhất cũng như trung bình đều là O(z) Do không phải so sánh giá trị khoá dọc đường đi, nónhanh hơn cây tìm kiếm số học nếu như gặp các khoá cấu trúc lớn Tốc độ như vậy có thể nói là tốt,nhưng vấn đề bộ nhớ khiến ta phải xem xét: Giá trị chứa trong các nút nhánh của cây tìm kiếm cơ
số là vô nghĩa dẫn tới sự lãng phí bộ nhớ
Một giải pháp cho vấn đề này là: Duy trì hai dạng nút trên cây tìm kiếm cơ số: Dạng nút nhánh chỉchứa các liên kết trái, phải và dạng nút lá chỉ chứa giá trị khoá Cài đặt cây này trên một số ngônngữ định kiểu quá mạnh đôi khi rất khó
Giải pháp thứ hai là đặc tả một cây tương tự như RST, nhưng sửa đổi một chút: nếu có nút lá chứagiá trị X được nối với cây bằng một nhánh độc đạo thì cắt bỏ nhánh độc đạo đó, và thay vào chỗnhánh này chỉ một nút chứa giá trị X Như vậy các giá trị khoá vẫn chỉ chứa trong các nút lá nhưngcác nút lá giờ đây không chỉ nằm trên mức z + 1 mà còn nằm trên những mức khác nữa Phươngpháp này không những tiết kiệm bộ nhớ hơn mà còn làm cho quá trình tìm kiếm nhanh hơn Giáphải trả cho phương pháp này là thao tác chèn, xoá khá phức tạp Tên của cấu trúc dữ liệu này làTrie (Trie chứ không phải Tree) tìm kiếm cơ số
2――=―0010 7――=―0111 8――=―1000
10―=―1010 12―=―1100 11―=―1011
4――=―0100 5
7 4
0
0
0
0 8
0
5――=―0101 2――=―0010
7――=―0111 8――=―1000 10―=―1010
12―=―1100 11―=―1011 4――=―0100 5
Hình 22: Cây tìm kiếm cơ số a) và Trie tìm kiếm cơ số b)
Tương tự như phương pháp sắp xếp bằng cơ số, phép tìm kiếm bằng cơ số không nhất thiết phảichọn hệ cơ số 2 Ta có thể chọn hệ cơ số lớn hơn để có tốc độ nhanh hơn (kèm theo sự tốn kém bộnhớ), chỉ lưu ý là cây tìm kiếm số học cũng như cây tìm kiếm cơ số trong trường hợp này khôngcòn là cây nhị phân mà là cây R_phân với R là hệ cơ số được chọn
Trong các phương pháp tìm kiếm bằng cơ số, thực ra còn một phương pháp tinh tuý và thông minhnhất, nó có cấu trúc gần giống như cây nhưng không có nút dư thừa, và quá trình duyệt bít của khoátìm kiếm không phải từ trái qua phải mà theo thứ tự của các bít kiểm soát lưu tại mỗi nút đi qua.Phương pháp đó có tên gọi là Practical Algorithm To Retrieve Information Coded In Alphanumeric