tài liệu tham khảo khoa toán tin

27 8 0
tài liệu tham khảo khoa toán tin

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

Thông tin tài liệu

Việc xóa một phần tử ra khỏi cây AVL diễn ra tương tự như đối với cây nhị phân tìm kiếm; chỉ khác là sau khi hủy, nếu cây AVL bị mất cân bằng, ta phải cân bằng lại cây. Việc cân bằng l[r]

(1)

Chương IV

CẤU TRÚC CÂY

Trong cấu trúc liệu động tổ chức theo kiểu danh sách liên kết, có ưu điểm thao tác chèn, xóa, tốc độ thực trong các thao tác truy cập đến phần tử hay tìm kiếm thường chậm Để khắc phục nhược điểm trì ưu điểm cấu trúc liệu động thao tác chèn, xóa, ta dùng cấu trúc liệu động khác là tìm kiếm xét chương để lưu trữ khai thác liệu hiệu

IV.1 Định nghĩa khái niệm bản IV.1.1 Định nghĩa cây

Cây tập hợp N phần tử gọi nút (hay đỉnh), có duy nhất đỉnh đặc biệt gọi gốc, tập hợp cạnh có hướng A (A

NxN) nối cặp nút với gọi cung hay nhánh Mỗi nút được

nối với gốc dãy cặp cung liên liếp nút gốc ; mức

cha 5,6,7; mức

nút

4 mức

nút (con 4); mức

(Cây tam phân, có chiều cao 4)

Bậc nút 2, bậc nút 1, bậc nút 3, bậc nút

IV.1.2 Các khái niệm khác

* Mỗi cung = (ni , ni+1)  A có hai nút đầu, nút ni gọi cha, nút dưới ni+1 gọi

* Nút gốc nút (duy nhất) khơng có nút cha Mọi nút khác có nút

cha

* Một đường p từ n1 đến nk dãy đỉnh {n1, n2, … , nk} cho: = (ni , ni+1)  A,  i = 1, , k-1

* Độ dài đường Lx,y từ x đến y số cung đường từ x đến y Ký hiệu Lx độ dài đường từ gốc đến x

(2)

(  Lx )/n, n số nút hay số phần tử N x  N

trong đó, Lx độ dài đường từ gốc đến đỉnh x.

* Mọi nút khác gốc nối với gốc đường bắt đầu từ gốc kết thúc nút Trong khơng có chu trình

* Bậc nút số nút

* Bậc bậc lớn nút Cây bậc n gọi n

-phân

* Nút nút có bậc lớn khơng Nút nút có bậc khơng Mỗi nút với tạo thành

* Mức nút (khác nút gốc) số đỉnh đường từ gốc đến nút đó. Mức nút gốc 1:

Mức(gốc) = 1;

Mức(con) = Mức(cha) + 1,  (cha,con)  A * Chiều cao mức lớn nút

* Ví dụ: có nhiều ứng dụng để biểu diễn loại liệu thực tế Chẳng hạn:

- Biểu thức số học: ((a*b)+c)/((d*e)+(f-g)) biểu diễn dạng Ta biểu diễn: toán tử nút gốc tóan hạng nút

/

+ +

* c *

-a b d e f g

- Sơ đồ tổ chức quốc gia, địa phương hay quan có dạng

- Mục lục sách theo hệ thống phân loại đó, …

* Cây có thứ tự : mà nút xếp theo thứ tự và có để ý đến vị trí (thứ tự) nút

Trong có thứ tự ta thay đổi vị trí ta có Chẳng hạn, hai có thứ tự sau xem khác nhau:

+ +

* c c *

a b a b

(3)

* Từ có tổng quát (cây n- phân) ta chuyển nhị phân (xem II.6.) nghĩa dùng nhị phân để biểu diễn tổng quát Do tính chất đơn giản tầm quan trọng vậy, trước hết ta khảo sát nhị phân

IV.2 Cây nhị phân

IV.2.1 Định nghĩa: nhị phân (có thứ tự) mà số lớn nút con nút 2.

Ta cịn xem nhị phân cấu trúc liệu đệ qui * Định nghĩa đệ qui: Một nhị phân (Binary tree) :

+ rỗng ( phần neo hay trường hợp sở);

+ nút mà có nhị phân khơng giao nhau, gọi cây bên trái bên phải (phần đệ qui)

IV.2.2 Vài tính chất nhị phân

Gọi h n chiều cao số phần tử nhị phân. - Số nút mức i  2i-1, hay nói xác số nút tối đa mức i 2i-1. Do đó, số nút tối đa 2h-1.

- Số nút tối đa nhị phân 2h –1, hay n  2h –1. Do đó, chiều cao nó: n  h  log2(n+1)

