Tải bản đầy đủ (.docx) (28 trang)

Cây tìm kiếm cơ số (Rst - Radix Search Tree) và Phần Code

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (890.26 KB, 28 trang )

MỤC LỤC
I.

Tìm hiểu về cây nhị phân
1.
2.
3.
4.

II.

Cây
Cây nhị phân
Cây nhị phân tìm kiếm
Cây AVL

Các giải thuật của cây nhị phân tìm kiếm
1.
2.
3.
4.
5.

Duyệt cây
Tìm kiếm
Chèn
Xóa phần tử
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.

VI.

Đá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
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ị phân

1. Cây (tree)
Cây (Tree) là một cấu trúc dữ liệu phân cấp, khác với Mảng, Danh sách liên
kết, Ngăn xếp, Hàng đợi là một dạng cấu trúc dữ liệu tuyến tính.
Trong một cây: Nút (Node) trên cùng hay đầu tiên gọi là gốc (root). Các nút
nằm bên dưới nó gọi là nút con và nút nằm bên trên gọi là nút cha của nó.

Các nút khơng có con gọi là lá.

Các khái niệm:

2


 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…
 Nút lá: nút lá là nút có bậc bằng 0. Ví dụ các nút lá: B, G, H, K, L, F
 Nút nhánh: là nút có bậc khác 0 mà khơng phải nút gốc (hay cịn gọi là
nút trung gian). Ví dụ các nút C, D, E
 Mức của nút: là số nguyên đếm từ 0, các nút ngang hàng nhau thì có
cùng mức. Nút gốc A có mức là 0, mức 1 gồm các nút B, C, D, mức 3
gồm H, K, L.
 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.

3



Trong 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 :
struct Node
{
int data;
Node *left;
Node *right;
};
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

4


Cá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ụ:

5


- 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:

6


.

II.

Các thuật tốn của cây nhị phân tìm kiếm.
1. Duyệt cây nhị phân tìm kiếm

Có 3 cách duyệt cây nhị phân:

7



- Duyệt tiền tự (NLR) (Pre – Oder): duyệt nút gốc, duyệt tiền tự cây con
trá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út
gố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. 

8


3. 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.

Ví dụ: Tìm kiếm số 16 cây nhị phân tìm kiếm bên dưới:

B1: So sánh với phần tử gốc 16 > 15 nên ta sẽ tiếp tục tìm kiếm ở cây
con bên phải.
B2: So sánh với phần tử gốc cây con mới 16 < 20 nên ta sẽ tìm kiếm ở
cây con bên trái.
B4: So sánh với phần tử gốc cây con mới 16 < 18 nên ta sẽ tìm kiếm ở
cây con bên trái.

B5: So sánh với phần tử gốc cây con mới 16 = 16 đã được tìm thấy trả
về giá trị true.
3. Chèn phần tử
1. Bắt đầu từ gốc. 
2. So sánh phần tử chèn với gốc, nếu nhỏ hơn gốc thì gọi đệ quy cây con
bên trái, còn lại gọi đệ quy cây con bên phải. 

9


3. 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á

10


Ví 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

11


Trườ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.

III.

Cây tìm kiếm cơ số (Radix search tree)

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.

12


+) 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ó:

Khóa
010
011
100
110

Giá trị
2
3
4
6

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


13


struct Node{
struct Item {
KEY key;
VALUE value;
};
Node *left;
Node *right;
};
Các thuật toán tìm kiếm và chèn giống hệt với cây nhị phân tìm kiếm ngoại
trừ một sự khác biệt: Chúng tơi phân nhánh trên cây không theo kết quả của
việc so sánh giữa các khóa đầy đủ, nhưng thay vì theo tới các bit đã chọn của
khóa. Ở mức đầu tiên, bit đầu tiên được sử dụng; ở mức thứ hai, bit đứng đầu
thứ hai được sử dụng; và cứ thế, cho đến khi đến khi gặp một nút bên ngoài.
1. 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

14


Trườ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.

15


Ví dụ : Ta chèn phần tử key 010 với giá trị 2 vào:

Key
000
001
110
111
010

Tiếp tục ta chèn key 011 giá trị là 3 vào:

16

Value
0
1
6
7
2


Key
000

001
110
111
010
011

Value
0
1
6
7
2
3

2. 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 ngồ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ị.

17


Nếu tìm kiếm đến nút ngồ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

Key
000
001
110
111

Value
0
1
6
7

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.

IV.

Đánh giá độ phức tạp

18


1. 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 tố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
0
1
2
3

h-1

Số nút trên mỗi chiều cao

Số nút tất cả





Các giải thuật trong trường hợp này xấu nhất sẽ chạy từ root đến cuối
cây hay duyệt một chiều cao là h. Lúc này số nút mà h duyệt qua sẽ

19



bằng h = log(n) – 1. Như vậy trong trường hợp trung bình các giải thuật
tìm kiếm, chèn, xóa sẽ có độ phức tạp là O(logn).
c. Trường hợp xấu nhất
Trong trường hợp xấu nhất, chúng ta sẽ duyệt cây từ gốc tới nút lá sâu
nhất đối với cây nhị phân tìm kiếm chỉ có một nút con.

