Chương 5. Cây nhị phân tìm kiếm

Tr n Minh Thái Email: minhthai@huflit.edu.vn Website: www.minhthai.edu.vn

1

Nội dung

1.

Khái niệm

2.

Đặc điểm

3.

Định nghĩa cấu trúc dữ liệu

4.

Các lưu ý khi cài đặt

5.

Các thao tác xử lý

2

Khái niệm

• Bậc của một nút: là số cây

con của nút đó

2

2

2

• Bậc của cây: là bậc lớn nhất của các nút trong cây. Cây có bậc n thì gọi là cây n- phân

1

0

1

0

• Nút gốc: là nút không có nút

cha

0

0

• Nút lá: là nút có bậc bằng 0

• Nút nhánh: là nút có bậc khác 0 và không phải là gốc

3

Khái niệm

• Chiều dài đường đi đến nút x: là số nhánh cần đi qua kể từ gốc đến x

M c 1ứ

• Chiều cao h của cây: Mức lớn nhất của các nút lá

M c 2ứ

M c 3ứ

M c 4ứ

x

4

Đặc điểm của cây nhị phân

2I-1

2h-1, với h là chiều cao của cây

• Số nút ở mức I (cid:0) • Số nút ở mức lá (cid:0) • Chiều cao của cây h (cid:0)

log2N, với N là số nút trong cây

5

Biểu diễn cây nhị phân

• Cây nhị phân là một cấu trúc bao gồm các phần tử (node) được kết nối với nhau theo quan hệ “cha-con” với mỗi cha có tối đa 2 con.

• Mỗi nút gồm các thông tin:

• Dữ liệu lưu trữ: data

• Liên kết tới cây con trái của nút: pLeft

• Liên kết tới cây con phải của nút: pRight

6

Định nghĩa kiểu dữ liệu dùng C

data Giá trị

pLeft

pRight

ỏ Tr  trái

ả Tr  ph i

typedef struct TNode {

DataType data; TNode *pLeft, *pRight;

} *Tree;

7

TNODE Nút

Ví dụ khai báo node chứa giá trị nguyên

typedef struct TNode

{

int data;

TNode *pLeft, *pRight;

} *Tree;

8

Cây nhị phân tìm kiếm

• Là 1 cây nhị phân

• Giá trị của một node luôn

7

lớn hơn giá trị của các

node nhánh trái và nhỏ

3 36

1 6 15 40

hơn giá trị các node nhánh

phải

Nút có giá trị nhỏ nhất nằm

ở nút trái nhất của cây 9

Nút có giá trị lớn nhất nằm

ở nút phải nhất của cây

4 23

Các lưu ý khi cài đặt

ướ

ể ữ ệ

B c 1:

Khai báo ki u d  li u bi u di n cây

ướ B c 2:

ư ữ ệ  Xây d ng hàm đ a d  li u (nh p) vào cây

ướ

ế

B c 3:

Xây d ng các thao tác duy t, tìm ki m, hu , …

10

Cấu trúc chương trình

Khai báo cấu trúc cây

Khởi tạo cây rỗng

Xây dựng cây

Các thao tác

Hủy cây

11

Các thao tác cơ bản

1. Tạo cây

2. Duyệt cây

3. Cho biết các thông tin của cây

4. Tìm kiếm

5. Xoá node trên cây

12

Tạo cây

7

36

3

1

6

4

15

40

Ø Nếu node cần thêm

nhỏ hơn node đang

xét thì thêm về bên

trái

Ø Ngược lại thì thêm về

bên phải

13

7 4015461363

Hàm thêm một phần tử vào cây

int InsertNode (Tree & t, int x) {

ị if(t!=NULL) { return 0; //Có giá tr  trùng

if(x==t­>data)  else { InsertNode(t­>pLeft, x);

if(xdata) else InsertNode(t­>pRight, x);

}

} else {

ế ộ ớ return ­1; //Thi u b  nh

t=new TNode; if(t==NULL)  t­>data=x; t­>pLeft=t­>pRight=NULL; return 1; //Thêm thành công

14

}

}

Bài tập

1.

Viết hàm tạo cây nhị phân số nguyên từ các giá trị nhập vào từ bàn phím. Quá trình nhập cho đến khi gặp giá trị trùng hoặc hết bộ nhớ

2.

Viết hàm tạo cây nhị phân số nguyên từ dãy số nguyên cho trước

15

Duyệt cây