IV.2.3 Biểu diễn nhị phân

Ta chọn cấu trúc động để biểu diễn nút nhị phân:

LChild RChild Data

trong đó: LChild, RChild trỏ đến nút bên trái nút phải LChild hay RChild trỏ rỗng khơng có nút bên trái hay bên phải

Nút có dạng:

LChild RChild  Data 

Trong ngôn ngữ C hay C++, ta khai báo kiểu liệu cho nút cây nhị phân sau:

typedef ElementType; /* Kiểu mục liệu nút */

(4)

struct TN * LChild, *RChild;  TreeNode;

typedef TreeNode *TreePointer;

* Ví dụ: Ta biểu diễn biểu thức số học: a * b + c nhị phân:

+

* c

a b

+ Nút gốc *  c 

 a   b 

Trong thuật toán thuộc chương này, ta sử dụng hàm CấpPhát() để cấp phát vùng nhớ cho nút nhị phân Hàm trả địa bắt đầu vùng nhớ đựoc cấp phát cho nút việc cấp phát thành công trả trị NULL ngược lại Trong C++, hàm viết sau:

TreePointer CấpPhát ()

{TreePointer Tam= new TreeNode; if (Tam == NULL)

cout << “\nLỗi cấp phát vùng nhớ cho nút nhị phân !”;

return Tam; }

IV.2.4 Duyệt nhị phân

IV.2.4.1 Định nghĩa: Duyệt qua nhị phân quét qua nút cây nhị phân cho nút xử lý lần

Dựa vào định nghĩa đệ qui ta chia nhị phân làm phần: gốc, bên trái, bên phải Ta có phương pháp duyệt nhị phân tùy

theo trình tự duyệt phần trên:

(5)

L : quét trái nút R : quét phải nút N : xử lý nút

IV.2.4.2 Các thuật toán duyệt nhị phân

* Thuật toán duyệt qua theo thứ tự (LNR: Trái - Gốc - Phải) :

+Duyệt qua trái theo thứ tự giữa; +Duyệt qua gốc;

+Duyệt qua phải theo thứ tự

* Thuật toán duyệt qua theo thứ tự đầu (NLR: Gốc - Trái - Phải): +Duyệt qua gốc;

+Duyệt qua trái theo thứ tự đầu; +Duyệt qua phải thứ tự đầu Thuật toán NLR duyệt theo chiều sâu.

* Thuật toán duyệt qua theo thứ tự cuối (LRN: Trái - Phải - Gốc): +Duyệt qua trái theo thứ tự cuối;

+Duyệt qua phải theo thứ tự cuối; +Duyệt qua gốc

* Ví dụ: Biểu diễn biểu thức: A - B * C + D lên nhị phân: +

- D

A *

B C

Duyệt theo thứ tự khác nhau:

LNR: A - B * C + D ( biểu thức trung tố ) NLR: + - A * B C D ( biểu thức tiền tố ) LRN: A B C * - D + ( biểu thức hậu tố )

Với cách biểu diễn biểu thức số học dạng nhị phân, dựa cách duyệt LRN ta tính giá trị biểu thức (Bài tập).

Do định nghĩa đệ quy nhị phân, thuật toán duyệt qua theo kiểu đệ quy thích hợp

IV.2.4.3 Cài đặt thuật toán duyệt qua nhị phân LNR a Cài đặt thuật toán LNR dạng đệ qui :

(6)

Output: - Duyệt qua xử lý nút nhị phân theo thứ tự LNR */

void LNRĐệQuy (TreePointer Root)

{ if (Root != NULL)

{ LNRĐệQuy (Root->LChild);

Xử lý (Root); //Xử lý theo yêu cầu cụ thể, chẳng hạn: Xuất(Root->Data); LNRĐệQuy (Root->RChild) ;

} return; }

Thuật toán duyệt nhị phân theo thứ tự (LNR) viết lại dạng lặp, cách sử dụng stack để lưu lại địa nút gốc trước đi

đến trái Trước hết, ta khai báo cấu trúc nút stack trên:

typedef struct NS  TreePointer Data; struct NS * Next;  NodeStack; typedef NodeStack * StackType;

b Cài đặt thuật toán LNR dạng lặp :

/* Input: - Root : trỏ đến nút gốc nhị phân

Output: - Duyệt qua xử lý nút nhị phân theo thứ tự LNR */

void LNRLap(TreePointer Root) { TreePointer p;

int TiepTuc = 1; StackType S;

p = Root; S = CreateEmptyStack(); // Khởi tạo ngăn xếp rỗng

{ while (p != NULL)

{ Push(S,p); // Đẩy p vào stack S p = p->LChild;

}

if (!EmptyStack(S)) // Nếu stack S khác rỗng { Pop(S,p); // Lấy phần tử p đỉnh stack S

XuLy(p);

p = p->RChild; }

else TiepTuc = 0; } while (TiepTuc);

