You are on page 1of 80

CẤU TRÚC DỮ LIỆU CÂY

(TREE DATA STRUCTURE)


• Các khái niệm
• Biểu diễn cây nhị phân
• Duyệt cây nhị phân
• Cây nhị phân tìm kiếm
• Cây nhị phân tìm kiếm cân bằng (tham khảo)

1
CÁC KHÁI NIỆM

• Cây là một tập hữu hạn các nút (đỉnh) trong đó có một
nút đặc biệt gọi là gốc (root)
• Giữa các nút có quan hệ phân cấp gọi là “quan hệ cha
con” và được biểu diễn bởi một cung (cạnh) từ cha đến
con

2
CÁC KHÁI NIỆM

• Cây được định nghĩa một cách đệ qui:


 Một nút là một cây, nút đó cũng là gốc của cây
 Nếu r là một nút và T1, T2, …, Tk là các cây với x1, x2, …, xk
lần lượt là các gốc thì một cây mới T sẽ được tạo lập bằng
cách cho r trở thành cha của các nút x1, x2, …, xk
 Lúc này r là gốc còn T1, T2, …, Tk là các cây con (subtrees)
của T (x1, x2, …, xk là con của nút r)

3
CÁC KHÁI NIỆM

• Qui ước cho phép tồn tại cây không có nút nào và gọi đó
là cây rỗng (null tree)
• Cây mà mỗi nút chỉ có tối đa hai con gọi là cây nhị phân

4
CÁC KHÁI NIỆM
r

x1 x2 x3

x21 x22

5
CÁC KHÁI NIỆM

• Nút gốc không có cha, các nút không có con gọi là các
nút lá (leaf node), các nút còn lại là các nút trong
(internal node)
• Số con của một nút x trong cây T gọi là bậc (degree) của
x

6
CÁC KHÁI NIỆM

• Độ dài đường đi (số cạnh) từ nút gốc r đến một nút x gọi
là độ sâu (depth) của x trong T
• Độ sâu lớn nhất của các nút trong T gọi là chiều cao
(height) của T

7
CÁC KHÁI NIỆM
r Độ sâu 0

c Độ sâu 1
a Bậc của x là 2 x

Chiều cao h=3 d e Độ sâu 2

z Độ sâu 3

8
CÁC KHÁI NIỆM
• Một cây được gọi là cây m-phân nếu số con nhiều nhất
của một nút của cây là m
• Một cây được gọi là m-phân đầy đủ nếu mọi nút trong có
đúng m con
• Cây m-phân độ cao h được gọi là cân bằng nếu tất cả các
lá đều ở mức h hoặc h  1

9
CÁC KHÁI NIỆM

T2 không cân bằng


10
CÁC KHÁI NIỆM

• Một cây nhị phân là cân bằng nếu tại mọi nút của cây, độ
cao của cây con trái và cây con phải khác nhau không quá
1
• Nếu h là chiều cao của cây nhị phân cân bằng có n nút thì
h  1.44log2 n

11
BIỂU DIỄN CÂY NHỊ PHÂN
• Biểu diễn cây nhị phân
 Mỗi nút x, (ngoài thông tin dữ liệu), được biểu diễn bởi một khóa
và hai con trỏ left và right chỉ đến con trái và con phải của nó
trong cây nhị phân T
 Nếu left[x] = NULL thì x không có con trái
 Nếu right[x] = NULL thì x không có con phải
 Nút gốc của cây T được trỏ bởi root[T]
 Nếu root[T] = NULL thì cây T là rỗng

12
BIỂU DIỄN CÂY NHỊ PHÂN
• Nếu mỗi nút của cây biểu diễn một đối tượng có khóa là một số
nguyên, có thể định nghĩa CTDL cây nhị phân trong C++ như
sau

typedef struct CELL *TREE;


struct CELL {
int key; //trong thực tế có thêm nhiều dữ liệu khác
TREE left, right;
};
TREE T;

13
DUYỆT CÂY NHỊ PHÂN