Thứ tự trước

(NLR)

Thứ tự giữa

(LNR)

Thứ tự sau

(LRN)

16

ế

ả B cướ K t qu  duy t theo th  t

ứ ự  NLR 3

7

7 L7 R7

3

36

L3 R3 R7 1 R3 R7

1 6 15 40

6 L6 R7 4 R7

36 L36 R36

1 2 3 4 5 6 7 8 9

KQ 7

3

1

6

4

36

15 R15 R36 23 R36 40 40

23

15

17

4 23

Hàm duyệt NLR

Tại node t đang xét, nếu khác rỗng thì

• In giá trị của t

void NLR (Tree t) {

• Duyệt cây con bên trái của t

theo thứ tự NLR

• Duyệt cây con bên phải của t

if(t!=NULL) {

theo thứ tự NLR

cout<Key<<“\t”; NLR(t->pLeft); NLR(t->pRight);

}

18

}

Bài tập

Vẽ cây nhị phân tìm kiếm theo thứ tự nhập từ trái sang phải và duyệt cây theo thứ tự trước:

• 27; 19; 10; 21; 35; 25; 41; 12; 46; 7

• H; B; C; A; E; D; Z; M; P; T

• Huế; Đà Nẵng; Hà Nội; Vĩnh Long; Cần Thơ; Sóc Trăng; Nha Trang; Đồng Nai; Vũng Tàu; An Giang; Tiền Giang; Bình Dương; Hải Dương

19

ứ ự ệ ế LNR 7

L7 L3 3 36

ả B cướ K t qu  duy t theo th  t 7 3 3 1

3 6 15 40

R7 R3 R3 R3 L6

7 7 7 6 6 4 23 4

R7 R7 R7 7 7 7 6

7

36 R36

1 R7 R7 R7 R7 L36 15 R15 23

1 2 3 4 5 6 7 8 9 10 11 12 13

36 R36 36 R36 36 R36 40 40 KQ 1 3 4 6 7 15 23 36 20

Hàm duyệt LNR

Tại node t đang xét, nếu khác rỗng thì

• Duyệt cây con bên trái của t

theo thứ tự LNR

void LNR (Tree t) {

• In giá trị của t

• Duyệt cây con bên phải của t

theo thứ tự LNR

if(t!=NULL) {

LNR(t->pLeft); cout<Key<<“ “; LNR(t->pRight);

}

21

}

ứ ự

ế

ả B cướ K t qu  duy t theo th  t

LRN

7

3 36

6 15 40 1

L7 R7 7 L3 R3 3 R7 7 R3 3 R7 7 1 L6 6 3 6 3 4 6 3 3

R7 7 R7 7 R7 7 7 R7 L36 R36 36 R15 15 15 23 15

22

1 2 3 4 5 6 7 8 9 10 11 12 13 14 KQ 1

7 R36 36 7 R36 36 7 R36 36 7 36 7 40 36 7 7 36 7

40

4

6 3

23

15

4 23

Hàm duyệt LRN

Tại node t đang xét, nếu khác rỗng thì

void LRN (Tree t) {

• Duyệt cây con bên trái của t theo thứ tự LRN

• Duyệt cây con bên phải của t theo thứ tự LRN

if(t!=NULL) {

LRN(t->pLeft); LRN(t->pRight); cout<Key<<“

• In giá trị của t

“;

}

23

}

Bài tập

• Vẽ cây nhị phân tìm kiếm theo thứ tự nhập:

27, 19, 10, 21, 3, 15, 41, 50, 30, 7

Hãy duyệt cây trên theo thứ tự giữa

• Vẽ cây nhị phân tìm kiếm theo thứ tự nhập:

H, B, C, A, E, D, T, M, X, O

Hãy duyệt cây trên theo thứ tự sau

24

Kiểm tra kết quả duyệt bằng cách xây dựng lại cây nhị phân từ kết quả duyệt Tạo cây từ kết quả duyệt NLR

•Chọn giá trị đầu tiên làm node gốc

•Lần lượt đưa các giá trị còn lại từ trái sang phải vào cây theo nguyên tắc tạo cây

Tạo cây từ kết quả duyệt LRN

•Chọn giá trị cuối cùng làm node gốc

•Lần lượt đưa các giá trị còn lại từ phải sang trái vào cây theo nguyên tắc tạo cây

25

Bài tập