(7)

Với hai trường hợp duyệt lại (NLR LRN), ta cài đặt chúng dạng đệ quy lặp (bài tập) Một cách tổng quát, ta viết lại ba thuật toán duyệt dạng lặp (bài tập).

IV.2.5 Một cách biểu diễn khác nhị phân

Trong số trường hợp, biểu diễn nhị phân, người ta không quan tâm đến quan hệ chiều từ cha đến mà chiều ngược lại: từ đến cha Khi đó, ta dùng cấu trúc sau:

Parent Data

LChild RChild

trong đó: LChild, RChild trỏ đến nút trái nút phải Parent trỏ đến nút cha

Trong ngôn ngữ C hay C++, ta khai báo kiểu liệu cho nút cây nhị phân dạng sau:

typedef ElementType; /* Kiểu mục liệu nút */

typedef struct TNP ElementType Data; //Để đơn giản, ta xem Data trường khóa liệu

struct TNP * LChild, *Rchild, *Parent;  TreeNodeP;

typedef TreeNodeP *TreePointer; * Ví dụ:

e

f c

a b d

IV.2.6 Biểu diễn n - phân nhị phân

Phương pháp cài đặt n - phân mảng có n vùng liên kết có lợi

khi hầu hết nút có bậc n Khi n vùng liên kết sử dụng,

nhưng với có nhiều nút có bậc nhỏ n gây nên việc lãng phí nhớ vì có nhiều vùng liên kết không sử dụng tới

(8)

con nút anh em với Để biểu diễn T T2, ta theo qui tắc sau:

+ Nút gốc T biểu diễn tương ứng với nút gốc T2

+ Con (trái nhất) nút T trái nút tương ứng T2

+ Nút anh em kề phải P nút Q T tương ứng với nút P2 T2 qua liên kết phải nút Q2 tương ứng T2

Cây n-phân T a

Q b P c d

e f g h i

j k l m n

a nhị phân T2 tương ứng Q2 P2

b c d

e f g h i

j k l m n

IV.2.7 Xây dựng nhị phân cân hoàn toàn

IV.2.7.1 Định nghĩa: Cây nhị phân cân hoàn toàn (CBHT) nhị

phân mà nút nó, số nút trái chênh lệch không so với số nút phải

* Ví dụ:

e

f c

(9)

IV.2.7.2 Xây dựng nhị phân cân hoàn toàn

Xây dựng nhị phân cân hồn tồn có n phần tử:

TreePointer TạoCâyCBHT(Nguyên n)

{ TreePointer Root; Nguyên nl, nr; ElementType x;

if (n<=0) return NULL; nl = n/2; nr = n-nl-1; Nhập1PhầnTử(x);

if ((Root =CấpPhát()) == NULL) return NULL; Root->Data = x;

Root->LChild = TạoCâyCBHT(nl); Root->RChild = TạoCâyCBHT(nr); return Root;

}

* Nhận xét:

- Một CBHT có n nút có chiều cao bé h log2n

- Một CBHT dễ cân sau thêm hay hủy nút trên cây, việc chi phí cân lại lớn phải thao tác lại tồn

bộ Do CBHT có cấu trúc ổn định, sử dụng

trong thực tế

IV.3 Cây nhị phân tìm kiếm (BST)

IV.3.1 Định nghĩa nhị phân tìm kiếm (BST)

Cây BST nhị phân có tính chất giá trị khóa nút lớn giá trị khoá nút thuộc bên trái (nếu có) nhỏ giá trị khoá nút thuộc bên phải (nếu có)

* Ví dụ: Xét BST sau lưu giá trị: 46, 17, 63,2, 25, 97 Ta biểu diễn trình tìm kiếm phần tử 25, 55 BST qua hình đây:

46

25<46 55>46 (không thấy 55) 17 63

25>17 (thấy 25)

25 97

(10)

IV.3.2 Tìm kiếm phần tử BST

(Thuật tốn tìm kiếm nhị phân sau tương tự phép tìm kiếm nhị phân mảng)

IV.3.2.1 Thuật tốn tìm kiếm dạng đệ qui:

/* Input: - Root: trỏ đến nút gốc BST - Item: giá trị khóa phần tử cần tìm

Output: - Trả trỏ LocPtr đến nút BST chứa Item tìm thấy Item BST

- Trả trị NULL ngược lại */

TreePointer TìmBSTĐệQuy (TreePointer Root, ElementType Item)

