MỤC LỤC I Tìm hiểu về cây nhị phân 1 Cây 2 Cây nhị phân 3 Cây nhị phân tìm kiếm 4 Cây AVL II Các giải thuật của cây nhị phân tìm kiếm 1 Duyệt cây 2 Tìm kiếm 3 Chèn 4 Xóa phần tử 5 Tìm kiếm có bổ sung III Cây tìm kiếm cơ số 1 Chèn 2 Tìm kiếm IV Đánh giá độ phức tạp 1 Cây tìm kiếm nhị phân 2 Cây tìm kiếm cơ số V Đánh giá, phân tích 1 Cây tìm kiếm nhị phân 2 Cây tìm kiếm cơ số 3 Cách khắc phục VI Trình bày giải thuật (Phần code C, C++) 1) Radix Search Tree Câu hỏi và trả lời I Tìm hiểu về cây nhị p.
Trang 11 Cây tìm kiếm nhị phân
2 Cây tìm kiếm cơ số
1 Cây tìm kiếm nhị phân
2 Cây tìm kiếm cơ số
3 Cách khắc phục
VI Trình bày giải thuật (Phần code C, C++)
1) Radix Search Tree
Trang 2Câu hỏi và trả lời
Các khái niệm:
Trang 3 Bậc của nút: là số nút con của nút đó Ví dụ bậc của nút A là 3, bậc của nút C là 1, bậc của nút G là 0…
Bậc của cây: là bậc lớn nhất của nút trong cây đó, cây bậc n sẽ được gọi là cây n – phân Ví dụ cây trong hình trên có bậc 3, gọi là cây tam phân, cây có bậc 2 gọi là cây nhị phân…
Chiều cao (chiều sâu): là mức lớn nhất của các nút lá Ví dụ cây trên cónút lá bậc lớn nhất là H, K, L mức 3, vậy chiều cao của cây là 3
Độ dài đường đi đến nút x: là số nhánh (cạnh nối hai nút) cần đi qua tính từ nút gốc đến nút x Hay độ dài đường đi đến nút mức i chính là i
Ví dụ nút E có độ dài đường đi là 2
2 Cây nhị phân (Binary tree)
Cây nhị phân là một cây mỗi nút trong nó có nhiều nhất 2 nút con
Trang 4Trong một nút của cây nhị phân bao gồm:
+) Dữ liệu (Data) : Bất kì dữ liệu nào
+) Con trỏ tới nút con bên trái : Lưa địa chỉ của nút con bên trái và kiểu
dữ liệu là Con trỏ trỏ vào node
+) Con trỏ tới nút con bên phải: Lưa địa chỉ của nút con bên trái và kiểu
dữ liệu là Con trỏ trỏ vào node
Ví dụ về một nút của cây nhị phân có dữ liệu có kiểu là int, được viết :
3 Cây nhị phân tìm kiếm (Binary search tree hay BST)
Cây nhị phân tìm kiếm là một cây nhị phân các phần tử đã được sắp xếp
Trang 5Các nút có đặc điểm sau:
+) Dữ liệu : Dữ liệu phải so sánh được
+) Cây con bên trái của một nút chỉ chứa các nút có dữ liệu nhỏ hơn dữ liệu của nút đó
+) Cây con bên phải của một nút chỉ chứa các nút có dữ liệu lớn hơn dữ liệu của nút đó
+) Mỗi cây con bên trái và bên phải cũng phải là một cây tìm kiếm nhị phân. Không được có các nút trùng lặp
- Ví dụ về cây nhị phân tìm kiếm:
Ví dụ:
Trang 6- Cây ở dưới không phải là cây nhị phân tìm kiếm bởi cây con bên phải của phần tử 5 có phần tử 3 bé hơn nó Do vậy cây dưới chỉ là một cây nhị phân.
4 Cây nhị phân cân bằng (Cây AVL)
Cây AVL là một cây nhị phân tìm kiếm tự cân bằng, mà tại mỗi đỉnh của cây độ cao của cây con trái và cây con phải không chênh lệch quá một
Ví dụ về cây AVL:
Trang 71 Duyệt cây nhị phân tìm kiếm
Có 3 cách duyệt cây nhị phân:
Trang 8- Duyệt tiền tự (NLR) (Pre – Oder): duyệt nút gốc, duyệt tiền tự cây contrái, duyệt tiền tự cây con phải
- Duyệt trung tự (LNR) (inOrder): duyệt trung tự cây con trái, duyệt nútgốc, duyệt trung tự cây con phải
- Duyệt hậu tự (LRN) (Post – Oder): duyệt hậu tự cây con trái, duyệt hậu tựcây con phải, duyệt nút gốc
- Duyệt tiền tự (giữa trái phải): A B D H I E K L C F M N G O P
- Duyệt trung tự (trái giữa phải): H D I B K E L A M F N C O G P
- Duyệt hậu tự (trái phải giữa): H I D K L E B M N F O P G C A
2 Tìm kiếm
1 Bắt đầu từ gốc.
2 So sánh phần tử cần tìm với gốc, nếu nhỏ hơn gốc thì gọi đệ quy cây con bên trái, nếu lớn hơn phần tử gốc gọi đệ quy cây con bên phải.
Trang 93 Nếu phần tử cần tìm kiếm được tìm thấy ở bất kỳ đâu, hãy trả về true, ngược lại trả về false.
Trang 103 Sau khi đến cuối, chỉ cần chèn nút đó ở bên trái (nếu nhỏ hơn hiện tại) nút khác bên phải.
B1: So sánh với phần tử gốc 4 < 8 nên ta sẽ tiếp tục tìm kiếm ở cây con bên trái
B2: So sánh với phần tử gốc cây con mới 4 > 3 nên ta sẽ tìm kiếm ở cây con bên phải
B4: So sánh với phần tử gốc cây con mới 4 < 6 nên ta sẽ tìm kiếm ở cây con bên trái
B5: Vì cây con bên trái là Null nên ta sẽ chèn phần tử 4 vào đây
4 Xóa phần tử
Trường hợp 1: Xóa một lá
Trang 11Ví dụ trên xóa phần tử 7 khỏi cây nhị phân tìm kiếm
Lần 1: 7 > 5 nên chuyển sang cây con bên phải
Lần 2: 7 > 6 nên chuyển sang cây con bên phải
Lần 3: 7 = 7 Kiểm tra cây con bên trái, cây con bên phải là bằng null nên => Ta sẽ xóa phần tử 7 ra khỏi cây nhị phân tìm kiếm
Trường hợp 2: Xóa một nút có 1 nút con
Ví dụ trên xóa phần tử 6 ra khỏi cây
Lần 1: 6 > 5 nên chuyển sang cây con bên phải
Lần 2: 6 = 6 nên ta sẽ kiểm tra cây con: Cây con bên phải có phần tử 7 mà cây con bên trái bằng null nên ta sẽ sao chép cây con 7 vào phần tử 6
Trang 12Trường hợp 3: Xóa một nút có 2 nút con
Ví dụ xóa phần tử 2 ở trên
Lần 1: 2 < 5 nên ta sẽ chuyển sang cây con bên trái
Lần 2: 2 = 2 ta sẽ kiểm tra cây con bên trái và cây con bên phải đều khác null.Lần 3: Lúc này ta sẽ tìm phần tử nhỏ nhất của cây con bên phải là 3
Lần 4: vì 3 là phần tử bé nhất nên ta sẽ sao chép cây con 3 vào phần tử 2 và xóa đi node hiện tại
5 Tìm kiếm phần tử có bổ sung
Tìm kiếm phần tử có bổ sung là khi tìm kiếm một phần tử trong cây không xuất hiện thì mình chèn phần tử đó vào cây Thực ra là sự kết hợp của tìm kiếm thông thường kết hợp với chèn
Radix search tree là một cây nhị phân có các khóa được liên kết với mỗi lácủa nó
+) Sử dụng một cặp (khóa , giá trị) thay vì giá trị như binary search tree
+) Khóa được cấu tạo từ 2 thành phần Ví dụ: 010101, abababbaba
Trang 13+) Khóa sẽ được phân tách được đặt dọc theo cây nhị phân
+) Ta sẽ phải quy định nút trái nút phải Ví dụ như bit 0 sẽ tham chiếu đến nút con bên trái, bit 1 sẽ tham chiếu tới nút con bên phải
+) Giá trị sẽ được lưu tại lá cuối cùng và một giá trị sẽ được liên kết với một khóa
+) Các khóa có độ dài cố định và không được có khóa nào trùng lặp với nhau
Ví dụ: Tạo một cây tìm kiếm cơ số có:
Một nút của một Cây tìm kiếm cơ số bao gồm :
- Một nút trong chứa khóa và giá trị được sử dụng ở nút lá
- Một con trỏ trái và một con trỏ phải
Trang 141 Chèn
Ta sẽ duyệt từ gốc đến lá kiểm tra xem nếu vị trí thứ i của khóa cần tìm nút nếu là 1 ta sẽ tìm kiếm sang cây con phải, nếu là 0 ta sẽ tìm cây con trái cho đến nút lá (nút ngoài cùng) Lúc này ta sẽ tạo nút mới lưa khóa và giá trị nếu không có nút nào ở lá thì ta sẽ chèn vào đó nếu có một nút ở lá thì: Bây giờ ta sẽ có 2 trường hợp:
Trường hợp 1: Hai vị trí i của node cũ và node mới khác nhau
Ta chỉ cần tạo một node tạm liên kết tới node cũ và node mới ở mỗi bênứng với
Trang 15Trường hợp 2: Vị trí i của hai node đều bằng nhau
Lúc này ta sẽ tạo một node tạm và tăng i lên sau đó tiếp tục so sánh nếu vẫn bằng nhau ta sẽ tạo thêm node liên kết tới node tạm trước đó cho đến khi khác nhau ta sẽ quay lại trường hợp 1
Trang 16Ví dụ : Ta chèn phần tử key 010 với giá trị 2 vào:
Tiếp tục ta chèn key 011 giá trị là 3 vào:
Trang 172 Tìm kiếm
Ta sẽ duyệt từ gốc đến lá kiểm tra xem nếu vị trí thứ i của khóa cần tìm nút nếu là 1 ta sẽ tìm kiếm sang cây con phải, nếu là 0 ta sẽ tìm cây con trái cho đến nút lá (nút ngoài cùng)
Có 3 trường hợp xãy ra : Nếu khóa của lá này trùng với khóa cần tìm nhưng giá trị bằng rỗng thì ta sẽ trả về rỗng; Nếu khóa của lá khác chiều dài của khóa cần tìm thì return về null; Nếu khóa của lá bằng khóa cần tìm thì trả về giá trị
Trang 18Nếu tìm kiếm đến nút ngoài cùng
nhưng giá trị lại không bằng ta có thể kết
hợp với chèn để thêm một cặp key value
vào đây gọi là tìm kiếm có bổ sung
Ví dụ: Ta có cây tìm kiếm cơ số sau tìm
kiếm giá trị với key 001:
Gọi d là vị trí của khóa cần tìm: d = 1; key sẽ là key thứ d : key[1] = 0
B1: Với d = 1 thì key = 0 ta sẽ tìm kiếm cây con bên trái
B2: d =2 => key = 0 tìm kiếm cây con bên trái
B3: d = 3=> key = 1 tìm kiếm cây con bên phải
B4: Key hiện tại bằng với key của lá nên ta sẽ trả về giá trị là 1
Trang 191 Cây nhị phân tìm kiếm
a Trường hợp tốt nhất
Đối với giải thuật tìm kiếm trường hợp tốt nhất của nó sẽ là O(1), trường hợp này xãy ra khi phần tử muốn tìm kiếm nằm ngay phần root, khi này sẽ chỉ tốn một phép toán
Còn với giải thuật chèn và xóa trường hợp tốt nhất sẽ là O(logn)
b Trường hợp trung bình
Trường hợp trung bình thường xãy ra với cây nhị phân cân bằng
Với n là số nút trong cây, h là chiều cao của cây
Chiều cao Số nút trên mỗi chiều cao Số nút tất cả
Trang 20bằng h = log(n) – 1 Như vậy trong trường hợp trung bình các giải thuậttìm kiếm, chèn, xóa sẽ có độ phức tạp là O(logn).
Trang 21Tìm kiếm O(1) O(logn) O(n)
2 Cây tìm kiếm cơ số
Các giải thuật trong cây tìm kiếm cơ số có độ phức tạp giống như cây nhị phân tìm kiếm ở trên ngoại trừ có sự khác biệt sau:
Đối với cây tìm kiếm cơ số ta sẽ có w là số nút mà khóa được chia ra
Trang 22Như vậy khóa ở trên được chia ra thành 4 bit riêng biệt được đặt từ gốc tới lá Lúc này w = 4
Thay vì các trường hợp sẽ tính theo n bây giờ mình sẽ tính theo w
Ta có bảng đánh giá đánh giá độ phức tạp về thời gian như sau:
- Phụ thuộc vào dạng của cây
- Tốn chi phí bộ nhớ hơn so với tìm kiếm tuần tự để cho các nút và tham chiếu
2 Cây tìm kiếm cơ số
Ưa điểm:
Trang 23- Ổn định hơn so với BST, nếu biểu diễn 1 đến số 1000 với key là mã số nhị phân tương ứng.
+ Radix search tree luôn ổn định, khi tìm kiếm giá trị chỉ tốn 12 lần, vì key được biểu diễn 12 số nhị phân
+ Binary search tree phụ thuộc vào hình dạng của cây, nếu cây là một dạng danh sách liên kết khi tìm kiếm lâu nhất sẽ tốn tận 1000 lần
- Phụ thuộc vào kiến trúc máy tính
- Khó làm hiệu quả triển khai ở một số ngôn ngữ cấp cao
3 Khắc phục
Binary search tree :
- Để có tính ổn định ta sẽ sử dụng cây AVL luôn đưa về dạng cân bằng để có độ phức tạp luôn là O(logn)
- Nếu có thể sử dụng Radix search tree
Radix search tree:
- Ta có thể tối ưa tìm kiếm bằng cách liên kết các giá trị với nhau
VI Trình bày giải thuật ( Phần code )
1 Radix Search Tree
VII #include <iostream>
VIII #include <stack>
Trang 24IX #include <string>
X.
XI struct Item{
XII std::string key;
XIII int value;
XIV };
XV.
XVI struct Node{
XVII Node* right = nullptr;
XVIII Node* left = nullptr;
XIX Item item;
XX };
XXI.
XXII Node* newNode(std::string nKey, int nValue){
XXIII Node* tmp = new Node();
XXIV tmp->item.key = nKey;
XXXVI Node* split(Node* nNode, Node* oNode, int pos){
XXXVII Node* tmp = new Node();
XXXVIII.
XXXIX switch (bit(nNode->item.key, pos)*2 + bit(oNode->item.key, pos))
XL {
XLII tmp->left = split(nNode, oNode, pos+1);
XLVIII case 2: // node cũ bằng 0 node mới bằng 1
XLIX tmp->left = oNode;
Trang 25LI break ;
LIII tmp->right = split(nNode, oNode, pos+1);
LXI if (nodeRoot == nullptr){
LXII return newNode(nKey, nValue);
LXIII }
LXIV.
LXV if (nodeRoot->right == nullptr && nodeRoot->left == nullptr){
LXVI return split(newNode(nKey, nValue), nodeRoot, pos);
LXVII }
LXVIII.
LXIX if (bit(nKey, pos)){
LXX nodeRoot->right = insertR(nodeRoot->right, nKey, nValue, pos + 1); LXXI } else {
LXXII nodeRoot->left = insertR(nodeRoot->left, nKey, nValue, pos + 1); LXXIII }
LXXIV return nodeRoot;
LXXV }
LXXVI.
LXXVII Item* searchR(Node* nodeRoot, std::string key, int pos){
LXXVIII if (nodeRoot == nullptr){
LXXIX return nullptr;
LXXX }
LXXXI
LXXXII if (nodeRoot->left == nullptr && nodeRoot->right == nullptr){
LXXXIII if (nodeRoot->item.key == key){
LXXXIV return &nodeRoot->item;
Trang 26XCV }
XCVI.
XCVII /*
XCVIII void deleteNode(Node* node){
XCIX node->left = nullptr;
CXVIII return root;
CXIX }else if (tmp->right == tmp1 && tmp->left != nullptr){
CXXVII Node* removeR(Node* nodeRoot, std::string key, int pos){
CXXVIII if (nodeRoot != nullptr){
CXXIX std::stack<Node*> s;
CXXX s.push(nodeRoot);
CXXXI Node* tmp = nullptr;
CXXXII Node* tmp1 = nullptr;
CXXXIII Node* tmp2 = nullptr;
Trang 27CLXXXI Node* root = nullptr;
CLXXXII root = insertR(root, "000" , 0);
Trang 28CLXXXIII root = insertR(root, "001" , 1);
CLXXXIV root = insertR(root, "110" , 6);
CLXXXV root = insertR(root, "111" , 7);
CLXXXVI root = insertR(root, "010" , 2);
CLXXXVII root = insertR(root, "011" , 3);
CLXXXVIII.
CLXXXIX Item* tmp = nullptr;
CXC tmp = searchR(root, "101" , 0); CXCI std::cin.get();
CXCII }