Vẽ cây nhị phân tìm kiếm T biết rằng khi duyệt cây T theo thứ tự NLR thì được dãy sau: 9, 4, 1, 3, 8, 6, 5, 7, 10, 14, 12, 13, 16, 19

• Hãy duyệt cây T trên theo thứ tự LRN

• Liệt kê các nút lá của cây. Liệt kê các nút nhánh của cây

26

Bài tập

Vẽ cây nhị phân tìm kiếm T biết rằng khi duyệt cây T theo thứ tự LRN thì được dãy sau: 1, 4, 7, 5, 3, 16, 18, 15, 29, 25, 30, 20, 8

• Hãy duyệt cây T trên theo thứ tự NLR

• Cây T có chiều cao là bao nhiêu? Tìm các đường đi từ gốc có độ dài là 4 trên cây

27

Tìm kiếm

1.

Tìm x

2.

Tìm min

3.

Tìm min của cây con bên phải

4.

Tìm max

5.

Tìm max của cây con bên trái

28

Ví dụ tìm x = 23

7

3 36

1 6 15 40

29

4 23

Bài tập

Cho cây nhị phân tìm kiếm, mỗi node có giá trị nguyên, hãy định nghĩa các hàm sau:

• Tìm node có giá trị x

• Tìm node có giá trị lớn nhất

• Tìm node có giá trị nhỏ nhất của cây con phải

30

Bài tập

Cho cây nhị phân tìm kiếm, mỗi node có giá trị nguyên, hãy định nghĩa các hàm sau:

1. In ra các node có giá trị chẵn

2. In ra các node có giá trị lớn hơn x

3. Đếm số node của cây

4. Tính độ cao của cây

31

Bài tập

5. Đếm số node lá (node bậc 0)

6. Đếm số node có 1 cây con (node bậc 1)

7. Đếm số node chỉ có 1 cây con phải

8. Đếm số node có 1 cây con trái

9. Đếm số node 2 cây con (node bậc 2)

10. In các node trên từng mức của cây

11. Cho biết độ dài đường đi từ gốc đến node x

32

Xóa node trên cây

1.

Node lá

7

2.

Node có 1 cây con

3.

Node có 2 cây con

3 36

1 6 15 40

33

4 23

Xóa node lá

7

Xóa 1

Xóa 23

3 36

1 6 15 40

34

4 23

Xóa node 1 cây con

Node cha của 6

Xóa 6

7

3 36

1 6 15 40

35

4 23

Xóa node 1 cây con

Xóa 15

7

3 36

1 6 15 40

36

4 23

Xóa node có 2 cây con

Bước 1: Tìm node thế

mạng

• Cách 1: Tìm node trái nhất của cây con phải

7

• Cách 2: Tìm node phải

nhất của cây con trái

3 36

1 6 15 40

4 23

Bước 2: Thay giá trị của node thế mạng vào node cần xóa

16

Bước 3: Xóa node thế mạng

37

Xóa node 2 cây con

Xóa 36

Bước 1: Tìm node thế

mạng

7

3 36

Node thế mạng

1 6 15 40

4 23

38

16

Xóa node 2 cây con

Xóa 36

7

Bước 2: Thay giá trị node thế mạng cho node cần xóa

Cập nhật giá trị

3 23

Node thế mạng

1 6 15 40

4 23

39

16

Xóa node 2 cây con

Xóa 36

7

Bước 3: Xóa node thế mạng

3 23

Xóa

1 6 15 40

4 23

40

16

Xóa node 2 cây con

Kết quả: xóa 36

7

3 23

1 6 15 40

41

16 4

Bài tập

Cho dãy số theo thứ tự nhập từ trái sang phải: 20, 15, 35, 30, 11, 13, 17, 36, 47, 16, 38, 28, 14

• Vẽ cây nhị phân tìm kiếm cho dãy số trên

• Trình bày từng bước và vẽ lại cây sau khi lần lượt xoá

các nút: 11 và 35

42

Bài tập

• Viết hàm xóa node có giá trị x trong cây nhị phân số nguyên

43

void Remove(Tree & t, int x) {

if (t != NULL) {

if (x < t->key)

Remove(t->pLeft, x);

else if (x > t->key)

Remove(t->pRight, x);

else {

TNode * pHuy = t; if (t->pLeft == NULL) t = t->pRight;

else if (t->pRight == NULL)

t = t->pLeft;

else SearchStandFor(pHuy, t->pRight);

delete pHuy;

}

}

44

}