{

if (Root)

{if (Item== Root->Data) return Root;

else if (Item > Root->Data) return TìmBSTĐệQuy (Root->RChild,Item);

else return TìmBSTĐệQuy (Root->LChild,Item); }

else return(NULL); }

* Thủ tục viết dạng đệ qui thích hợp với lối tư tự nhiên của

giải thuật định nghĩa đệ qui nhị phân Song trường hợp thủ

tục viết dạng lặp lại tỏ hiệu

IV.3.2.2 Thuật tốn tìm kiếm dạng lặp:

/* Input: - Root: trỏ đến nút gốc BST - Item: giá trị khóa phần tử cần tìm

Output: - Trả trỏ LocPtr đến nút BST chứa Item trỏ Parent đến nút cha nút chứa Item tìm thấy Item BST

- Trả trị NULL ngược lại */

TreePointer TìmBSTLặp(TreePointer Root, ElementType Item, TreePointer &Parent)

{ TreePointer LocPtr = Root; Parent = NULL;

while (LocPtr != NULL)

if (Item==LocPtr->Data) return (LocPtr); else {Parent = LocPtr;

if (Item > LocPtr->Data) LocPtr = LocPtr->RChild; else LocPtr = LocPtr->LChild;

(11)

Với cấu trúc cây, việc tìm kiếm theo khóa nhanh nhiều so với cấu trúc danh sách liên kết Chi phí tìm kiếm (độ phức tạp) trung bình nhị phân có n nút khoảng log2 n.

IV.3.3 Chèn phần tử vào BST, xây dựng BST

Việc chèn thêm phần tử Item vào BST cần phải thỏa ràng buộc trong định nghĩa BST Trước chèn Item, ta cần tìm khóa Item có trong BST hay khơng, cóthì khỏi chèn (do BST ta chứa phần tử có khóa khác nhau); ngược lại, chấm dứt thao tác tìm kiếm ta cũng

biết vị trí chèn (ở nút lá).

* Ví dụ: Giả sử ta có BST (với nút có khóa khác nhau): O

E T

C M P U

Ta cần thêm phần tử ‘R’: O

(R > O) E (R<T) T

C M P U (R>P)

R Parent

Yêu cầu “vào – ra” thao tác chèn:

/* Input: - Root: trỏ đến nút gốc BST - Item: giá trị liệu nút cần chèn

Output: - Trả trị trỏ Root đến nút gốc BST chèn

- Trả trị -1 Item có

- Trả trị gặp lỗi cấp phát nhớ cho nút */

IV.3.3.1 Thao tác chèn nút Item vào BST (dạng lặp): int ChènBSTLặp(TreePointer &Root, ElementType Item)

{ TreePointer LocPtr, Parent;

if (TìmBSTLặp(Root, Item, Parent))

(12)

}

else { if ((LocPtr=CấpPhát ())==NULL) return 0; LocPtr->Data = Item;

LocPtr->LChild = NULL; LocPtr->RChild = NULL; if (Parent == NULL)

Root = LocPtr; // rỗng

else if (Item < Parent->Data) Parent->LChild = LocPtr; else Parent->RChild = LocPtr;

return 1; }

}

IV.3.3.2 Thủ tục chèn nút Item vào BST (dạng đệ qui): int ChènBSTĐệQui(TreePointer &Root, ElementType Item)

{ TreePointer LocPtr;

if (Root == (TreePointer) NULL) // chèn nút vào rỗng { if ((Root = CấpPhát ()) == NULL) return 0;

Root ->Data = Item;

Root ->LChild = NULL; Root ->RChild = NULL; }

else if (Item < Root->Data) ChènBSTĐệQui (Root->LChild,Item); else if (Item > Root->Data) ChènBSTĐệQui(Root->RChild,Item); else { cout << “\nĐã có phần tử “<< Item << “ cây”;

return -1; }

return 1; }

IV.3.3.3 Xây dựng BST

Ta xây dựng BST cách lặp lại thao tác chèn phần tử vào BST đây, xuất phát từ rỗng Hàm TạoCâyBST(Root) sau trả trị gặp lỗi cấp phát vùng nhớ cho nút Root trả trị việc chèn nút vào thành công (không chèn nút có khóa trùng với khóa nút chèn)

int TạoCâyBST(PointerType &Root)

{ ElementType Item;

Root = NULL;

while (CònLấyDữLiệu(Item))

if (!ChènBSTLặp(Root, Item)) return 0; return 1;

}

(13)

Ta nhận xét sau duyệt BST theo thứ tự LNR ta sẽ

thu dãy tăng theo khóa Từ đó, ta có phương pháp xếp dựa cây

BST sau Giả sử ta cần xếp dãy X phần tử * Giải thuật BSTSort :

- Bước : Đưa phần tử dãy X lên BST