• Có ba thứ tự duyệt cơ bản


 Duyệt theo trung thứ tự (inorder tree walk-LNR)
 Duyệt theo tiền thứ tự (preorder tree walk-NLR)
 Duyệt theo hậu thứ tự (postorder tree walk-LRN)

14
InorderTreeWalk
void InorderTreeWalk(TREE x) //LNR
{
if(x!=NULL)
{
InorderTreeWalk(x->left);
cout<<(x->key)<<' '; //thăm (xử lý) nút x
InorderTreeWalk(x->right);
}
}

15
CÂY NHỊ PHÂN TÌM KIẾM
(BINARY SEARCH TREE-BST)
• Là mô hình dữ liệu có nhiều ứng dụng trong thực tế
• Khoá của các phần tử được lưu trữ thỏa mãn tính chất cây nhị
phân tìm kiếm (binary search tree property)
 Nếu y là một nút trong cây con trái của nút x thì
key[y]< key[x]
 Nếu y là một nút trong cây con phải của nút x thì
key[y]> key[x]

16
CÂY BST

17
CÁC THAO TÁC TRÊN CÂY BST

• Khởi tạo cây


• Các thao tác tìm kiếm
• Các thao tác chèn và xóa

18
TreeInitialize
void TreeInitialize(TREE &T)
{
T=NULL;
}

19
CÁC THAO TÁC TÌM KIẾM

• TreeSearch: Tìm một phần tử theo khoá cho trước


• TreeMinimum: Tìm phần tử có khoá nhỏ nhất
• TreeMaximum: Tìm phần tử có khoá lớn nhất
• Độ phức tạp các thao tác tìm kiếm

20
CÁC THAO TÁC TÌM KIẾM

21
TreeSearch
• Đầu vào là một pointer x chỉ đến gốc của cây nhị phân tìm
kiếm và khoá k của phần tử cần tìm
• Thủ tục trả về pointer chỉ đến nút có khoá k hoặc NULL
• Dựa vào tính chất của cây nhị phân tìm kiếm để xác định nút
nào phải tìm kế tiếp trong các con trái và phải của x
 Nếu k < key[x], tìm tiếp trong cây con trái
 Ngược lại, tìm tiếp trong cây con trái

22
TreeSearch
TREE TreeSearch(TREE x,int k)
{
if(x==NULL||k==x->key) return x;
if(k<x->key) return TreeSearch(x->left,k);
else return TreeSearch(x->right,k);
}

23
TreeMinimum
• Đầu vào là một pointer x chỉ đến gốc của cây nhị phân tìm
kiếm
• Thủ tục trả về pointer chỉ đến nút có khoá nhỏ nhất
• Dựa vào tính chất của cây nhị phân tìm kiếm, phần tử có khoá
nhỏ nhất được tìm kiếm theo các pointer chỉ đến con trái bắt
đầu từ gốc cho đến khi pointer này là NULL

24
TreeMinimum
TREE TreeMinimum(TREE x)
{
if(x!=NULL) while(x->left!=NULL) x=x->left;
return x;
}

25
TreeMaximum

• Đầu vào là một pointer x chỉ đến gốc của cây nhị phân
tìm kiếm
• Thủ tục trả về pointer chỉ đến nút có khoá lớn nhất
• Phần tử có khoá nhỏ nhất được tìm kiếm theo các pointer
chỉ đến con phải bắt đầu từ gốc cho đến khi pointer này
là NULL

26
TreeMaximum
TREE TreeMaximum(TREE x)
{
if(x!=NULL) while(x->right!=NULL) x=x->right;
return x;
}

27
ĐỘ PHỨC TẠP

• Các thao tác tìm kiếm trên cây nhị phân tìm kiếm có độ
phức tạp là O(h), trong đó h là chiều cao của cây

28
CHÈN VÀ XOÁ

• TreeInsert: Chèn một nút vào cây


• TreeDelete: Xóa một nút trên cây
• Độ phức tạp thao tác chèn và xóa

29
TreeInsert

• Thủ tục TreeInsert(T, k) tìm một vị trí thích hợp để chèn