void SearchStandFor(Tree &pHuy, Tree &pTM) {

if(pTM->pLeft)

SearchStandFor(pHuy, pTM->pLeft);

else {

pHuy->key=pTM->key; pHuy=pTM; pTM=pTM->pRight;

}

}

45

Đánh giá

• Thao tác tìm kiếm, thêm mới, xóa đều có độ phức

tạp trung bình O(h), với h là chiều cao của cây

• Trường hợp tốt nhất, cây có n nút sẽ có độ cao h = log2(n). Chi phí tìm kiếm sẽ tương đương tìm kiếm nhị phân trên mảng có thứ tự

• Trường hợp xấu nhất, cây có thể bị suy biến thành 1 DSLK. Lúc đó các thao tác trên sẽ có độ phức tạp O(n)

 Vì vậy cần có cải tiến cấu trúc của CNPTK để đạt được chi phí cho các thao tác là log2(n)

46

Cây nhị phân tìm kiếm cân bằng

• Phát minh: Nhà toán học Nga G. M. Adel’Son-

Vel’Skil và E. M. Landis (1962)

• Cấu trúc cây giúp tối ưu thời gian tìm kiếm

• Cây nhị phân tìm kiếm cân bằng: cây AVL

• Chi phí tìm kiếm, thêm mới, xoá trong cây n nút là

O(log n)

47

Định nghĩa

• Cây AVL là một cây nhị phân tìm kiếm

• Chiều cao cây con trái và phải của nút gốc hơn

kém nhau không quá 1

• Cả hai cây con trái và phải cũng phải là cây AVL

48

Chỉ số cân bằng (Balance factor)

Chỉ số cân bằng (bF – balance Factor) của một nút:

bF = hR – hL

ü hL: chiều cao cây con trái

ü hR: chiều cao cây con phải

Có 3 trường hợp:

ØbF = 0: hL = hR (Ký hiệu EH: Equal Height)

ØbF > 0: hL < hR (Ký hiệu RH: Right Higher)

ØbF < 0: hL > hR (Ký hiệu LH: Left Higher)

49

Khai báo cấu trúc cây AVL

typedef struct AVLNode {

DataType key;

char bF;

AVLNode* pLeft;

AVLNode* pRight;

} *AVLTree;

const char LH = -1; const char EH = 0; const char RH = 1;

50

Trường hợp 1: Lệch trái

51

Trường hợp 2: Lệch phải

52

Trường hợp 1.1: T1 lệch trái

ơ

Quay đ n Left ­ Left

53

Trường hợp 1.2: T1 không lệch

ơ

Quay đ n Left ­ Left

54

void RotateLL ( AVLTree &T)  {              AVLNode * T1 = T­> pLeft ;              T­>pLeft      = T1­>pRight ;              T1­> pRight = T;              switch(T1­> bF)              {                           case LH: T­> bF = EH;                                        T1­> bF = EH;                                        break ;                           case EH: T­> bF = LH;                                        T1­> bF = RH;                                        break ;              }              T = T1;  }

Trường hợp 1.3: T1 lệch phải

Quay kép Left ­ Right

56

void RotateLR(AVLTree &T) { AVLNode * T1 = T-> pLeft ; AVLNode * T2 = T1-> pRight ; T-> pLeft = T2-> pRight ; T2-> pRight = T; T1-> pRight = T2-> pLeft ; T2-> pLeft = T1; switch (T2-> bF) { case LH: T-> bF = RH; T1-> bF = EH; break ; case EH: T-> bF = EH; T1-> bF = EH; break ; case RH: T-> bF = EH; T1-> bF = LH; break ; } T2-> bF = EH; T = T2; }

Trường hợp 2.1 và 2.2

void RotateRR(AVLTree &T) {              AVLNode * T1 = T­> pRight ;              T­>pRight = T1­> pLeft ;              T1­> pLeft = T;              switch (T1­> bF)              {                           case RH: T­> bF = EH;                                        T1­> bF = EH;                                        break ;                           case EH: T­> bF = RH;                                        T1­> bF = LH;                                        break ;              }              T = T1;  }

58

Trường hợp 2.3

