Trên cây nhị phân tìm kiếm có các thao tác sau: - Tìm một nút trên cây theo khóạ
- Thêm một nút vào câỵ - Xóa một nút trên câỵ
16
8 27
5 11 19 32
44
ạ Tìm một nút trên cây nhị phân tìm kiếm
Bài toán Giả sử có một cây nhị phân tìm kiếm có nút gốc là root. Ta cần tìm một nút có khóa là X trên câỵ
Ý tưởng thuật toán
- Nếu cây rỗng thì thuật toán dừng, kết quả không có khóa X trên câỵ
- Nếu khóa của một nút gốc bằng khóa X thì thuật toán dừng và kết quả đã tìm thấy nút chứa khóa X.
- Nếu khóa của nút gốc lớn hơn khóa X thì tiến hành (một cách đệ quy) tìm khóa X ở trên cây con tráị
- Nếu khóa của nút gốc nhỏ hơn khóa X thì tiến hành (một cách đệ quy) tìm khóa X ở cây con phảị
Thuật toán
- Giả sử cấu trúc một nút của cây đƣợc mô tả nhƣ sau: Gốc của Type
Pnode = ^node; {con trỏ chứa liên kết tới một nút} node= record {cấu trúc nút}
data: ElemenType; {trƣờng chứa khóa, ElemenType là kiểu dữ liệu: có thể kiểu số, kiểu kí tự, kiểu chuỗi, kiểu cấu trúc…}
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;
Cây đƣợc lƣu trong con trỏ root. Cây rỗng thì root = nil.
- Hàm tìm kiếm trên cây nhị phân tìm kiếm trả về nút chứa khóa X cần tìm hoặc trả về nil nếu không tìm thấỵ
Thuật toán 2.1
Hàm tìm kiếm nút X(val) trong cây tree
45 Begin
if (tree = nil) then return nil; {không tìm thấy khóa trên cây} if (val = treệdata) then return tree {tìm thấy khóa trên cây}
Else if ( val < treệdata) then {tìm tiếp trên cây con trái} Return search (treệleft, val)
Else {tìm tiếp trên cây con phải }
Return search (treệright, val); End;
Ví dụ 2.1 Tìm nút có khóa 28 trong cây ở Hình 2.6
- So sánh với khóa nút gốc là 16, vì 28 > 16 vậy ta tìm tiếp trên cây con bên phải, tức là cây có nút gốc là 27.
- So sánh 28 với khóa của nút gốc là 27, vì 28 > 27 vậy ta tìm tiếp trên cây con bên phải, tức là cây có nút gốc là 32.
- So sánh 28 với khóa của nút gốc là 32, vì 28 < 32 vậy ta tìm trên cây con bên trái, tức là cây có nút gốc có khóa là 28.
- So sánh 28 với khóa nút gốc là 28, vì 28 = 28 vậy giải thuật dừng và ta tìm đƣợc nút chứa khóa cần tìm.
Cài đặt chƣơng trình:
struct bin_tree { int data;
struct bin_tree * right, * left; };
typedef struct bin_tree node; node* search(node ** tree, int val) { if(!(*tree))
46 }
if(val == (*tree)->data) { return *tree; }
else if(val < (*tree)->data)
{ search(&((*tree)->left), val); }
else if(val > (*tree)->data)
{ search(&((*tree)->right), val); }
}
Kết quả thử nghiệm
47
Thêm một nút vào cây nhị phân tìm kiếm
Bài toán Cho một cây nhị phân tìm kiếm có nút gốc là root, thêm vào cây có một nút là X sao cho cây nhận đƣợc sau khi thêm cũng là cây nhị phân tìm kiếm.
Ý tưởng thuật toán
- Nếu cây rỗng thì X là nút gốc của cây, thuật toán dừng.
- Nếu khóa của nút gốc bằng khóa X thì thuật toán dừng (nút đã có trên cây). - Nếu khóa của nút gốc lớn hơn khóa X thì thêm X vào cây con bên tráị - Nếu khóa của nút gốc bé hơn khóa X thì thêm X vào cây con bên phảị
Thuật toán 2.2
Thủ tục chèn thêm nút X(val) vào cây tree
Procedure insertnode( val:integer, var Tree: ^node); Var Temp: ^node;
Begin
New(Temp); Temp^.data = val; Temp^.left = nil; Temp^.right = nil; If Tree = nil then
Begin
Tree := Temp; Return;
End;
If (val = (treệdata)) then {Trên cây đã tồn tại nút, không chèn đƣợc} Return
Else {cây khác rỗng} begin
48
If (val < Treệdata) {Thực hiện chèn giá trị vào cây con trái } insertnode(Treệleft, val)
Else
If (val > Treệdata) then {Thực hiện chèn giá trị vào cây con phải} insertnode(Treệright, val);
end; End;
Ví dụ 2.2 Thêm nút có giá trị là 6 vào cây nhị phân tìm kiếm.
- So sánh 6 với khóa của nút gốc là 16, vì 6 < 16 vậy ta xét tiếp đến cây con bên trái, tức là cây có nút gốc có khóa là 8.
- So sánh 6 với khóa của nút gốc là 8, vì 6 < 8 vậy ta xét tiếp đến cây con bên trái, tức là cây có nút gốc có khóa là 5.
- So sánh 6 với khóa của nút gốc là 5, vì 6 > 5 vậy ta xét tiếp đến cây con bên phải, nút con bên phải bằng nil, chứng tỏ rằng khóa 6 chƣa có trên cây, ta thêm nút mới chứa khóa 6 và nút mới này là con bên phải của nút có khóa là 5 nhƣ Hình 2.8.
Hình 2.8. Sau khi thêm nút 6 vào cây nhị phân tìm kiếm.
16 8 27 5 11 19 32 9 12 28 39 6
49
Cài đặt thuật toán
void insert(node ** tree, int val) {
node *temp = NULL; if(!(*tree)) {
temp = (node *)malloc(sizeof(node)); temp->left = temp->right = NULL; temp->data = val; *tree = temp; return; } if (val == (*tree)->data) {
printf(“Tren cay da ton tai nut, khong chen duoc”); }
else
if(val < (*tree)->data)
{ insert(&(*tree)->left, val); }
else if(val > (*tree)->data) {
insert(&(*tree)->right, val); }
50
Kết quả thử nghiệm
Hình 2.9. Kết quả thực hiện chương trình chèn thêm nút trên cây nhị phân
b. Xóa một nút trên cây nhị phân tìm kiếm
Giả sử ta muốn xóa một nút có khóa X. Trƣớc hết ta phải tìm kiếm nút chứa khóa X trên câỵ Việc xóa nhƣ vậy phải đảm bảo không phá vỡ cấu trúc cây nhị phân tìm kiếm.
Bài toán Cho một cây nhị phân tìm kiếm có nút gốc là root, xóa nút có khóa là a trên cây sao cho sau khi xóa cây vẫn còn thỏa mãn các tính chất của cây nhị phân tìm kiếm.
Ý tưởng thuật toán
Để xóa, trƣớc tiên ta phải tìm nút có khóa a cần xóa (dùng thuật toán tìm kiếm trên cây) và ta chỉ thực hiện thao tác xóa nếu tìm đƣợc.
51 Để xóa nút X, ta phải xét các trƣờng hợp sau:
- X là nút lá của câỵ
- X có một cây con trái hoặc phảị - X có cả hai cây con trái và phảị Trƣờng hợp 1:
Nếu X là nút lá và giả sử parent là nút cha của X. Trong trƣờng hợp này ta chỉ cần đặt con trỏ trái hoặc phải của parent đến nil tùy thuộc vào X là nút con trái hay nút con phải của parent. Sau đó giải phóng ô nhớ nút X. Nếu nút cần xóa là nút gốc thì đây là nút duy nhất của cây nên sau khi xóa cây sẽ rỗng.
Hình 2.10 cho ta thấy hình ảnh cây sau khi xóa nút có khóa 9.
Parent x
nil
Hình 2.10. Sau khi xóa nút 9 trên cây nhị phân tìm kiếm.
Trƣờng hợp 2: Khi có X đúng một nút con, giả sử parent là nút cha của X và subtree là nút con khác nil của X. Trong trƣờng hợp này ta chỉ cần đặt con trỏ trái hoặc phải của nút parent vào nút con của X là subtree tùy thuộc vào X là nút con trái hay con phải của parent. Sau đó giải phóng ô nhớ của nút X. Hình 2.11, để xóa nút 11 ta liên kết bên phải của nút 8 đến nút 12.
16
8 27
5 11 19 32
9 12 28 39
52 Parent x Subtree Hình 2.11. Xóa nút có một nút con.
Trong trƣờng hợp nút X là gốc của cây, khi đó chỉ cần chuyển gốc của cây về nút con khác nil của nút x sau đó giải phóng X.
Trƣờng hợp 3: Nếu nút cần xóa X có cả hai cây con trái và phảị Để xóa nút X trong trƣờng hợp này ta đƣa về một trong hai dạng trên bằng cách tìm nút có khóa lớn nhất của các nút trong cây con trái của nút X (hoặc nút có khóa nhỏ nhất của các nút trong cây con phải) giả sử nút này là ỵ Dễ thấy nút y có không quá một cây con vì nó là nút tận cùng bên phải cây con trái X (hoặc là nút tận cùng bên trái của cây con phải X). Hình 2.12 minh họa thao tác xóa nút có hai cây con.
x
Hình 2.12. Xóa nút có hai con.
16 8 27 5 11 19 32 12 28 39 6 16 8 27 5 11 19 32 9 12 28 39 6 10
53
Thuật toán 2.3
Thủ tục xóa khóa X khỏi cây nhị phân tìm kiếm
Xóa nút X(val) khỏi cây có nút gốc root
Procedure deletenode(var root: ^node; val : integer); Var p, q, parent, subtree: node;
Begin
{Tìm nút cần xóa p, parent là nút cha của p} P:= root;
Parent := nil;
While (p ≠ nil ) and (p^.data ≠ val ) do Begin
Parent := p;
If p^.data < val then p:=p^.right; Else If p^.data > val then p:=p^.left;
End;
If p = nil then writeln(„khong co phan tu can xoa‟) Else
Begin
{trƣờng hợp nút cần xóa có 2 cây con:
-Tìm nút q là nút trái nhất của cây con bên phảị -parent là nút cha của nút q.}
If (p^.left ≠ nil) and (p^.right ≠ nil) then Begin
q := p^.right; Parent := p; While q^.left ≠ nil do
54 Begin
Parent := q; q := p^.left; end;
{Đƣa dữ liệu của nút q vào nút p} p^.data := q^.data;
p := q; end;
{Xóa nút p trong trƣờng hợp không quá một nút con} {Tìm cây con của p}
Subtree := p^.left;
If Subtree = nil then Subtree := p^.right; If parent = nil then {nút cần xóa là nút gốc}
Root := Subtree Else
Begin
{p là nút trái của parent} If parent^.data > p^.data then
Parent^.left := subtree Else Parent^.right := subtree; End; Dispose(p); End; End;
55
Cài đặt thuật toán
node* deleteNode(node* root, int key) { node* p; node* q; node* parent; node* subtree; p = root; parent = NULL;
while ((p != NULL) && (p->data != key)) {
parent = p;
if (p->data < key) {
p = p->right; } else if (p->data > key) { p = p->left; } } if (p == NULL) {
printf("Khong co phan tu can xoa"); }
else {
56
if((p->left != NULL) && p->right != NULL) {
q = p->right; parent = p;
while (q->left != NULL) { parent = q; q = q->left; } p->data = q->data; p = q; } subtree = p->left; if (subtree == NULL) { subtree = p->right; } if(parent == NULL) { root = subtree; }else { if (parent->data > p->data) { parent->left = subtree; }
57 else { parent->right = subtree; } } } return root; } Kết quả thử nghiệm
58
Hình 2.14. Kết quả thực hiện chương trình xóa nút có một cây con trên cây nhị phân
Hình 2.15. Kết quả thực hiện chương trình xóa nút có hai cây con trái và phải trên cây nhị phân
59