nút có khóa k vào cây T
• Nếu T là rỗng, nút có khóa k được chèn sẽ là nút gốc của
cây
• Ngược lại chèn nút có khóa k vào cây như là một nút con
của một nút lá của cây con bên trái hoặc bên phải của cây
T

30
TreeInsert
Chèn một phần tử có khoá 13 vào cây, các nút được
làm sáng chỉ đường đi từ gốc đến vị trí cần chèn

12

18

5 15 19

2 9 13 17

31
TreeInsert
void TreeInsert(TREE &T, int k)
{
if(T==NULL)
{
T=new(CELL); T->key=k; T->left=NULL; T->right=NULL;
}
else if(k<T->key) TreeInsert(T->left, k);
else if(k>T->key) TreeInsert(T->right, k);
}

32
TreeDelete
• TreeDelete(T, k) tìm và xoá nút z có khóa k, xét ba trường hợp
 Nếu z không có con, sửa con trỏ của cha z đến z thành
NULL
 Nếu z chỉ có một con, loại bỏ z bằng một liên kết mới giữa
cha và con của nó
 Nếu z có hai con, loại phần tử có khóa nhỏ nhất y trong cây
con phải của z và thay thế khóa (dữ liệu) của z bởi khóa (dữ
liệu) của y

33
TreeDelete

12 12

5 18 5 18

2 9 15 20 2 9 15 20

z 13 19 19

Xóa nút khóa 13, không có con

34
TreeDelete

12 12

5 18 5 18

z
2 9 15 20 2 9 13 20

13 19 19

Xóa nút khóa 15, có một con

35
TreeDelete

12 12 19

5 z 18 5 z 18

2 9 15 20 2 9 15 20

19 13 y 19
13

Xóa nút khóa 18, có hai con

36
TreeDelete

12 12

5 z 18 5 z 19

2 9 15 20 2 9 15 20

13 19 13

Xóa nút khóa 18, có hai con

37
TreeDelete
void TreeDelete(TREE &T, int k)
{
if(T!=NULL)
if(k<T->key) TreeDelete(T->left,k);
else if(k>T->key) TreeDelete(T->right,k);
else if(T->right==NULL) T=T->left;
else if(T->left==NULL) T=T->right;
else T->key=DeleteMin(T->right);
}
38
DeleteMin
int DeleteMin(TREE &T)
{
int min;
if(T->left==NULL)
{
min=T->key;
T=T->right;
return min;
}
else return DeleteMin(T->left);
}

39
ĐỘ PHỨC TẠP
• Định Lý 1: Các thao tác chèn (TreeInsert) và xóa
(TreeDelete) thực hiện trong thời gian O(h) trên một cây
nhị phân tìm kiếm độ cao h

40
ĐỘ PHỨC TẠP
• Độ cao của cây nhị phân tìm kiếm biến đổi thông qua các
thao tác chèn và xoá các nút trên cây
• Thời gian thao tác là O(h), vì vậy khi cây suy biến và có
chiều cao là n-1 (n là số nút trên cây) thì các thao tác này
sẽ không còn hiệu quả
• Xác suất để xẩy ra sự suy biến khi chèn, xoá một cách
ngẩu nhiên là rất nhỏ

41
ĐỘ PHỨC TẠP
• Định Lý 2: Độ cao trung bình của một cây nhị phân tìm
kiếm được xây dựng ngẩu nhiên trên n khoá phân biệt là
O(log2 n)
• Định Lý 3: Độ phức tạp trung bình của các thao tác tìm
kiếm, chèn và xóa trên một cây nhị phân tìm kiếm n nút
có khóa phân biệt là O(log2 n)

42
CÂY NHỊ PHÂN TÌM KIẾM CÂN BẰNG
• Định nghĩa
• Các trường hợp mất cân bằng
• Các thao tác cân bằng cho cây mất cân bằng
• Chèn một phần tử vào cây cân bằng
• Xóa một phần tử khỏi cây cân bằng

