Bài giảng Cấu trúc dữ liệu và giải thuật - Chương 4: Cây (Tree) - Đỗ Ngọc Như Loan
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 gốc (x1, x2, , xk là con của nút r)
Bạn đang xem 20 trang mẫu của tài liệu "Bài giảng Cấu trúc dữ liệu và giải thuật - Chương 4: Cây (Tree) - Đỗ Ngọc Như Loan", để tải tài liệu gốc về máy hãy click vào nút Download ở trên
Tóm tắt nội dung tài liệu: Bài giảng Cấu trúc dữ liệu và giải thuật - Chương 4: Cây (Tree) - Đỗ Ngọc Như Loan
Cây (Tree) GV: Đỗ Ngọc Như Loan 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 A X Y Z (Hình 1) (Hình 2) (Hình 3) (Hình 4) 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 gốc (x1, x2, , xk là con của nút r) 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 ) 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ó ít nhất 1 con 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 Độ 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. Mức (level) của x = Độ sâu + 1. Độ sâu lớn nhất của các nút trong T gọi là chiều cao (height ) của T. Cây rỗng có chiều cao là -1. Ví dụ CÂY NHỊ PHÂN (BINARY TREE) Cây mà mỗi nút chỉ có tối đa hai con gọi là cây nhị phân Ví dụ: (Hình 1) (Hình 2) (Hình 3) (Hình 4) X Y Z Khái niệm Một cây được gọi là 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. 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 chênh lệch 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 X Y Z (Hình 1) (Hình 2) (Hình 3) (Hình 4) 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 như một khóa ), có 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 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; 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) Inorder tree walk void InorderTreeWalk(TREE x ) { if(x !=NULL) { InorderTreeWalk(x- >left); cout key)<<' '; //thăm (xử lý) nút x InorderTreeWalk(x- >right); } } 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 ] Các thao tác trên bst Khởi tạo cây Các thao tác tìm kiếm Chèn một nút vào cây Xóa một núy trên cây treeinitialize void TreeInitialize(TREE &T) { T=NULL ; } 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 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 phải treesearch TREE TreeSearch(TREE x,int k) { if(x ==NULL||k==x->key) return x; if(kkey) return TreeSearch(x->left,k); else return TreeSearch(x->right,k); } 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 TREEMINIMUM TREE TreeMinimum(TREE x) { if(x !=NULL) while(x- >left!=NULL) x=x- >left; return x; } 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á lớn 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 TREEMAXIMUM TREE TreeMaximum(TREE x) { if(x !=NULL) while(x- >right!=NULL) x=x- >right; return x; } Độ 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 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 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 . treeinsert void TreeInsert(TREE &T, int k) { if(T ==NULL) { T=new(CELL ); T->key=k; T- >left=NULL; T->right=NULL; } else if(kkey) TreeInsert(T->left, k); else if(k>T->key) TreeInsert(T->right, k); } treedelete TreeDelete(T, k) tìm và xoá 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 treedelete treedelete treedelete treedelete treedelete void TreeDelete(TREE &T, int k) { if(T !=NULL) if(kkey) 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); } deletemin int DeleteMin(TREE &T) { int min; if(T- >left==NULL) { min=T- >key; T=T- >right; return min; } else return DeleteMin(T->left); } Độ 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 Độ 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ỏ Độ 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(log 2 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(log 2 n ) Yêu cầu: Cho dãy số nguyên gồm n số đôi một khác nhau và số k . Hãy kiểm tra xem có giá trị k trong dãy không? Input: Dữ liệu vào từ file TREE1 .INP gồm 2 dòng: + Dòng 1: Gồm 2 số nguyên n và k (n<=1000) + Dòng 2 chứa dãy số nguyên n phần tử Output: Xuất ra file TREE1 .OUT , g ồm 1 dòng chứa YES nếu tìm được, NO nếu không tìm được. Ví dụ: TREE1. INP 5 3 5 7 1 3 4 TREE1 .OUT YES Giải bài toán này bằng cách sử dụng CÂY NHỊ PHÂN TÌM KIẾM Yêu cầu: Cho dãy số nguyên gồm n số đôi một khác nhau và số k . Hãy xóa số k ra khỏi dãy và xuất ra kết quả dãy đã được sắp xếp theo thứ tự tăng dần. Input: Dữ liệu vào từ file TREE2 .INP gồm 2 dòng: + Dòng 1: Gồm 2 số nguyên n và k (n<=1000) + Dòng 2 chứa dãy số nguyên n phần tử Output: Xuất ra file TREE2 .OUT , g ồm 1 dòng chứa dãy số đã xóa k và được sắp xếp theo thứ tự tăng dần. Ví dụ: TREE2. INP 5 3 5 7 1 3 4 TREE2 .OUT 1 4 5 7 Giải bài toán này bằng cách sử dụng CÂY NHỊ PHÂN TÌM KIẾM CÂY NHỊ PHÂN TÌM KiẾM CÂN BẰNG 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 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 Chiều cao cây cân bằng n nút luôn luôn là Cây cân bằng còn được gọi là cây AVL ĐỊNH NGHĨA // các hằng số biểu diễn hệ số cân bằng #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 typedef struct AVLNode *AVLTree; struct AVLNode { int balFactor; // hệ số cân bằng int key; AVLTree pLeft, pRight; }; AVLTree T; Các trường hợp mất cân bằng Các trường hợp mất cân bằng Các trường hợp mất cân bằng Các trường hợp mất cân bằng 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) Cây lệch bên trái Cây lệch bên phải Cân bằng cây lệch bên trái T1 lệch phải, sử dụng phép quay left-right QUAY 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; } QUAY 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; } QUAY 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; } QUAY 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; } 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); break; case EH: rotateLL(T); break; case RH: rotateLR(T); break; } } 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); break; case EH: rotateRR(T); break; case RH: rotateRR(T); break; } } Chèn 1 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 : khi không đủ bộ nhớ, + 0: gặp nút đã có + 1: chèn thành công (chiều cao không tăng) + 2: nếu chèn thành công và chiều cao tăng Chèn 1 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 { 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 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 } XÓA 1 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> của Trần Hạnh Nhi và Dương Anh Đức Độ 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(log 2 n) 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 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 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
File đính kèm:
- bai_giang_cau_truc_du_lieu_va_giai_thuat_chuong_4_cay_tree_d.pptx