- Bước 2 : Khởi tạo lại dãy rỗng X Duyệt BST theo thứ tự (LNR), trong thao tác XửLý(Nút) lưu Nút->Data vào phần tử dãy X.

* Ví dụ: Giả sử cần xếp dãy gồm n phần tử lưu mảng X Khi ta có thuật tốn sau:

1.Khởi tạo BST rỗng

2.for (i = 0; i< n; i++) Chèn X[i] vào BST; 3.Đặt lại i = 0;

4.Duyệt qua theo thứ tự LNR, việc XửLý(Nút) nút duyệt qua là:

- Gán X[i]  Nút->Data; - Tăng i lên 1;

IV.3.5 Xóa phần tử khỏi BST, hủy nhị phân

Giả sử ta cần xóa nút (trên BST) trỏ x Việc xoá một phần tử BST cần phải thoả ràng buộc BST, việc xóa phức tạp so với chèn Ta phân biệt trường hợp : x trỏ đến nút lá, x trỏ đến nút có con, x trỏ đến nút có hai

a) Xoá nút lá:

C x C Xoá nút D

B D B NULL

- Đặt trỏ phải (hay trái) nút cha x thành NULL - Giải tỏa nút D

b) Xố nút có nút con:

- Đặt trỏ phải (hoặc trái) nút cha nút cần xóa trỏ đến nút khác rỗng nút cần xóa

- Giải tỏa nút cần xóa

Giả sử ta cần xóa nút E có nút con: C x C

Xoá nút E

(14)

Kết hợp hai trường hợp thành trường hợp: x trỏ đến nút có nhiều khác rỗng Gọi:

+ x đến nút cần xóa

+ SubTree đến (khác rỗng , có) x

+ Parent đến nút cha nút trỏ x (nếu x đến gốc thì

Parent=NULL)

Ta có giải thuật xóa cho trường hợp là: SubTree = x->LChild;

if (SubTree == NULL ) SubTree = x->RChild; //SubTree khác rỗng (nếu có) x

if (Parent == NULL) Root = SubTree; // xoá nút gốc

else if (Parent->LChild == x) Parent->LChild = SubTree ; else Parent->RChild = SubTree;

delete x;

c) Xoá nút có hai nút con:

Giả sử ta cần xố nút E có nút BST sau : C

x B E

D K

(Nút E I L theo thứ tự giữa)

J

Đưa trường hợp đầu cách sau: Thay trị nút mà x trỏ đến trị nút theo thứ tự (nút nút cực trái xa theo nhánh phải x, nút nhỏ (tất nhiên theo trường khóa) trong số nút lớn x->Data) Sau xố nút (nút này nút có tối đa nút )

C x

B E ( Thay E I)

D K

(Xóa nút I) I L

(15)

C x B I

D K

J L

* Sau ta xây dựng thủ tục XóaBST để xố nút Item cây BST Trong thủ tục có dùng đến thủ tục TìmBSTLặp Thủ tục XốBST tìm nút có khóa Item xố khỏi BST.

Gọi:

- x: trỏ đến nút chứa Item

- xSucc: phần tử x theo thứ tự (nếu x có con) - Parent: trỏ đến cha x hay xSucc

- SubTree: trỏ đến x

/* Input: - Root: trỏ đến nút gốc BST - Item: giá trị liệu nút cần xóa

Output: - Trả trị trỏ Root đến nút gốc BST tìm thấy nút

chứa Item xoá - Trả trị ngược lại */

int XóaBST (TreePointer &Root, ElementType Item)

{ TreePointerx,Parent, xSucc,SubTree;

if ((x = TìmBSTLặp(Root,Item,Parent)) ==NULL) return 0;//khơng thấy Item else { if ((x->LChild != NULL) && (x->RChild != NULL)) // nút có 2 con

{ xSucc = x->RChild; Parent = x;

while (xSucc->LChild != NULL) { Parent = xSucc;

xSucc = xSucc->LChild; }

x->Data = xSucc->Data; x = xSucc;

} //đã đưa nút có nút có tối đa

SubTree = x->LChild;

if (SubTree == NULL) SubTree = x->RChild;

if (Parent == NULL) Root = SubTree; // xoá nút gốc else if (Parent->LChild == x) Parent->LChild = SubTree;

(16)

delete x; return 1; }

}

Ta hủy toàn BST cách sử dụng ý tưởng duyệt theo thứ tự cuối LRN: hủy trái, hủy phải hủy nút gốc

void HủyCâyNhịPhân (PointerType &Root)

{ if (Root)

{ HủyCâyNhịPhân (Root->LChild); HủyCâyNhịPhân (Root->RChild); delete Root;

} return ; }

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