Lúc này chiều cao h đúng bằng số nút n. Như vậy độ phức tạp của các
giải thuật tìm kiếm, xóa, chèn là O(n).
Đối với duyệt cây nhị phân tìm kiếm sẽ có độ phức tạp trong mọi
trường hợp là O(n).
Ta có bảng đánh giá đánh giá độ phức tạp về thời gian như sau:
Tốt nhất

Trung bình

20

Xấu nhất


Tìm kiếm
Chèn
Xóa
Duyệt

O(1)
O(logn)
O(logn)
O(n)


O(logn)
O(logn)
O(logn)
O(n)

O(n)
O(n)
O(n)
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.
Ví dụ như sau:
Khóa
Giá trị
0011
3
1001
9
1100
12
1101
13

21



Như 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:
Tìm kiếm
Chèn
Xóa
Duyệt

V.

Tốt nhất
O(logw)
O(logw)
O(logw)
O(w)

Trung bình
O(logw)
O(logw)
O(logw)
O(w)

Xấu nhất
O(w)
O(w)
O(w)
O(w)


Đánh giá, phân tích
1. Cây nhị phân tìm kiếm
Ưa điểm:
- Tìm kiếm tốt hơn so với tìm kiếm tuần tự vì trường hợp
trung bình của nó O(logn).
- Tổ chức cấu trúc hợp lý, dễ quản lý hơn so với cây nhị
phân thông thường.
Nhược điểm:
- Khi cây nhị phân là một dạng giống danh sách liên kết nó
tốn chi phí O(n)
- 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:

22


- Ổ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 ln ổ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.
Nhược điểm:
- Phụ thuộc vào giá trị có key được biểu diễn dưới dạng bit,
… hay không.
- Tốn không gian bộ nhớ O(2n) để lưa trữ key, trong khi BST

tốn O(n).
- Dữ liệu sai lệch có thể dẫn đến suy thối cây có hiệu
suất kém .
- 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 ln đưa về
dạng cân bằng để có độ phức tạp ln 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.
VIII.

#include <iostream>
#include <stack>

23


IX.
X.


#include <string>

XI.
XII.
XIII.
XIV.
XV.

struct Item{
std::string key;
int value;
};

XVI.
XVII.
XVIII.
XIX.
XX.
XXI.
XXII.
XXIII.
XXIV.
XXV.
XXVI.
XXVII.
XXVIII.
XXIX.
XXX.
XXXI.

XXXII.
XXXIII.
XXXIV.
XXXV.
XXXVI.
XXXVII.
XXXVIII.
XXXIX.
XL.
XLI.
XLII.
XLIII.
XLIV.
XLV.
XLVI.
XLVII.
XLVIII.
XLIX.
L.

struct Node{
Node* right = nullptr;
Node* left = nullptr;
Item item;
};

Node* newNode(std::string nKey, int nValue){
Node* tmp = new Node();
tmp->item.key = nKey;
tmp->item.value = nValue;

return tmp;
}

bool bit(std::string nKey, int pos){
if (nKey[pos] == '1'){
return true;
}
return false;
}
Node* split(Node* nNode, Node* oNode, int pos){
Node* tmp = new Node();
switch (bit(nNode->item.key, pos)*2 + bit(oNode->item.key, pos))
{
case 0:// cả hai vị trí pos trong key mỗi node đều bằng 0
tmp->left = split(nNode, oNode, pos+1);
break;
case 1:// node cũ bằng 1 node mới bằng 0
tmp->left = nNode;
tmp->right = oNode;
break;
case 2:// node cũ bằng 0 node mới bằng 1
tmp->left = oNode;
tmp->right = nNode;

24


LI.
LII.
LIII.

LIV.
LV.
LVI.
LVII.
LVIII.
LIX.
LX.
LXI.
LXII.
LXIII.
LXIV.
LXV.
LXVI.
LXVII.
LXVIII.
LXIX.
LXX.
LXXI.
LXXII.
LXXIII.
LXXIV.
LXXV.
LXXVI.
LXXVII.
LXXVIII.
LXXIX.
LXXX.
LXXXI.
LXXXII.
LXXXIII.

LXXXIV.
LXXXV.
LXXXVI.
LXXXVII.
LXXXVIII.
LXXXIX.
XC.
XCI.
XCII.
XCIII.
XCIV.

break;
case 3:// cả hai vị trí pos trong key mỗi node đều bằng 1
tmp->right = split(nNode, oNode, pos+1);
break;
}
return tmp;
}
Node* insertR(Node* nodeRoot, std::string nKey, int nValue, int pos = 0){
if (nodeRoot == nullptr){
return newNode(nKey, nValue);
}
if (nodeRoot->right == nullptr && nodeRoot->left == nullptr){
return split(newNode(nKey, nValue), nodeRoot, pos);
}
if (bit(nKey, pos)){
nodeRoot->right = insertR(nodeRoot->right, nKey, nValue, pos + 1);
}else{
nodeRoot->left = insertR(nodeRoot->left, nKey, nValue, pos + 1);

}
return nodeRoot;
}

Item* searchR(Node* nodeRoot, std::string key, int pos){
if (nodeRoot == nullptr){
return nullptr;
}
if (nodeRoot->left == nullptr && nodeRoot->right == nullptr){
if (nodeRoot->item.key == key){
return &nodeRoot->item;
}else{
return nullptr;
}
}
if (bit(key, pos)){
return searchR(nodeRoot->right, key, pos+1);
}else{
return searchR(nodeRoot->left, key, pos+1);
}

25


×