Bài giảng Cấu trúc dữ liệu và giải thuật - Chương 6: Cây nhị phân tìm kiếm (Binary Search Trees) - Nguyễn Mạnh Hiển

Định nghĩa • Xét trường hợp các phần tử có giá trị khác nhau • Cây nhị phân tìm kiếm là cây nhị phân, trong đó với mọi nút X: − Tất cả các giá trị trên cây con trái của X nhỏ hơn X − Tất cả các giá trị trên cây con phải của X lớn hơn X Các thao tác chính • Tìm phần tử nhỏ nhất • Tìm phần tử lớn nhất • Tìm phần tử x • Chèn phần tử x • Xóa phần tử x Tất cả các thao tác trên có thời gian chạy trung bình là O(log N)  sẽ chứng minh sau

pdf25 trang | Chia sẻ: thanhle95 | Lượt xem: 458 | Lượt tải: 1download
Bạn đang xem trước 20 trang tài liệu Bài giảng Cấu trúc dữ liệu và giải thuật - Chương 6: Cây nhị phân tìm kiếm (Binary Search Trees) - Nguyễn Mạnh Hiển, để xem tài liệu hoàn chỉnh bạn click vào nút DOWNLOAD ở trên
Cây nhị phân tìm kiếm (Binary Search Trees) Nguyễn Mạnh Hiển hiennm@tlu.edu.vn Định nghĩa • Xét trường hợp các phần tử có giá trị khác nhau • Cây nhị phân tìm kiếm là cây nhị phân, trong đó với mọi nút X: − Tất cả các giá trị trên cây con trái của X nhỏ hơn X − Tất cả các giá trị trên cây con phải của X lớn hơn X Đây có phải là cây nhị phân tìm kiếm? Các thao tác chính • Tìm phần tử nhỏ nhất • Tìm phần tử lớn nhất • Tìm phần tử x • Chèn phần tử x • Xóa phần tử x Tất cả các thao tác trên có thời gian chạy trung bình là O(log N)  sẽ chứng minh sau Cài đặt template // T là kiểu phần tử class BinarySearchTree { public: hàm tạo, hàm hủy kiểm tra rỗng xóa rỗng cây tìm min, tìm max, tìm phần tử x chèn/xóa phần tử x private: struct BinaryNode { ... }; // kiểu của các nút BinaryNode * root; // con trỏ tới nút gốc các hàm trợ giúp }; Kiểu của các nút struct BinaryNode { T elem; BinaryNode * left; BinaryNode * right; BinaryNode(T x, BinaryNode * l, BinaryNode * r) { elem = x; left = l; right = r; } }; Hàm tạo, hàm hủy, xóa rỗng BinarySearchTree() { root = NULL; } ~BinarySearchTree() { makeEmpty(); } void makeEmpty() { // hàm xóa rỗng cây makeEmpty(root); // gọi hàm private trợ giúp } bool isEmpty() { // hàm kiểm tra rỗng return (root == NULL); } Xóa rỗng cây có gốc t // Hàm private trợ giúp xóa rỗng cây void makeEmpty(BinaryNode * & t) { if (t == NULL) return; // thoát ra nếu cây rỗng makeEmpty(t->left); // xóa cây con trái makeEmpty(t->right); // xóa cây con phải delete t; // xóa nút gốc t = NULL; } Tìm phần tử nhỏ nhất // Hàm public T findMin() { BinaryNode * v = findMin(root); // gọi hàm private return v->elem; } // Hàm private trợ giúp (dùng đệ quy) BinaryNode * findMin(BinaryNode * t) { if (t == NULL) // cây rỗng? return NULL; if(t->left == NULL) // nút ngoài cùng bên trái? return t; return findMin(t->left); // tìm trên cây con trái } Tìm phần tử lớn nhất // Hàm public T findMax() { BinaryNode * v = findMax(root); // gọi hàm private return v->elem; } // Hàm private trợ giúp (không dùng đệ quy) BinaryNode * findMax(BinaryNode * t) { if (t != NULL) while (t->right != NULL) // chưa đến tận cùng? t = t->right; // đi tiếp sang bên phải return t; } Tìm phần tử x // Hàm public bool contains(T x) { return contains(x, root); // gọi hàm private } // Hàm private trợ giúp bool contains(T x, BinaryNode * t) { if (t == NULL) return false; else if (x elem) return contains(x, t->left); else if (x > t->elem) return contains(x, t->right); else return true; // tìm được x } Chèn phần tử Trước khi chèn 5 Sau khi chèn 5 Cài đặt thao tác chèn // Hàm public (chèn x vào cây) void insert(T x) { insert(x, root); // gọi hàm private } // Hàm private trợ giúp (chèn x vào cây có gốc t) void insert(T x, BinaryNode * & t) { if (t == NULL) t = new BinaryNode(x, NULL, NULL); else if (x elem) insert(x, t->left); else if (x > t->elem) insert(x, t->right); else ; // gặp x; không làm gì cả } Xóa nút lá Trước khi xóa 3 Sau khi xóa 3 Cách xóa: Chỉ đơn giản xóa nút đó Xóa nút chỉ có một con Trước khi xóa 4 Sau khi xóa 4 Cách xóa: Cho nút cha trỏ tới con của nút bị xóa Xóa nút có hai con Trước khi xóa 2 Sau khi xóa 2 Cách xóa: Thay nút bị xóa bằng nút nhỏ nhất của cây con phải Cài đặt thao tác xóa // Hàm public (xóa x khỏi cây) void remove(T x) { remove(x, root); // gọi hàm private } // Hàm private trợ giúp (xóa x khỏi cây có gốc t) void remove(T x, BinaryNode * & t) { ... // xem code ở slide sau } Cài đặt thao tác xóa (tiếp) void remove(T x, BinaryNode * & t) { if (t == NULL) return; // thoát ra nếu cây rỗng if (x elem) remove(x, t->left); // xóa ở cây con trái else if (x > t->elem) remove(x, t->right); // xóa ở cây con phải else if (t->left != NULL && t->right != NULL) { // 2 con t->elem = findMin(t->right)->elem; remove(t->elem, t->right); } else { // 1 con hoặc lá BinaryNode * old = t; t = (t->left != NULL) ? t->left : t->right; delete old; } } Phân tích thời gian chạy • Gọi N là tổng số nút trong cây • Gọi d là độ sâu trung bình của các nút • Thao tác xóa rỗng cây có thời gian chạy là O(N) • Các thao tác tìm/chèn/xóa có thời gian chạy trung bình là O(d) = O(log N), bao gồm: − Thời gian trung bình đi từ nút gốc tới nút v – nơi diễn ra thao tác – là O(d) − Thời gian xử lý thao tác tại nút v là O(1) • Tiếp theo, ta sẽ chứng minh d = O(log N) Chứng minh d = O(log N) (1) • Độ sâu trung bình của các nút: d = tổng-độ-sâu-của-các-nút / số-nút = D/N Tổng độ sâu của các nút được gọi là độ dài đường đi bên trong • Hãy tính độ dài đường đi bên trong của các cây sau: 2 2 3 1 2 5 3 1 6 9 7 8 0 6 4 2 Chứng minh d = O(log N) (2) • Nếu cây con trái của nút gốc có i nút: D(N) = D(i) + D(N-i-1) + N-1 D(i) là độ dài đường đi bên trong của cây con trái D(N-i-1) là độ dài đường đi bên trong của cây con phải Độ dài đường đi của mỗi nút trong cả hai cây con được cộng thêm 1 khi tính từ nút gốc của toàn cây 2 5 1 7 9 8 3 2 2 5 1 7 9 8 11 6 Chứng minh d = O(log N) (3) Gốc Cây con có N-1 nút Tính giá trị trung bình của D(N) theo kiểu đệ quy: D(1) = 0 D(N) = 1/N i=0 N-1 [ D(i) + D(N-i-1) ] + N - 1 = 2/N i=0 N-1 D(i) + N - 1 Gốc Cây con có N-2 nút Cây con có 1 nút Gốc Cây con có N-3 nút Cây con có 2 nút Chứng minh d = O(log N) (4) D(N) = 2/N i=0 N-1 D(i) + N - 1 ND(N) = 2 i=0 N-1 D(i) + N(N-1) (1) (N-1)D(N-1) = 2 i=0 N-2 D(i) + (N-1)(N-2) (2) Lấy (1) trừ (2) theo từng vế, ta có: ND(N) - (N-1)D(N-1) = 2D(N-1) + 2(N-1) ND(N) = (N+1)D(N-1) + 2(N-1) D(N)/(N+1) = D(N-1)/N + 2(N-1)/[N(N+1)] < D(N-1)/N + 2/N D(N)/(N+1) < D(N-1)/N + 2/N D(N-1)/(N) < D(N-2)/(N-1) + 2/(N-1) D(N-2)/(N-1) < D(N-3)/(N-2) + 2/(N-2) ... D(2)/3 < D(1)/2 + 2/2 Chứng minh d = O(log N) (5) D(N)/(N+1) < D(N-1)/N + 2/N < D(N-2)/(N-1) + 2/(N-1) + 2/N < D(N-3)/(N-2) + 2/(N-2) + 2/(N-1) + 2/N ... < D(1)/(2) + 2/2 + ... + 2/(N-2) + 2/(N-1) + 2/N = 2 i=2 N 1/i Nếu ta chứng minh được i=2 N 1/i = O(logN) thì sẽ suy ra độ sâu trung bình của một nút là d = D(N)/N  D(N)/(N+1) = O(log N) D(N)/(N+1) < D(N-1)/N + 2/N D(N-1)/(N) < D(N-2)/(N-1) + 2/(N-1) D(N-2)/(N-1) < D(N-3)/(N-2) + 2/(N-2) ... D(2)/3 < D(1)/2 + 2/2 Chứng minh d = O(log N) (6) 1 2 3 4 1/ 2 f(x) = 1/x 1/ 3 1/ 4 Diện tích của các hình chữ nhật < diện tích dưới 1/x i=2 4 1/i < ∫1 4 1/x dx i=2 N 1/i < ∫1 N 1/x dx = ln N - ln 1 = O(logN)