Trên nhị phân tìm kiếm BST có n phần tử mà CBHT (cân bằng hồn tồn), phép tìm kiếm phần tử thực nhanh: trong trường hợp xấu nhất, ta cần thực log2n phép so sánh Nhưng CBHT

có cấu trúc ổn định thao tác cập nhật cây, nên sử dụng

trong thực tế Vì thế, người ta tận dụng ý tưởng CBHT để xây dựng nhị phân tìm kiếm có trạng thái cân yếu hơn, việc cân lại xảy ra phạm vi cục đồng thời chi phí cho việc tìm kiếm dạt mức O(log2n).

Đó nhị phân tìm kiếm cân IV.4.1 Định nghĩa

Cây nhị phân tìm kiếm gọi nhị phân tìm kiếm cân (gọi tắt cây

cân hay AVL tác giả Adelson-Velskii-Landis đưa vào năm 1962)

nếu nút nó, độ cao trái độ cao phải chênh

lệch không 1.

Rõ ràng, nhị phân tìm kiếm cân hồn tồn cân bằng, điều ngược lại không Chẳng hạn nhị phân tìm kiếm ví dụ sau cân khơng phải cân hồn tồn:

* Ví dụ: (cây nhị phân tìm kiếm cân khơng cân hồn tồn) O

(17)

C M

Cây cân AVL thực việc tìm kiếm nhanh tương đương (nhị phân tìm kiếm) cân hồn tồn có cấu trúc ổn định hẳn cân hồn tồn mà thể qua thao tác trình bày phần

IV.4.2 Chiều cao cân bằng

* Định lý (AVL): Gọi hb(n) độ cao AVL có n nút, đó:

log2(n+1) hb(n) < 1.4404 * log2(n+2) –0.3277

Cây AVL tối ưu (trong trường hợp tốt nhất, có chiều cao bé nhất) là cây cân hồn tồn có n nút với: n = 2k-1 Một AVL không cao

quá 45% cân hoàn toàn tương ứng nó.

Chứng minh: Bất đẳng thức thứ bên trái có tính chất nhị phân (phần II.2)

Để chứng minh bất đẳng thức thứ hai bên phải, ta gọi N(h) số nút cây AVL T(h) có chiều cao h

Ta có: N(0) = ứng với rỗng T(0) N(1) = ứng với có nút T(1) Khi h > 1, gốc T(h) có hai có số nút nhất, có chiều cao h -1, có chiều cao h -2 Do đó:

N(h) = + N(h –1) + N(h –2),  h >1 N(0) = 0, N(1) =

Đặt F(h) = N(h) + Khi đó:

F(h) = F(h –1) + F(h –2),  h >1 F(0) = 1, F(1) =

Giải hệ thức truy hồi (bằng cách ? Bài tập), ta được: n +  N(h) + = F(h) = (r1h+2 – r2h+2) /

√5 > (r1h+2 – 1) / √5

với: r1 = (1+ √5 ) /2, r2 = (1 - √5 ) /2  (-1; 1)

=> h +2 < log r1 (1+ √5 (n + 1)) < log r1 ( √5 (n + 2)) < logr1 (n + 2) + log r1 ( √5 )

h < log2 (n + 2)/ log2 (r1) + log r1 ( √5 ) -  1.44042 log2 (n + 2) – 0.32772

Vậy AVL có n nút có chiều cao tối đa (trong trường hợp xấu

nhất) O(log2n)

IV.4.3 Chỉ số cân việc cân lại AVL

* Định nghĩa: Chỉ số cân (CSCB) nút p hiệu chiều cao cây phải trái nó.

(18)

EH = 0, RH = 1, LH = -1

CSCB(p) = EH  hR(p) =hL(p):2 cao

CSCB(p) = RH  hR(p) > hL(p) : lệch phải

CSCB(p) = LH  hR(p) < hL(p) : lệch trái

Với nút AVL, ngồi thuộc tính thơng thường nhị phân, ta cần lưu thêm thông tin số cân cấu trúc nút:

typedef ElementType; /* Kiểu mục liệu nút */

typedef struct AVLTN  ElementType Data; //Ở ta xem Data trường khóa liệu

int Balfactor; //Chỉ số cân

struct AVLTN * Lchild, *Rchild;  AVLTreeNode;

typedef AVLTreeNode *AVLTree;

Việc thêm hay hủy nút AVL làm tăng hay giảm chiều cao, ta cần phải cân lại Để giảm tối đa chi phí cân lại

cây, ta cân lại AVL phạm vi cục Các trường hợp cân bằng

Ngoài thao tác thêm hủy, cân bằng, ta cịn có thêm thao tác cân lại AVL trường hợp thêm hủy nút của Khi đó, độ lệch chiều cao phải trái Do trường hợp lệch trái phải tương ứng đối xứng nhau, nên ta xét trường hợp AVL lệch trái

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