43
ĐỊNH NGHĨA
• Một cây nhị phân được gọi là cân bằng hoàn toàn nếu tại
mỗi nút, cây con trái và cây con phải của nó có số nút
chênh lệch nhau không quá 1

44
ĐỊNH NGHĨA
• Một cây nhị phân được gọi là cân bằng nếu tại mỗi nút,
cây con trái và cây con phải của nó có độ cao chênh lệch
nhau không quá 1
• Cây cân bằng còn được gọi là cây AVL (do các nhà khoa
học Adelson, Velskii và Landis đưa ra năm 1962)
• Chiều cao cây cân bằng n nút luôn luôn là O(log2n)

45
ĐỊNH NGHĨA
typedef struct AVLNode *AVLTree;
struct AVLNode {
int balFactor;
int key;
AVLTree pLeft, pRight;
};
AVLTree T;

46
ĐỊNH NGHĨA
• Các hằng số biểu diễn quan hệ về độ cao các cây con
#define LH -1 //Cây con trái cao hơn
#define EH 0 //Hai cây con có chiều cao bằng nhau
#define RH 1 //Cây con phải cao hơn

47
CÁC TRƯỜNG HỢP MẤT CÂN BẰNG
• Khi cây T mất cân bằng, chênh lệch độ cao các cây con
của T là 2
• Cây T lệch bên trái (3 trường hợp)
• Cây T lệch bên phải (3 trường hợp)

48
CÂY LỆCH BÊN TRÁI
Trường hợp 1 Trường hợp 2

T1 T1
h-1 h-1

h h-1 h h

Chiều cao của T là h+2

49
CÂY LỆCH BÊN TRÁI
Trường hợp 3

T1
h-1

h-1 h

Chiều cao của T là h+2

50
CÂY LỆCH BÊN PHẢI
Trường hợp 1 Trường hợp 2

h-1 T1
h-1 T1

h-1 h h h

Chiều cao của T là h+2

51
CÂY LỆCH BÊN PHẢI

Trường hợp 3

h-1 T1

h h-1

Chiều cao của T là h+2


52
CÂN BẰNG CÂY LỆCH BÊN TRÁI
Trường hợp 1

T1 T
h-1 h L1
R

h h-1 h-1 h-1


L1 R1 R1 R

T1 lệch trái, sử dụng phép quay Left-Left

53
CÂN BẰNG CÂY LỆCH BÊN TRÁI
Trường hợp 2

T1 T
h-1 h L1
R

h L1 R1 h h R1 R h-1

T1 không lệch, sử dụng phép quay Left-Left

54
CÂN BẰNG CÂY LỆCH BÊN TRÁI
Trường hợp 3 Xác định T2 là con T1

T1
T1 h-1
h-1 R
R
T2
h-1 L1 R1
h-1 L1 R1
h
h
L2 R2
T1 lệch phải

55
CÂN BẰNG CÂY LỆCH BÊN TRÁI
Trường hợp 3

T1
h-1 T1 T
R
T2
h-1 L1 R1
h-1 L1 L2 R2 R h-1
h
L2 R2

T1 lệch phải, sử dụng phép quay Left-Right


56
CÂN BẰNG CÂY LỆCH BÊN TRÁI
• Lưu ý sau khi cân bằng
 Trường hợp 1 và 3 cây có chiều cao h+1
 Trường hợp 2 cây có chiều cao h+2 và độ cao cây con trái
và phải của nút T (cũ) là khác nhau

57
QUAY ĐƠN LEFT-LEFT
Trường hợp 1

T1 T
h-1 h L1
R

h h-1 h-1 h-1


L1 R1 R1 R

T1 lệch trái, sử dụng phép quay Left-Left

58
QUAY ĐƠN LEFT-LEFT
T1
Ví dụ T
50 40

T1 40 T
30 50
60

30 45 45 60
20

20
T1 lệch trái, sử dụng phép quay Left-Left (trường hợp 2)

59
CÂN BẰNG CÂY LỆCH BÊN TRÁI
Trường hợp 2

T1 T
h-1 h L1
R