void RotateRL ( AVLTree &T)  {              AVLNode * T1 = T­> pRight ;              AVLNode * T2 = T1­> pLeft ;              T­>pRight = T2­> pLeft ;              T2­>pLeft = T;              T1­>pLeft = T2­> pRight ;              T2­> pRight = T1;              switch (T2­> bF)              {                           case RH: T­> bF = LH;                                        T1­> bF = EH;                                        break ;                           case EH: T­> bF = EH;                                        T1­> bF = EH;                                        break ;                           case LH: T­> bF = EH;                                        T1­> bF = RH;                                        break ;              }              T2­> bF = EH;              T = T2;  }

59

Thêm 1 node vào cây AVL

• Tương tự như trên cây NPTK

• Sau khi thêm xong, nếu chiều cao của cây thay đổi. Nếu có mất cân bằng  cân bằng lại ở nút này

• Hàm insertNode trả về giá trị

• -1: không đủ bộ nhớ

• 0: trùng giá trị

• 1: thêm thành công

• 2: thêm thành công và chiều cao tăng

60

int InsertNode(AVLTree &T, int X) { int kq; if (T != NULL ) { if(T->key == X) return 0; // tồn tại x else if (T->key > X) // thêm về bên trái { kq = InsertNode(T->pLeft, X); if (kq < 2) return res; switch (T->bF) { case RH: T->bF= EH; return 1; case EH: T->bF = LH; return 2; case LH: BalanceLeft(T); return 1; } }

61

else //Trường hợp T->key pRight, X); if (res < 2) return res; switch (T->bF) { case LH: T->bF = EH; return 1; case EH: T->bF = RH; return 2; case RH: BalanceRight(T); return 1; } } }

62

//Tạo node mới

T = new TNode; if (T == NULL) return -1; //thiếu bộ nhớ

T->key = X; T->bF = EH; T->pLeft = T->pRight = NULL;

return 2; //Thêm thành công và chiều cao tăng }

63

int BalanceLeft(AVLTree &T) { switch (T-> pLeft -> bF) { case LH: RotateLL (T); return 2; case EH: RotateLL (T); return 1; case RH: RotateLR (T); return 2; } return 0; } int BalanceRight(AVLTree &T) { switch (T-> pRight -> bF) { case LH: RotateRL (T); return 2; case EH: RotateRR (T); return 1; case RH: RotateRR (T); return 2; } return 0; }

Hủy 1 node trên cây

• Tương tự như trên cây NPTK

• Sau khi hủy, nếu mất cân bằng  cân bằng lại

• Hàm deleteNode trả về giá trị

• 1: hủy thành công

• 0: không có X trong cây

• 2: hủy thành công và chiều cao của cây giảm

65

66

int DeleteNode(AVLTree &T, int X) {              int kq ;              if (T==NULL)                           return 0;              if (T­>key > X)              {                           kq = DeleteNode (T­> pLeft , X);                           if ( kq < 2)                                        return kq ;                           switch ( T­> bF )                           {                                        case LH: T­> bF = EH;                                                  return 2;                                        case EH: T­> bF = RH;                                                     return 1;                                        case BalanceRight (T);                           }              }

else if (T->key < X) { kq = DeleteNode (T-> pRight , X); if ( kq < 2) return kq ; switch (T-> bF ) { case RH: T-> bF = EH; return 2; case EH: T-> bF = LH; return 1; case LH: return BalanceLeft (T); } }

67

else // if (T­>key == X)              {                                                     AVLNode * p = T;                           if (T­> pLeft == NULL)                           {                                        T = T­>pRight ; kq = 2;                           }                           else if (T­> pRight == NULL)                           {                                        T = T­> pLeft ; kq = 2;                           }

68

69

else // if (T­> pLeft != NULL && T­> pRight != NULL)                           {                                        kq = SearchStandFor ( p, T ­> pRight );                                        if( kq < 2)                                                     return kq;                                        switch( T­> bF )                                        {                                                     case RH: T­> bF = EH;                                                                  return 2;                                                     case EH: T­> bF = LH;                                                                  return 1;                                                     case LH: return BalanceLeft (T);                                        }                           }                           delete p;                           return kq ;              }  }

70

int SearchStandFor(AVLTree &p, AVLTree &q){              int kq;             if (q­>pLeft != NULLL) {                         kq = SearchStandFor(p, q­>pLeft);                         if (kq < 2) return kq;                         switch (q­>bF){                                     case LH: q­>bF= EH;                                                 return 2;                                     case EH: q­>bF= RH;                                                 return 1;                                     case RH: return BalanceRight(T);                          }             } else {                         p­>key = q­>key;                         p = q;                         q = q­>pRight;                         return 2;             } }