Bài giảng Cấu trúc dữ liệu và giải thuật: Cây nhị phân tìm kiếm cung cấp cho bạn đọc những kiến thức về khái niệm cây nhị phân tìm kiếm, các thao tác trên cây nhị phân tìm kiếm, một vài ví dụ sử dụng cây nhị phân tìm kiếm.
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 • Giả thiết giá trị khác • Cây nhị phân tìm kiếm nhị phân, với nút X: − Tất giá trị trái X nhỏ X − Tất giá trị phải X lớn X Đây có phải nhị phân tìm kiếm? Các thao tác • • • • • Tìm phần tử nhỏ Tìm phần tử lớn Tìm phần tử x Chèn phần tử x Xóa phần tử x Tất thao tác có thời gian chạy trung bình O(log N) chứng minh sau Cài đặt template // T kiểu 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 Chèn/xóa phần tử x; private: struct BinaryNode { }; BinaryNode * root; Các hàm trợ giúp; }; phần tử tử x; // Kiểu nút // Con trỏ tới nút gốc Kiểu nút struct BinaryNode { T elem; // Phần tử BinaryNode * left; // Con trỏ tới trái BinaryNode * right; // Con trỏ tới phải // Hàm tạo cấu trúc BinaryNode BinaryNode(T x, BinaryNode * l, BinaryNode * r) { elem = x; // Khởi tạo trường phần tử left = l; // Khởi tạo trường trỏ trái right = r; // Khởi tạo trường trỏ phải } }; Hàm tạo, hàm hủy, xóa rỗng BinarySearchTree() { root = NULL; // Ban đầu rỗng } ~BinarySearchTree() { makeEmpty(); // Xóa hết nút } void makeEmpty() { // Hàm xóa rỗng makeEmpty(root); // Gọi hàm private trợ giúp (slide sau) } bool isEmpty() { // Hàm kiểm tra rỗng return (root == NULL); } Xóa rỗng có gốc t // Hàm private trợ giúp việc xóa rỗng cây, viết theo // kiểu đệ quy void makeEmpty(BinaryNode * & t) { Có dấu & trước t gán giá trị cho t thân hàm if (t == NULL) return; // Thoát rỗng makeEmpty(t->left); // Xóa rỗng trái makeEmpty(t->right); // Xóa rỗng phải delete t; // Xóa nút gốc t = NULL; // Cây rỗng tức khơng tồn gốc } Tìm phần tử nhỏ // Hàm public T findMin() { BinaryNode * v = findMin(root); // Gọi hàm private return v->elem; // Lấy phần tử nhỏ trả } // Hàm private trợ giúp (dùng đệ quy) // Phần tử nhỏ nằm nút bên trái BinaryNode * findMin(BinaryNode if (t == NULL) // return NULL; if(t->left == NULL) // return t; return findMin(t->left); // } * t) { Cây rỗng? Nút bên trái? Tìm trái Tìm phần tử lớn // Hàm public T findMax() { BinaryNode * v = findMax(root); // Gọi hàm private return v->elem; // Lấy phần tử lớn trả } // Hàm private trợ giúp (dùng vòng lặp thay cho đệ quy) // Phần tử lớn nằm nút bên phải 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; } Chèn phần tử Trước chèn Sau chèn 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 } Có dấu & trước t gán giá trị // Hàm private trợ giúp (chèn x vào có gốc t) cho t thân hàm void insert(T x, BinaryNode * & t) { if (t == NULL) // Cây rỗng tạo nút t = new BinaryNode(x, NULL, NULL); else if (x < t->elem) // Nếu x nhỏ insert(x, t->left); // chèn x else if (x > t->elem) // Nếu x lớn insert(x, t->right); // chèn x else // Gặp x khơng làm ; // Câu lệnh rỗng } chứa x giá vào giá vào trị trị xét trái xét phải Xóa nút Trước xóa Sau xóa Cách xóa: Chỉ đơn giản xóa nút Xóa nút có Trước xóa Sau xóa Cách xóa: Trước xóa, cho nút cha trỏ tới nút nút bị xóa Xóa nút có hai Trước xóa Sau xóa Cách xóa: Thay nút bị xóa (2) nút nhỏ phải (3) Sau đó, xóa nút nhỏ phải (là nút nút con) 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ó gốc t) void remove(T x, BinaryNode * & t) { Có dấu & trước t // Xem code slide sau gán giá trị cho t thân hàm } Cài đặt thao tác xóa (tiếp) void remove(T x, BinaryNode * & t) { if (t == NULL) return; // Thoát rỗng if (x < t->elem) // Nếu x nhỏ giá trị xét remove(x, t->left); // xóa x trái else if (x > t->elem) // Nếu x lớn giá trị xét remove(x, t->right); // xóa x phải else if (t->left != NULL && t->right != NULL) { // Nút t->elem = findMin(t->right)->elem; Tìm phải remove(t->elem, t->right); đặt vào nút cần xóa } Xóa nút phải else { // Nút BinaryNode * old = t; // Giữ lại địa nút cần xóa t = (t->left != NULL) ? t->left : t->right; delete old; // Xóa Xác định (có thể NULL } Chú ý: t liên kết từ nút trường hợp nút lá) trái hay phải } cần xóa tới nút Phân tích thời gian chạy • Gọi n tổng số nút • Gọi d độ sâu trung bình nút • Thao tác xóa rỗng có thời gian chạy O(n), có nút phải xóa nút nhiêu lần • Các thao tác tìm/chèn/xóa có thời gian chạy trung bình O(d), diễn hai bước sau đây: Đi từ nút gốc tới nút v, nơi diễn thao tác, thời trung bình O(d) Xử lý nút v vài thao tác bản, tức O(1) • Tiếp theo, ta chứng minh d = O(log n), thời gian tìm/chèn/xóa trung bình O(log n) Chứng minh d = O(log n) (1) • Độ sâu trung bình nút: d = Tổng-độ-sâu-của-các-nút / Số-nút = D/n Tổng độ sâu nút (D) gọi độ dài đường bên • Hãy tính độ dài đường bên sau: 2 Chứng minh d = O(log n) (2) • Nếu trái nút gốc có i nút: D(n) = D(i) + D(n-i-1) + n-1 − D(i) độ dài đường bên trái − D(n-i-1) độ dài đường bên phải − Độ dài đường nút hai cộng thêm tính từ nút gốc toàn 11 Chứng minh d = O(log n) (3) Tính giá trị trung bình D(n): D(1) = D(n) = 1/n i=0n-1 [ D(i) + D(n-i-1) ] + n-1 = 2/n i=0n-1 D(i) + n-1 Gốc Cây có n-1 nút Gốc Cây có nút Cây có n-2 nút Gốc Cây có nút Cây có n-3 nút Chứng minh d = O(log n) D(n) = 2/n i=0n-1 D(i) + n-1 nD(n) = i=0n-1 D(i) + n(n-1) (n-1)D(n-1) = i=0n-2 D(i) + (n-1)(n-2) Lấy (1) trừ (2) theo 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 (4) (1) (2) 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-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 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 = i=2n 1/i Nếu ta chứng minh i=2N 1/i = O(log n) suy độ sâu trung bình nút d = D(n)/n D(n)/(n+1) = O(log n) Chứng minh d = O(log n) (6) Diện tích hình chữ nhật < diện tích 1/x i=24 1/i < ∫14 1/x dx i=2n 1/i < ∫1n 1/x dx = ln n - ln = O(log n) f(x) = 1/x 1/ 1/ 1/ 4 Bài tập Chèn giá trị sau vào nhị phân tìm kiếm rỗng: 20, 15, 19, 26, 31, 21, 14, 23, 25 Sau đó, xóa nút gốc Viết hàm nhận vào nhị phân tìm kiếm hai giá trị k1 k2, k1 k2 Hàm in tất giá trị nằm khoảng [k1; k2] ... 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 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)... i=0n-1 D(i) + n(n-1) (n-1)D(n-1) = i=0n-2 D(i) + (n-1)(n-2) Lấy (1) trừ (2) theo 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)]... i=0n-1 [ D(i) + D(n-i-1) ] + n-1 = 2/n i=0n-1 D(i) + n-1 Gốc Cây có n-1 nút Gốc Cây có nút Cây có n-2 nút Gốc Cây có nút Cây có n-3 nút Chứng minh d = O(log n) D(n) = 2/n i=0n-1 D(i) + n-1 nD(n)