h L1 R1 h h R1 R h-1

T1 không lệch, sử dụng phép quay Left-Left

60
QUAY ĐƠN LEFT-LEFT
T1
Ví dụ T
50 40

T1 40 T
30 50
60

30 45 45 60
20

20 48 48

T1 lệch trái, sử dụng phép quay Left-Left (trường hợp 1)


61
QUAY ĐƠN LEFT-LEFT
void rotateLL(AVLTree &T)
{ AVLTree T1=T->pLeft;
T->pLeft=T1->pRight;
T1->pRight=T;
switch(T1->balFactor) {
case LH: T->balFactor=EH; T1->balFactor=EH; break;
case EH: T->balFactor=LH; T1->balFactor=RH; break;
}
T=T1;
}

62
QUAY ĐƠN RIGHT-RIGHT
void rotateRR(AVLTree &T)
{ AVLTree T1=T->pRight;
T->pRight=T1->pLeft;
T1->pLeft=T;
switch(T1->balFactor) {
case RH: T->balFactor=EH; T1->balFactor=EH; break;
case EH: T->balFactor=RH; T1->balFactor=LH; break;
}
T=T1;
}

63
CÂN BẰNG CÂY LỆCH BÊN TRÁI
Trường hợp 3

T1
h-1 T1 T
R
T2
h-1 L1 R1
h-1 L1 L2 R2 R h-1
h
L2 R2

T1 lệch phải, sử dụng phép quay Left-Right


64
CÂN BẰNG CÂY LỆCH BÊN TRÁI
T2
Ví dụ T 40
50

T1 30
60 T1 T
30 50
T2
20 40
20 35 45 60
35 45

T1 lệch phải, sử dụng phép quay Left-Right


65
QUAY KÉP LEFT-RIGHT
void rotateLR(AVLTree &T)
{ AVLTree T1=T->pLeft, T2=T1->pRight;
T->pLeft=T2->pRight;
T2->pRight=T;
T1->pRight=T2->pLeft;
T2->pLeft=T1;
switch(T2->balFactor) {
case LH: T->balFactor=RH; T1->balFactor=EH; break;
case EH: T->balFactor=EH; T1->balFactor=EH; break;
case RH: T->balFactor=EH; T1->balFactor=LH; break;
}
T2->balFactor=EH;
T=T2;
}
66
QUAY KÉP RIGHT-LEFT
void rotateRl(AVLTree &T)
{ AVLTree T1=T->pRight, T2=T1->pLeft;
T->pRight=T2->pLeft;
T2->pLeft=T;
T1->pLeft=T2->pRight;
T2->pRight=T1;
switch(T2->balFactor) {
case RH: T->balFactor=LH; T1->balFactor=EH; break;
case EH: T->balFactor=EH; T1->balFactor=EH; break;
case LH: T->balFactor=EH; T1->balFactor=RH; break;
}
T2->balFactor=EH;
T=T2;
}
67
HÀM CÂN BẰNG KHI CÂY LỆCH TRÁI
void balanceLeft(AVLTree &T)
{ AVLTree T1=T->pLeft;
switch(T1->balFactor) {
case LH: rotateLL(T);
case EH: rotateLL(T);
case RH: rotateLR(T);
}
}

68
HÀM CÂN BẰNG KHI CÂY LỆCH PHẢI
void balanceRight(AVLTree &T)
{ AVLTree T1=T->pRight;
switch(T1->balFactor) {
case LH: rotateRL(T);
case EH: rotateRR(T);
case RH: rotateRR(T);
}
}

69
CHÈN MỘT PHẦN TỬ VÀO CÂY AVL
• Tìm và chèn tương tự như trên cây nhị phân tìm kiếm
• Sau khi chèn phải cân bằng lại
• Thao tác insertNode trả về -1, 0, 1 khi không đủ bộ nhớ,
gặp nút đã có hoặc chèn thành công (chiều cao không
tăng)
• Thao tác insertNode trả về 2 nếu chèn thành công và
chiều cao tăng