h-1

L R R

h L1 R1 h-1

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

T

T1

(19)

h-1

L R R

h-1 L1 R1 h

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

h-1

L R

h L1 R1 h

Việc cân lại trường hợp b (cây T1 lệch phải) phức tạp

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

h-1

L R R

h L1 R1 h-1

T1

T

T1

T

(20)

Cân lại phép quay đơn Left-Left, ta T1 không lệch:

h L1 h+1

h-1 R1 R h-1

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

h-1

L R R

h L1 R1 h

Cân lại phép quay đơn Left-Left (khi ta T1 lệch phải):

h L1 h+2

h R1 R h-1

Trường hợp b: T1 lệch phải, biểu diễn lại R1 = <L2, T2, R2> sau:

h-1

T

T1

T

T1

T1

T

T

(21)

L R

h-1 L1

R1 h

L2 R2

Cân lại phép quay kép Left – Right, ta T2 không lệch sau:

h+1

h-1 L1 L2 R2 R h-1

* Nhận xét:

- Trước cân lại, T lệch (và cân bằng) có chiều cao là

h+2 trường hợp Nhưng sau cân lại T, vẫn lệch (lệch phải, tất nhiên cân bằng) có chiều cao h+2

chỉ trường hợp c; hai trường hợp a b, T (là

T1 hay T2 tương ứng với trường hợp a hay b) không lệch có chiều

cao h+1.

- Các thao tác cân lại trường hợp có độ phức tạp là

O(1).

Sau phần cài đặt phép quay đơn kép cho T cân hai trường hợp bị lệch trái lệch phải

//Phép quay đơn Left – Left

void RotateLL(AVLTree &T)

{ AVLTree T1 = T->Lchild; T->Lchild = T1->Rchild; T1->Rchild = T;

switch (T1->Balfactor)

{case LH: T->Balfactor = EH;

T2

T2

T

(22)

T1->Balfactor = EH; break; case EH: T->Balfactor = LH;

T1->Balfactor = RH; break; }

T = T1; return ; }

//Phép quay đơn Right – Right

void RotateRR (AVLTree &T)

{ AVLTree T1 = T->Rchild; T->Rchild = T1->Lchild; T1->Lchild = T;

switch (T1->Balfactor)

{case RH: T->Balfactor = EH;

T1->Balfactor = EH; break; case EH: T->Balfactor = RH;

T1->Balfactor = LH; break; }

T = T1; return ; }

//Phép quay kép Left – Right

void RotateLR(AVLTree &T)

{ AVLTree T1 = T->Lchild, T2 = T1->Rchild; T->Lchild = T2->Rchild; T2->Rchild = T; T1->Rchild = T2->Lchild; T2->Lchild = 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;

return ; }

//Phép quay kép Right-Left

(23)

{ AVLTree T1 = T->RLchild, T2 = T1->Lchild; T->Rchild = T2->Lchild; T2->Lchild = T; T1->Lchild = T2->Rchild; T2->Rchild = T1; switch (T2->Balfactor)

{case LH: T->Balfactor = EH;

T1->Balfactor = RH; break; case EH: T->Balfactor = EH;

T1->Balfactor = EH; break; case RH: T->Balfactor = LH;

T1->Balfactor = EH; break; }

T2->Balfactor = EH; T = T2;

return ; }

Sau thao tác cân lại bị lệch trái hay lệch phải. //Cân lại bị lệch trái

int LeftBalance(AVLTree &T)

{ AVLTree T1 = T->Lchild; switch (T1->Balfactor)

{ case LH : RotateLL(T); return 2; //cây T giảm độ cao không bị lệch case EH : RotateLL(T); return 1;//cây T không giảm độ cao bị lệch phải

case RH : RotateLR(T); return 2; }

return 0; }

//Cân lại bị lệch phải

int RightBalance(AVLTree &T)

{ AVLTree T1 = T->Rchild; switch (T1->Balfactor)

{ case LH : RotateRL(T); return 2; //cây T không bị lệch case EH : RotateRR(T); return 1; //cây T bị lệch trái case RH : RotateRR(T); return 2;

}

return 0; }

IV.4.4 Chèn phần tử vào AVL

(24)

thêm vào, ta phải lần ngược lên gốc để kiểm tra xem có nút bị cân hay khơng Nếu có, ta phải cân lại nút (Việc cân lại cần thực lần nơi cân bằng)

Hàm chèn trả trị –1, 0, hay tương ứng khi: không đủ nhớ cấp phát cho nút gặp nút có thành công chiều cao bị tăng sau chèn