70
CHÈN MỘT PHẦN TỬ VÀO CÂY AVL
int insertNode(AVLTree &T, int x)
{ int res;
if(T) { if(T->key==x) return 0;
if(T->key>x){
res= insertNode(T->pLeft, x);
if(res<2) return res;
switch(T->balFactor) {
case RH: T->balFactor=EH; return 1;
case EH: T->balFactor=LH; return 2;
case LH: balanceLeft(T); return 1;
}
} else

71
CHÈN MỘT PHẦN TỬ VÀO CÂY AVL
int insertNode(AVLTree &T, int x)
{ int res;
if(T) { if(T->key==x) return 0;
if(T->key>x){ //-------; các lệnh đã có
} else { res= insertNode(T->pRight, x);
if(res<2) return res;
switch(T->balFactor) {
case LH: T->balFactor=EH; return 1;
case EH: T->balFactor=RH; return 2;
case RH: balanceRight(T); return 1;
}
}
} //kết thúc T khác rỗng
72
CHÈN MỘT PHẦN TỬ VÀO CÂY AVL
int insertNode(AVLTree &T, int x)
{ int res;
if(T) {
//….; các lệnh đã có
} //kết thúc T khác rỗng
T=new(AVLNode);
if(T==NULL) return -1; //thiếu bộ nhớ
T->key=x; T->balFactor=EH;
T->Pleft=T->pRight=NULL;
return 2; //chiều cao tăng
}

73
XÓA MỘT PHẦN TỬ KHỎI CÂY AVL
• Tìm và xóa tương tự như trên cây nhị phân tìm kiếm
• Sau khi xóa phải cân bằng lại
• Thao tác delNode trả về 1, 0 khi xóa thành công hoặc
không có x trong cây (chiều cao không giảm)
• Thao tác delNode trả về 2 nếu xóa thành công và chiều
cao giảm
• Tham khảo chương trình (hàm delNode) trong sách
CTDL&GT của Trần Hạnh Nhi và Dương Anh Đức

74
ĐỘ PHỨC TẠP
• Thao tác cân bằng tại một nút có độ phức tạp là O(1)
• Thao tác thêm và xóa có độ phức tạp là O(h)
• Nếu cây có n nút, độ phức tạp thêm và xóa là O(log2 n)

75
BIỂU DIỄN CÂY ĐA PHÂN
• Cơ chế biểu diễn cây nhị phân có thể mở rộng cho cây đa
phân
• Nếu mỗi nút có tối đa k con thì có thể dùng k+1 biến là p
(chỉ đến cha) và child1, child2, ..., childk (chỉ đến k con)
• Phương pháp này không hiệu quả (tốn nhiều bộ nhớ) và
không sử dụng được nếu số con tối đa của một nút quá
lớn

76
BIỂU DIỄN CÂY ĐA PHÂN
• Sử dụng 3 biến p, left-child, right-sibling để lưu trữ các pointer
chỉ đến cha, con trái trái nhất và anh, em bên phải của mỗi nút
trong T
• root[T] là pointer chỉ đến gốc của T
• left-child[x] chỉ đến con trái nhất của x
• right-sibling[x] chỉ đến anh em trực tiếp bên phải của x
• Nếu x không có con left-child[x] = NIL
• Nếu x là con phải nhất thì right-sibling[x] = NIL

77
BIỂU DIỄN CÂY ĐA PHÂN

78
CÁC BIỂU DIỄN KHÁC CỦA CÂY
• Có thể biểu diễn cây có gốc như một đồ thị có hướng
• Có thể biểu diễn cây có gốc mà không cần pointer chỉ đến
cha của nó
• Ngoài ra có thể dùng một mảng một chiều để biểu diễn
cây có gốc

79
ĐỌC VÀ TÌM HIỂU Ở NHÀ
• Đọc chương 12 sách Introduction to Algorithms của
Thomas H. Cormen, Charles E. Leiserson, Ronald D.
Rivest.

• Đọc thêm chương 8 sách C++ Plus Data Structures của


Nell Dale

80

You might also like