Khi chèn nút vào AVL, ta cần sử dụng hàm cấp phát nhớ cho nút AVL

AVLTree CấpPhátAVL()

{ AVLTree Tam= new AVLTreeNode; if (Tam == NULL)

cout << “\nKhông đủ nhớ cấp phát cho nút AVL !”; return Tam;

}

int ChènAVL( AVLTree &T, ElementType x)

{ int Kquả; if (T)

{ if (T->Data == x) return 0; //Đã có nút if (T-> Data > x)

{ Kqủa=ChènAVL(T->Lchild,x);//chèn x vào trái T

if (Kqủa < 2) return Kqủa; switch (T->Balfactor)

{ case LH: LeftBalance(T); return 1;//trước chèn,T lệch trái

case EH: T->Balfactor=LH;return 2;//trước chèn,T không lệch caseRH:T->Balfactor=EH; return 1;//trước chèn,T lệch phải

} }

else // T-> Data < x

{ Kqủa=ChènAVL(T->Rchild,x);//chèn x vào phải T if (Kqủa < 2) return Kqủa;

switch (T->Balfactor)

{ case LH: T->Balfactor = EH; return 1;//trước chèn,T lệch trái case EH:T->Balfactor=RH;return 2;//trước chèn,T không lệch

case RH : RightBalance(T); return 1;//trước chèn,T lệch phải }

} }

else //T==NULL

{ if ((T = CấpPhátAVL()) == NULL) return –1; //Thiếu nhớ T->Data = x;

(25)

T->Lchild = T->Rchild = NULL;

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

}

IV.4.5 Xóa phần tử khỏi AVL

Việc xóa phần tử khỏi AVL diễn tương tự nhị phân tìm kiếm; khác sau hủy, AVL bị cân bằng, ta phải cân lại Việc cân lại xảy phản ứng dây chuyền.

Hàm XóaAVL trả trị hoặc tùy theo việc hủy thành công x sau hủy, chiều cao bị giảm

int XóaAVL(AVLTree &T, ElementType x)

{ int Kqủa;

if (T== NULL) return 0; // khơng có x if (T-> Data > x)

{ Kqủa = XốAVL(T->Lchild,x);// tìm xóa x trái T

if (Kqủa < 2) return Kqủa; switch (T->Balfactor)

{ case LH : T->Balfactor = EH; return 2; //trước xóa,T lệch trái

case EH : T->Balfactor = RH; return 1;//trước xóa,T khơng lệch

case RH : return RightBalance(T); //trước xóa,T lệch phải

} }

else if (T-> Data < x)

{ Kqủa = XốAVL(T->Rchild,x); // tìm xóa x phải T

if (Kqủa < 2) return Kqủa; switch (T->Balfactor)

{ case LH : return LeftBalance(T); //trước xóa,T lệch trái

case EH : T->Balfactor = LH; return 1;//trước xóa,T khơng lệch

case RH : T->Balfactor = EH; return 2; //trước xóa,T lệch phải

} }

else //T->Data== x { AVLTree p = T;

if (T->Lchild == NULL)

{ T = T->Rchild; Kqủa = 2; }

else if (T->Rchild == NULL)

{ T = T->Lchild; Kqủa = 2; }

(26)

{ Kqủa = TìmPhầnTửThayThế(p,T->Rchild);

// tìm phần tử thay p để xóa nhánh phải T

if (Kqủa == 2) switch (T->Balfactor)

{ case LH : Kquả = LeftBalnce(T); break; case EH: T->Balfactor=LH; Kquả = 1; break; case RH: T->Balfactor=EH; Kquả = 2; break; }

} delete p; return Kquả; }

}

// Tìm phần tử thay

int TìmPhầnTửThayThế(AVLTree &p, AVLTree &q)

{ int Kqủa;

if (q->Lchild)

{ Kqủa = TìmPhầnTửThayThế(p, q->Lchild); if (Kqủa < 2) return Kquả;

switch (q->Balfactor)

{ case LH : q->Balfactor = EH; return 2; case EH : q->Balfactor = RH; return 1; case RH : return RightBalance(q); }

}

else { p->Data = q->Data; p = q;

q = q->Rchild; return 2; }

}

* Nhận xét:

- Thao tác thêm nút có độ phức tạp O(1) - Thao tác huỷ nút có độ phức tạp O(h)

- Với cân bằng, trung bình: lần thêm vào cần lần cân lại, lần hủy cần lần cân lại

- Việc hủy nút phải cân dây chuyền nút từ gốc đến phần tử bị hủy, thêm vào nút cần lần cân cục - Độ dài đường tìm kiếm trung bình AVL gần cân

(27)

Ngày đăng: 08/04/2021, 21:00

Tài liệu cùng người dùng

Tài liệu liên quan