Professional Documents
Culture Documents
Danh sách liên kết bao gồm các phần tử. Mỗi phần tử của danh sách đơn là một cấu trúc
chứa 2 thông tin :
- Thành phần dữ liệu: lưu trữ các thông tin về bản thân phần tử .
- Thành phần mối liên kết: lưu trữ địa chỉ của phần tử kế tiếp trong danh sách, hoặc lưu trữ
giá trị NULL nếu là phần tử cuối danh sách.
1
// con trỏ chỉ đến cấu trúc node
}NODE;
Các phần tử trong danh sách sẽ được cấp phát động. Biết phần tử đầu tiên ta sẽ truy
xuất được các phần tử tiếp theo. Thường sử dụng con trỏ Head để lưu trữ địa chỉ đầu tiên
của danh sách.
Ta có khai báo:
NODE *pHead;
Để quản lý địa chỉ cuối cùng trong danh sách ta dùng con trỏ TAIL. Khai báo như sau:
NODE *pTail;
VD:
NODE *new_ele // giữ địa chỉ của một phần tử mới được tạo
Data x; // lưu thông tin về một phần tử sẽ được tạo
LIST lst; // lưu trữ địa chỉ đầu, địa chỉ cuối của danh sách liên kết
Thuật toán :
Bắt đầu:
Nếu Danh sách rỗng Thì
B11 : pHead = new_ele;
B12 : pTail = pHead;
Ngược lại
B21 : new_ele ->pNext = pHead;
B22 : pHead = new_ele ;
Cài đặt:
Thuật toán :
Bắt đầu :
Nếu Danh sách rỗng thì
B11 : pHead = new_elelment;
B12 : pTail = pHead;
Ngược lại
B21 : pTail ->pNext = new_ele;
B22 : pTail = new_ele ;
3
Thuật toán :
Bắt đầu :
Nếu ( q != NULL) thì
B1 : new_ele -> pNext = q->pNext;
B2 : q->pNext = new_ele ;
Cài đặt :
Thuật toán :
Bước 1:
p = pHead; //Cho p trỏ đến phần tử đầu danh sách
Bước 2:
Trong khi (p != NULL) và (p->Info != k ) thực hiện:
p:=p->pNext;// Cho p trỏ tới phần tử kế
Bước 3:
Nếu p != NULL thì p trỏ tới phần tử cần tìm
Ngược lại: không có phần tử cần tìm.
Cài đặt :
Thuật toán :
Bắt đầu:
Nếu (pHead != NULL) thì
B1: p = pHead; // p là phần tử cần hủy
B2:
B21 : pHead = pHead->pNext; // tách p ra khỏi xâu
B22 : free(p); // Hủy biến động do p trỏ đến
B3: Nếu pHead=NULL thì pTail = NULL; //Xâu rỗng
4
Hủy một phần tử đứng sau phần tử q
Thuật toán :
Bắt đầu:
Nếu (q!= NULL) thì
B1: p = q->Next; // p là phần tử cần hủy
B2: Nếu (p != NULL) thì// q không phải là cuối xâu
B21 : q->Next = p->Next; // tách p ra khỏi xâu
B22 : free(p); // Hủy biến động do p trỏ đến
6
II. Danh sách liên kết kép
Là danh sách mà mỗi phần tử trong danh sách có kết nối với 1 phần tử đứng trước và 1
phần tử đứng sau nó.
Khai báo:
typedef struct tagDNode
{
Data Info;
struct tagDNode* pPre; // trỏ đến phần tử đứng trước
struct tagDNode* pNext; // trỏ đến phần tử đứng sau
}DNODE;
Cài đặt :
Cách 2: Chèn vào cuối danh sách
Cài đặt :
Cách 3 : Chèn vào danh sách sau một phần tử q
7
Cài đặt :
Cài đặt :
2. Hủy một phần tử khỏi danh sách
- Hủy phần tử đầu xâu
- Hủy phần tử cuối xâu
- Hủy một phần tử đứng sau phần tử q
- Hủy một phần tử đứng trước phần tử q
- Hủy 1 phần tử có khoá k
3. Xử lý các nút trên danh sách:
- Tìm nút có khóa k
- Hiển thị giá trị khóa của các nút trong danh sách
- Hủy tòan bộ danh sách
8
III. Ngăn xếp (stack)
Stack chứa các đối tượng làm việc theo cơ chế LIFO (Last In First Out) nghĩa là việc
thêm một đối tượng vào stack hoặc lấy một đối tượng ra khỏi stack được thực hiện theo cơ
chế "Vào sau ra trước".
Thao tác thêm 1 đối tượng vào stack thường được gọi là "Push".
Thao tác lấy 1 đối tượng ra khỏi stack gọi là "Pop".
Trong tin học, CTDL stack có nhiều ứng dụng: khử đệ qui, lưu vết các quá trình tìm
kiếm theo chiều sâu và quay lui, ứng dụng trong các bài toán tính toán biểu thức, .
VD:
Tạo stack S và quản lý đỉnh stack bằng biến t – chỉ số của phần từ trên cùng trong
stack:
Data S [N];
int t;
Hàng đội
10
Trạng thái hàng đợi lúc bình thường:
Q – biến hàng đợi, f quản lý đầu hàng đợi, r quản lý phần tử cuối hàng đợi.
Trạng thái hàng đợi lúc xoay vòng (mảng rỗng ở giữa):
Câu hỏi đặt ra: khi giá trị f=r cho ta điều gì ? Ta thấy rằng, lúc này hàng đợi chỉ có thể
ở một trong hai trạng thái là rỗng hoặc đầy.
Bài tập:
Nếu số phần tử trong dãy con 1, 3 lớn hơn 1 thì ta tiếp tục phân hoạch dãy 1, 3 theo
phương pháp trên. Ngược lại thì: dừng.
Giải thuật phân hoạch dãy am, am+1, ., an thành 2 dãy con:
Bước 1 : Chọn tùy ý một phần tử a[k] trong dãy là giá trị biên, m<= k <=n:
x = a[k]; i = m; j = n;
Bước 2 : Phát hiện và hiệu chỉnh cặp phần tử a[i], a[j] nằm sai vị trí:
Bước 2a : Trong khi (a[i]<x) i++;
Bước 2b : Trong khi (a[j]>x) j--;
Bước 2c : Nếu i<= j
// a[i]>= x; a[j]<=x mà a[j] đứng sau a[i]
Hoán vị (a[i],a[j]);
i++;
j--;
Bước 3 :
Nếu i <= j: Lặp lại Bước 2.//chưa xét hết mảng
Ngược lại: Dừng
Có thể phát biểu giải thuật sắp xếp QuickSort một cách đệ qui như sau :
12
Phân hoạch đoạn l =1, r = 8: x = A[4] =5
8: x = A[7] = 6
Dừng.
Cài đặt
13
Ðánh giá giải thuật
Hiệu qủa thực hiện của giải thuật QuickSort phụ thuộc vào việc chọn giá trị mốc.
Trường hợp tốt nhất xảy ra nếu mỗi lần phân hoạch đều chọn được phần tử median
(phần tử lớn hơn (hay bằng) nửa số phần tử, và nhỏ hơn (hay bằng) nửa số phần tử còn lại)
làm mốc, khi đó dãy được phân chia thành 2 phần bằng nhau và cần log2(n) bước phân
hoạch thì sắp xếp xong.
Nhưng nếu mỗi bước phân hoạch phần tử được chọn có giá trị cực đại (hay cực tiểu) là
mốc, dãy sẽ bị phân chia thành 2 phần không đều: một phần chỉ có 1 phần tử, phần còn lại
gồm (n-1) phần tử, do vậy cần thực hiện n bước phân hoạch mới sắp xếp xong. Ta có bảng
tổng kết
Trường hợp Ðộ phức tạp
Tốt nhất n*log(n)
Xấu nhất n2
Ý tưởng:
Khác với các thuật toán trước, Radix sort là một thuật toán tiếp cận theo một hướng
hoàn toàn khác. Nếu như trong các thuật toán khác, cơ sở để sắp xếp luôn là việc so sánh giá
trị của 2 phần tử thì Radix sort lại dựa trên nguyên tắc phân loại thư của bưu điện.
Ta biết rằng, để đưa một khối lượng thư lớn đến tay người nhận ở nhiều địa phương
khác nhau, bưu điện thường tổ chức một hệ thống phân loại thư phân cấp:
Trước tiên, các thư đến cùng một tỉnh, thành phố sẽ được sắp chung vào một lô để gửi
đến tỉnh thành tương ứng.
Bưu điện các tỉnh thành này lại thực hiện công việc tương tự. Các thư đến cùng một
quận, huyện sẽ được xếp vào chung một lô và gửi đến quận, huyện tương ứng. Cứ như vậy,
các bức thư sẽ được trao đến tay người nhận một cách có hệ thông mà công việc sằp xếp thư
không quá nặng nhọc.
Mô phỏng lại qui trình trên, để sắp xếp dãy a1, a2, ..., an, giải thuật Radix Sort thực
hiện như sau:
Trước tiên, ta có thể giả sử mỗi phần tử ai trong dãy: a1, a2, ..., an là một số nguyên có
tối đa m chữ số.
Ta phân loại các phần tử lần lượt theo các chữ số hàng đơn vị, hàng chục, hàng trăm, .
tương tự việc phân loại thư theo tỉnh thành, quận huyện, phường xã, ..
Ví dụ
Cho dãy số a:
701 1725 999 9170 3252 4518 7009 1424 428 1239 8425 7013
Phân lô theo hàng đơn vị:
12 0701
11 1725
10 0999
9 9170
8 3252
7 4518
6 7009
5 1424
4 0428
3 1239 0999
2 8425 1725 4518 7009
1 7013 9170 0701 3252 7013 1424 8425 0428 1239
CS A 0 1 2 3 4 5 6 7 8 9
Các lô B dùng để phân loại
15
2 0701 7009 4518 8425
1 9170 0701 7013 1424 1239 3252 9170 0999
CS A 0 1 2 3 4 5 6 7 8 9
Phân lô theo hàng trăm:
12 0999
11 9170
10 3252
9 1239
8 0428
7 1725
6 8425
5 1424
4 4518
3 7013 0428
2 7009 7013 3252 8425 1725
1 0701 7009 9170 1239 1424 4518 0701 0999
CS A 0 1 2 3 4 5 6 7 8 9
Phân lô theo hàng ngàn:
12 0999
11 1725
10 0701
9 4518
8 0428
7 8425
6 1424
5 3252
4 1239
3 9170 0999 1725
2 7013 0701 1424 7013
1 7009 0428 1239 3252 4518 7009 8425 9170
CS A 0 1 2 3 4 5 6 7 8 9
Lấy các phần tử từ các lô B0, B1, ., B9 nối lại thành a:
12 9170
11 8425
10 7013
9 7009
8 4518
7 3252
6 1725
5 1424
4 1239
16
3 0999
2 0701
1 0428
CS A 0 1 2 3 4 5 6 7 8 9
Trong đó một phần tử ở mức i chính là phần tử lớn trong cặp phần tử ở mức i+1, do
đó phần tử ở mức 0 (nút gốc của cây) luôn là phần tử lớn nhất của dãy.
Nếu loại bỏ phần tử gốc ra khỏi cây (nghĩa là đưa phần tử lớn nhất về đúng vị trí), thì
việc cập nhật cây chỉ xảy ra trên những nhánh liên quan đến phần tử mới loại bỏ, còn các
17
nhánh khác được bảo toàn, nghĩa là bước kế tiếp có thể sử dụng lại các kết quả so sánh ở
bước hiện tại.
Trong ví dụ trên ta có :
Loại bỏ 8 ra khỏi cây và thế vào các chỗ trống giá trị -∞ để tiện việc cập nhật lại cây :
Tiến hành nhiều lần việc loại bỏ phần tử gốc của cây cho đến khi tất cả các phần tử của
cây đều là -∞, khi đó xếp các phần tử theo thứ tự loại bỏ trên cây sẽ có dãy đã sắp xếp. Trên
đây là ý tưởng của giải thuật sắp xếp cây.
18
Tính chất 3 : Mọi dãy ap , a2 ,... , aq, dãy con aj, aj+1,…, ar tạo thành một heap với
j=(q div 2 +1).
Dựa trên tính chất 3, ta có thể thực hiện giai đoạn 1 bằng cách bắt đầu từ heap mặc
nhiên an/2+1 , an/2+2 ... an, sau đó thêm dần các phần tử an/2, an/2-1, ., a1 ta sẽ nhân được
heap theo mong muốn.
Ví dụ
Cho dãy số a:
12 2 8 5 1 6 4 15
Giai đoạn 1: hiệu chỉnh dãy ban đầu thành heap
19
Giai đoạn 2: Sắp xếp dãy số dựa trên heap :
20
thực hiện tương tự cho r=5,4,3,2 ta được:
Cài đặt
Trong giai đoạn sắp xếp ta cần thực hiện n-1 bước mỗi bước cần nhiều nhất là log2(n-1),
log2(n-2), … 1 phép đổi chỗi.
Như vậy độ phức tạp thuật toán Heap sort O(nlog2n)
Phép băm được đề xuất và hiện thực trên máy tính từ những năm 50 của thế kỷ 20. Nó
dựa trên ý tưởng: biến đổi giá trị khóa thành một số (xử lý băm) và sử dụng số này để đánh
chỉ cho bảng dữ liệu.
21
Các phép toán trên các cấu trúc dữ liệu như danh sách, cây nhị phân,… phần lớn được
thực hiện bằng cách so sánh các phần tử của cấu trúc, do vậy thời gian truy xuất không
nhanh và phụ thuộc vào kích thước của cấu trúc.
Trong bài này chúng ta sẽ khảo sát một cấu trúc dữ liệu mới được gọi là bảng băm
(hash table). Các phép toán trên bảng băm sẽ giúp hạn chế số lần so sánh, và vì vậy sẽ cố
gắng giảm thiểu được thời gian truy xuất. Độ phức tạp của các phép toán trên bảng băm
thường có bậc là 0(1) và không phụ thuộc vào kích thước của bảng băm.
Các khái niệm chính trên cấu trúc bảng băm:
· Phép băm hay hàm băm (hash function)
· Tập khoá của các phần tử trên bảng băm
· Tập địa chỉ trên bảng băm
· Phép toán thêm phần tử vào bảng băm
· Phép toán xoá một phần tử trên bảng băm
· Phép toán tìm kiếm trên bảng băm
Thông thường bảng băm được sử dụng khi cần xử lý các bài toán có dữ liệu lớn và
được lưu trữ ở bộ nhớ ngoài.
22
1. PHÉP BĂM (Hash Function)
Định nghĩa:
Trong hầu hết các ứng dụng, khoá được dùng như một phương thức để truy xuất dữ
liệu. Hàm băm được dùng để ánh xạ giá trị khóa khoá vào một dãy các địa chỉ của bảng băm
(hình 1).
Hình 1
Khóa có thể là dạng số hay số dạng chuỗi. Giả sử có 2 khóa phân biệt k i và kj nếu
h(ki)=h(kj) thì hàm băm bị đụng độ.
Một hàm băm tốt phải thỏa mãn các điều kiện sau:
Tính toán nhanh.
Các khoá được phân bố đều trong bảng.
Ít xảy ra đụng độ.
Xử lý được các loại khóa có kiểu dữ liệu khác nhau
Hàm Băm sử dụng Phương pháp chia
Dùng số dư: h(k) = k mod m
k là khoá, m là kích thước của bảng.
Như vậy h(k) sẽ nhận: 0,1,2,…,m-1.
Việc chọn m sẽ ảnh hưởng đến h(k).
Nếu chọn m=2p thì giá trị của h(k) sẽ là p bit cuối cùng của k trong biểu diễn nhị phân.
Nếu chọn m=10p thì giá trị của h(k) sẽ là p chữ số cuối cùng trong biểu diễn thập phân
của k.
Trong 2 ví dụ trên giá trị h(k) không phụ thuộc đầy đủ vào khóa k mà chỉ phụ thuộc
vào p bít (p chữ số) cuối cùng trong khóa k. Tốt nhất ta nên chọn m sao cho h(k) phụ thuộc
đầy đủ và khóa k. Thông thường chọn m là số nguyên tố.
VD: Bảng băm có 4000 mục, chọn m = 4093
Hàm Băm sử dụng Phương pháp nhân
h(k) = m*(k*A mod 1)
k là khóa, m là kích thước bảng, A là hằng số: 0 < A < 1
Chọn m và A
Theo Knuth thì chọn A bằng giá trị sau:
A=( 5 -1)/2=0.6180339887…
m thường chọn m = 2p
VD: k=123456; m=10000
H(k)= 10000 (123456* 0.6180339887 mod 1)
H(k)= 10000 (76300.0041089472 mod 1)
H(k)= 10000 (0.0041089472)
H(k)=41
Phép băm phổ quát (unisersal hashing)
Việc chọn hàm băm không tốt có thể dẫn đến xác suất đụng độ cao.
23
Giải pháp:
- Lựa chọn hàm băm h ngẫu nhiên.
- Khởi tạo một tập các hàm băm H phổ quát và từ đó h được chọn ngẫu nhiên.
Cho H là một tập hợp hữu hạn các hàm băm: ánh xạ các khóa k từ tập khóa U vào
miền giá trị {0,1,2,…, m-1}. Tập H là phổ quát nếu với mọi ∀ f ∈ H và 2 khoá phân biệt
k1,k2 ta có xác suất: Pr{f(k1) = f(k2)} <= 1/m
a. Mô tả dữ liệu
Giả sử
· K: tập các khoá (set of keys)
· M: tập các dịa chỉ (set of addresses).
· h(k): hàm băm dùng để ánh xạ một khoá k từ tập các khoá K thành một địa chỉ tương ứng
trong tập M.
b. Các phép toán trên bảng băm
· Khởi tạo (Initialize): Khỏi tạo bảng băm, cấp phát vùng nhớ hay qui định số phần tử (kích
thước) của bảng băm
· Kiểm tra rỗng (Empty): kiểm tra bảng băm có rỗng hay không?
· Lấy kích thước của bảng băm (Size): Cho biết số phần tử hiện có trong bảng băm
· Tìm kiếm (Search): Tìm kiếm một phần tử trong bảng băm theo khoá k chỉ định trước.
· Thêm mới phần tử (Insert): Thêm một phần tử vào bảng băm. Sau khi thêm số phần tử hiện
có của bảng băm tăng thêm một đơn vị.
· Loại bỏ (Remove): Loại bỏ một phần tử ra khỏi bảng băm, và số phần tử sẽ giảm đi một.
· Sao chép (Copy): Tạo một bảng băm mới tử một bảng băm cũ đã có.
· Xử lý các khóa trong bảng băm (Traverse): xử lý toàn bộ khóa trong bảng băm theo thứ tự
địa chỉ từ nhỏ đến lớn.
Các Bảng băm thông dụng:
24
Với mỗi loại bảng băm cần thiết phải xác định tập khóa K, xác định tập địa chỉ M và
xây dựng hàm băm h cho phù hợp.
*) Bảng băm với phương pháp kết nối trực tiếp: mỗi địa chỉ của bảng băm tương ứng
một danh sách liên kết. Các phần tử bị xung đột được kết nối với nhau trên một danh sách
liên kết.
*) Bảng băm với phương pháp kết nối hợp nhất: bảng băm này được cài đặt bằng danh
sách kề, mỗi phần tử có hai trường: trường key chứa khóa của phần tử và trường next chỉ
phần tử kế bị xung đột. Các phần tử bị xung đột được kết nối nhau qua trường kết nối next.
*) Bảng băm với phương pháp dò tuần tự: Khi thêm phần tử vào bảng băm nếu bị đụng
độ thì sẽ dò địa chỉ kế tiếp… cho đến khi gặp địa chỉ trống đầu tiên thì thêm phần tử vào địa
chỉ này.
*) Bảng băm với phương pháp dò bậc hai: ví dụ khi thêm phần tử vào bảng băm này,
nếu băm lần đầu bị xung đột thì sẽ dò đến địa chi mới, ở lần dò thứ i sẽ xét phần tử cách i 2
cho đến khi gặp địa chỉ trống đầu tiên thì thêm phần tử vào địa chỉ này.
*) Bảng băm với phương pháp băm kép: bảng băm này dùng hai hàm băm khác nhau,
băm lần đầu với hàm băm thứ nhất nếu bị xung đột thì xét địa chỉ khác bằng hàm băm thứ
hai.
Ưu điểm của các Bảng băm:
Bảng băm là một cấu trúc dung hòa giữa thời gian truy xuất và dung lượng bộ nhớ:
- Nếu không có sự giới hạn về bộ nhớ thì chúng ta có thể xây dựng bảng băm với mỗi
khóa ứng với một địa chỉ với mong muốn thời gian truy xuất tức thời.
- Nếu dung lượng bộ nhớ có giới hạn thì tổ chức một số khóa có cùng địa chỉ, khi đó
tốc độ truy xuất sẽ giảm.
Bảng băm dược ứng dụng nhiều trong thực tế, rất thích hợp khi tổ chức dữ liệu có kích
thước lớn và được lưu trữ ở bộ nhớ ngoài.
25
30, 50,60,11,21,31,…
27
· Nếu chưa bị xung đột thì thêm phần tử mới vào địa chỉ này.
· Nếu bị xung đột thì phần tử mới được cấp phát là phần tử trống phía cuối mảng. Cập
nhật liên kết next sao cho các phần tử bị xung đột hình thành một danh sách liên kết.
- Tìm kiếm: Khi tìm kiếm một phần tử có khóa key trong bảng băm, hàm băm h(key) sẽ giúp
giới hạn phạm vi tìm kiếm bằng cách xác định địa chỉ i trong khoảng từ 0 đến M-1, và việc
tìm kiếm phần tử khóa có khoá key trong danh sách liên kết sẽ xuất phát từ địa chỉ i.
Để minh họa cho bảng băm với phương pháp kết nối hợp nhất, xét ví dụ sau:
Giả sử, khảo sát bảng băm có cấu trúc như sau:
- Tập khóa K: tập số tự nhiên
- Tập địa chỉ M: gồm 10 địa chỉ (M={0, 1, …, 9}
- Hàm băm f(key) = key % 10.
VD:
Key : 11 12 21 1 13
Hash: 1 2 1 1 3
Add Key Next Add Key Next
0 NullKey -1 0 NullKey -1
1 NullKey -1 1 11 9
… NullKey -1 2 12 -1
M-1 NullKey -1 3 13 -1
… NullKey -1
8 1 -1
9 21 8
29
Hình thể hiện thêm các nut 32, 53, 22, 92, 17, 34, 24, 37, 56 vào bảng băm.
0 NULL 0 NULL 0 NULL 0 NULL 0 56
1 NULL 1 NULL 1 NULL 1 NULL 1 NULL
2 32 2 32 2 32 2 32 2 32
3 53 3 53 3 53 3 53 3 53
4 NULL 4 22 4 22 4 22 4 22
5 NULL 5 92 5 92 5 92 5 92
6 NULL 6 NULL 6 34 6 34 6 34
7 NULL 7 NULL 7 17 7 17 7 17
8 NULL 8 NULL 8 NULL 8 24 8 24
9 NULL 9 NULL 9 NULL 9 37 9 37
Khi thêm phần tử có khóa key vào bảng băm, hàm băm h(key) sẽ xác định địa chỉ i trong
khoảng từ 0 đến M-1.
· Nếu chưa bị xung đột thì thêm phần tử mới vào địa chỉ i này.
· Nếu bị xung đột thì hàm băm lại lần 1 h1 sẽ xét địa chỉ cách i là 1 2, nếu lại bị xung
đột thì hàm băm lại lần 2 h2 sẽ xét địa chỉ cách i 22 ,… , quá trình cứ thế cho đến khi nào
tìm được trống và thêm phần tử vào địa chỉ này.
30
- Khi tìm kiếm một phần tử có khóa key trong bảng băm thì xét phần tử tại địa chỉ i=f(key),
nếu chưa tìm thấy thì xét phần tử cách i 12, 22, …, quá trình cứ thế cho đến khi tìm được
khóa (trường hợp tìm thấy) hoặc rơi vào địa chỉ trống (trường hợp không tìm thấy).
- Hàm băm lại lần thứ i được biểu diễn bằng công thức sau:
fi(key)=( f(key) + i2 ) % M
với f(key) là hàm băm chính của bảng băm.
Nếu đã dò đến cuối bảng thì trở về dò lại từ đầu bảng.
Bảng băm minh họa có cấu trúc như sau:
- Tập khóa K: tập số tự nhiên
- Tập địa chỉ M: gồm 10 địa chỉ (M={0, 1, …, 9}
- Hàm băm f(key) = key % 10.
31
return(N ==0 ?TRUE :FALSE);
}
Phép toán full:
int full()
{
return(N = = M-1 ?TRUE :FALSE);
}
Phép toán search:
Tìm phần tử có khóa k trên bảng băm,nếu không tìm thấy hàm này trả về trị M, nếu tìm thấy
hàm này trả về địa chỉ tìm thấy.
int search(int k)
{
int i, d;
i = hashfuns(k);
d = 1;
while(hashtable[i].key!=k&&hashtable[i].key !=NULLKEY)
{
//Bam lai (theo phuong phap bac hai)
i = (i+d) % M;
d = d+2;
}
hashtable[i].key =k; N = N+1;
return(i);
}
2.4.5. Bảng băm với phương pháp băm kép
Mô tả:
Phương pháp băm kép dùng hai hàm băm bất kì, ví dụ chọn hai hàm băm như sau:
h1(key)= key %M.
h2(key) =(M-2)-key %(M-2).
Bảng băm trong trường hợp này được cài đặt bằng danh sách kề có M phần tử, mỗi
phần tử của bảng băm là một mẫu tin có một trường key để lưu khoá các phần tử.
- Khi khởi động bảng băm, tất cả trường key được gán NULLKEY.
- Khi thêm phần tử có khoá key vào bảng băm, thì i=h1(key) và j=h2(key) sẽ xác định địa
chỉ i và j trong khoảng từ 0 đến M-1:
· Nếu chưa bị xung đột thì thêm phần tử mới tại địa chỉ i này.
· Nếu bị xung đột thì hàm băm lại lần 1 h1 sẽ xét địa chỉ mới i+j, nếu lại bị xung đột thì
hàm băm lại lần 2 h2 sẽ xét địa chỉ i+2j, …, quá trình cứ thế cho đến khi nào tìm được địa
chỉ trống và thêm phần tử vào địa chi này.
- Khi tìm kiếm một phần tử có khoá key trong bảng băm, hàm băm i=h1(key) và j=h2(key)
sẽ xác định địa chỉ i và j trong khoảng từ 0 đến M-1. Xét phần tử tại địa chỉ i, nếu chưa tìm
32
thấy thì xét tiếp phần tử i+j, i+2j, …, quá trình cứ thế cho đến khi nào tìm được khoá
(trường hợp tìm thấy) hoặc bị rơi vào địa chỉ trống (trường hợp không tìm thấy).
Bảng băm dùng hai hàm băm khác nhau, hàm băm lại của phương pháp băm kép được
tính theo hai giá trị: i (kết quả hàm băm thứ nhất) và j (kết qủa hàm băm thứ hai) theo một
công thức bất kì. Nếu đã dò đến cuối bảng thì trở về dò lại từ đầu bảng.
Bảng băm minh họa có cấu trúc như sau:
- Tập khóa K: tập số tự nhiên
- Tập địa chỉ M: gồm 11 địa chỉ (M={0, 1, …, 10}
- Chọn hàm băm f1(key)=key % 11 và f2(key)=9-key %9.
Khai báo
#define NULLKEY –1
#define M 101 /*M la so nut co tren bang bam,du de chua cac nut nhap vao
bang bam,chon M la so nguyen to */
struct node
{
int key;//khoa cua nut tren bang bam
};
struct node hashtable[M]; //khai bao bang bam co M nut
Bài 4:CÂY, CÂY NHỊ PHÂN, CÂY NHỊ PHÂN TÌM KIẾM
1. Cấu trúc cây
1.1. Định nghĩa 1:
Cây là một tập hợp T các phần tử (nút trên cây) trong đó có 1 nút đặc biệt T0 được gọi
là gốc, các nút còn khác được chia thành những tập rời nhau T1, T2 , ... , Tn theo quan hệ
phân cấp trong đó Ti cũng là một cây.
Nút ở cấp i sẽ quản lý một số nút ở cấp i+1. Quan hệ này người ta còn gọi là quan hệ
cha-con.
1.2. Một số khái niệm cơ bản
- Bậc của một nút: là số cây con của nút đó .
- Bậc của một cây: là bậc lớn nhất của các nút trong cây. Cây có bậc n thì gọi là cây n-phân.
- Nút gốc: nút không có nút cha.
- Nút lá: nút có bậc bằng 0 .
- Nút nhánh: nút có bậc khác 0 và không phải là gốc .
- Mức của một nút:
Mức (T0 ) = 1.
Gọi T1, T2, T3, ... , Tn là các cây con của T0
Mức (T1) = Mức (T2) = ... = Mức (Tn) = Mức (T0) + 1.
- Độ dài đường đi từ gốc đến nút x: là số nhánh cần đi qua kể từ gốc đến x.
- Chiều cao h của cây: mức lớn nhất của các nút lá.
33
- Sơ đồ tổ chức cây thư mục
34
2. CÂY NHỊ PHÂN
2.1 Định nghĩa
Cây nhị phân là cây mà mỗi nút có tối đa 2 cây con
Cây nhị phân có thể ứng dụng trong nhiều bài toán thông dụng. Ví dụ dưới đây cho ta
hình ảnh của một biểu thức toán học:
35
2.3. Biểu diễn cây nhị phân T
Cây nhị phân là một cấu trúc bao gồm các phần tử (nút) được kết nối với nhau theo
quan hệ “cha-con” với mỗi cha có tối đa 2 con. Để biểu diễn cây nhị phân ta chọn phương
pháp cấp phát liên kết. Ứng với một nút, ta dùng một biến động lưu trữ các thông tin:
+ Thông tin lưu trữ tại nút.
+ Địa chỉ nút gốc của cây con trái trong bộ nhớ.
+ Địa chỉ nút gốc của cây con phải trong bộ nhớ.
Khai báo như sau:
typedef struct tagTNODE
{
Data Key;//Data là kiểu dữ liệu ứng với thông tin lưu tại nút
struct tagNODE *pLeft, *pRight;
}TNODE;
typedef TNODE *TREE;
2.4. Các thao tác trên cây nhị phân
Thăm các nút trên cây theo thứ tự trước (Node-Left-Right)
void NLR(TREE Root)
{
if (Root != NULL)
{
<Xử lý Root>; //Xử lý tương ứng theo nhu cầu
NLR(Root->pLeft);
NLR(Root->pRight);
}
}
Thăm các nút trên cây theo thứ tự giữa (Left- Node-Right)
void LNR(TREE Root)
{
if (Root != NULL)
{
LNR(Root->Left);
<Xử lý Root>; //Xử lý tương ứng theo nhu cầu LNR(Root->Right);
}
36
}
Thăm các nút trên cây theo thứ tự sau (Left-Right-Node)
void LRN(TREE Root)
{
if (Root != NULL)
{
LRN(Root->Left);
LRN(Root->Right);
<Xử lý Root>; //Xử lý tương ứng theo nhu cầu
}
}
Ứng dụng phương pháp này trong việc tính tổng kích thước của thư mục.
Ứng dụng tính toán giá trị của biểu thức.
37
2.5. Biểu diễn cây tổng quát bằng cây nhị phân
Nhược điểm của các cấu trúc cây tổng quát là bậc của các nút trên cây có thể rất khác
nhau ⇒ việc biểu diễn gặp nhiều khó khăn và lãng phí. Hơn nữa, việc xây dựng các thao tác
trên cây tổng quát phức tạp hơn trên cây nhị phân nhiều.
Vì vậy, nếu không quá cần thiết phải sử dụng cây tổng quát, người ta sẽ biến đổi cây
tổng quát thành cây nhị phân.
Ta có thể biến đổi một cây bất kỳ thành một cây nhị phân theo qui tắc sau:
- Giữ nút con trái nhất làm con trái.
- Các nút con còn lại biển đổi thành nút con phải.
38
Cây nhị phân tìm kiếm (CNPTK) là cây nhị phân trong đó tại mỗi nút, khóa của nút
đang xét lớn hơn khóa của tất cả các nút thuộc cây con trái và nhỏ hơn khóa của tất cả các nút
thuộc cây con phải.
Dưới đây là một ví dụ về cây nhị phân tìm kiếm:
Nhờ ràng buộc về khóa trên CNPTK, việc tìm kiếm trở nên có định hướng. Hơn nữa,
do cấu trúc cây việc tìm kiếm trở nên nhanh đáng kể. Chi phí tìm kiếm trung bình chỉ
khoảng log2N.
Trong thực tế, khi xét đến cây nhị phân chủ yếu người ta xét CNPTK.
3.2. Các thao tác trên cây
3.2.1. Thăm các nút trên cây
3.2.2. Tìm một phần tử x trong cây
TToán:
Dễ dàng thấy rằng số lần so sánh tối đa phải thực hiện để tìm phần tử X là bằng h, với
h là chiều cao của cây.
39
Việc thêm một phần tử X vào cây phải bảo đảm điều kiện ràng buộc của CNPTK. Ta
có thể thêm vào nhiều vị trí khác nhau trên cây, nhưng nếu thêm vào một nút lá thì sẽ dễ nhất
do ta có thể thực hiện quá trình tương tự thao tác tìm kiếm. Khi chấm dứt quá trình tìm kiếm
ta sẽ tìm được vị trí cần thêm.
Hàm insert trả về giá trị –1, 0, 1 khi không đủ bộ nhớ, gặp nút cũ hay thành công:
int insertNode(TREE &T, Data X)
{
if(T)
{
if(T->Key == X)return 0; //đã có
if(T->Key > X)
return insertNode(T->pLeft, X);
else
return insertNode(T->pRight, X);
}
T = new TNode;
if(T == NULL) return -1; //thiếu bộ nhớ
T->Key = X;
T->pLeft =T->pRight = NULL;
return 1; //thêm vào thành công
}
Trường hợp thứ nhất: chỉ đơn giản hủy X vì nó không móc nối đến phần tử nào khác.
40
Trường hợp thứ hai: trước khi hủy X ta móc nối cha của X với con duy nhất của nó.
Trường hợp cuối cùng: ta không thể hủy trực tiếp do X có đủ 2 con ⇒ Ta sẽ hủy gián
tiếp. Thay vì hủy X, ta sẽ tìm một phần tử thế mạng Y. Phần tử này có tối đa một con. Thông
tin lưu tại Y sẽ được chuyển lên lưu tại X. Sau đó, nút bị hủy thật sự sẽ là Y giống như 2
trường hợp đầu.
Vấn đề là phải chọn Y sao cho khi lưu Y vào vị trí của X, cây vẫn là CNPTK.
Có 2 phần tử thỏa mãn yêu cầu:
Phần tử nhỏ nhất (trái nhất) trên cây con phải.
Phần tử lớn nhất (phải nhất) trên cây con trái.
Việc chọn lựa phần tử nào là phần tử thế mạng hoàn toàn phụ thuộc vào ý thích của
người lập trình. Ở đây, cháng tôi sẽ chọn phần tử (phải nhất trên cây con trái làm phân tử thế
mạng.
41
VD:
Cần hủy phần tử 18.
Đối với cây cân bằng hoàn toàn, trong trường hợp xấu nhất ta chỉ phải tìm qua log2N
phần tử (N là số nút trên cây).
Sau đây là ví dụ một cây cân bằng hoàn toàn (CCBHT):
42
CCBHT có N nút có chiều cao h ≈ log2N. Đây chính là lý do cho phép bảo đảm khả
năng tìm kiếm nhanh trên CTDL này. Do CCBHT là một cấu trúc kém ổn định nên trong
thực tế không thể sử dụng. Nhưng ưu điểm của nó lại rất quan trọng. Vì vậy, cần đưa ra một
CTDL khác có đặc tính giống CCBHT nhưng ổn định hơn.
Dễ dàng thấy CCBHT là cây cân bằng. Điều ngược lại có thể không đúng không đúng.
Một vấn đề quan trọng, như đã đề cập đến ở phần trước, là ta phải khẳng định cây AVL
có N nút phải có chiều cao khoảng log2(n).
Để đánh giá chính xác về chiều cao của cây AVL, ta xét bài toán: cây AVL có chiều
cao h sẽ phải có tối thiểu bao nhiêu nút ?
Gọi N(h) là số nút tối thiểu của cây AVL có chiều cao h.
43
Ta có N(0) = 0, N(1) = 1 và N(2) = 2.
Cây AVL có chiều cao h sẽ có 1 cây con AVL chiều cao h-1 và 1 cây con AVL chiều
cao h-2. Như vậy:
Chỉ số cân bằng của một nút: Chỉ số cân bằng của một nút là hiệu của chiều cao cây con
phải và cây con trái của nó.
Đối với một cây cân bằng, chỉ số cân bằng (CSCB) của mỗi nút chỉ có thể nhận một
trong ba giá trị sau đây:
CSCB(p) = 0 <=> Độ cao cây trái (p) = Độ cao cây phải (p)
CSCB(p) = 1 <=> Độ cao cây trái (p) < Độ cao cây phải (p)
CSCB(p) =-1 <=> Độ cao cây trái (p) > Độ cao cây phải (p)
44
Xét nút P, ta dùng các ký hiệu sau:
P->balFactor = CSCB(P);
Độ cao cây trái P ký hiệu là hleft
Độ cao cây phải P ký hiệu là hright
Để khảo sát cây cân bằng, ta cần lưu thêm thông tin về chỉ số cân bằng tại mỗi nút. Lúc đó,
cây cân bằng có thể được khai báo như sau:
Để tiện cho việc trình bày, ta định nghĩa một số hăng số sau:
#define LH -1 //Cây con trái cao hơn
#define EH -0 //Hai cây con bằng nhau
#define RH 1 //Cây con phải cao hơn
Cây AVL với chiều cao được khống chế sẽ cho phép thực thi các thao tác tìm, thêm,
hủy với chi phí O (log2(n)) và bảo đảm không suy biến thành O(n).
Ta nhận thấy trường hợp thêm hay hủy một phần tử trên cây có thể làm cây tăng hay
giảm chiều cao, khi đó phải cân bằng lại cây.
Việc cân bằng lại một cây sẽ phải thực hiện sao cho chỉ ảnh hưởng tối thiểu đến cây
nhằm giảm thiểu chi phí cân bằng. Như đã nói ở trên, cây cân bằng cho phép việc cân bằng
lại chỉ xảy ra trong giới hạn cục bộ nên chúng ta có thể thực hiện được mục tiêu vừa nêu.
45
Như vậy, ngoài các thao tác bình thường như trên CNPTK, các thao tác đặc trưng của
cây AVL gồm:
Thêm một phần tử vào cây AVL.
Hủy một phần tử trên cây AVL.
Cân bằng lại một cây vừa bị mất cân bằng.
46
Trường hợp 2: cây T lệch về bên phải
Ta có các khả năng sau:
Ta có thể thấy rằng các trường hợp lệch về bên phải hoàn toàn đối xứng với các trường
hợp lệch về bên trái. Vì vậy ta chỉ cần khảo sát trường hợp lệch về bên trái.
Trong 3 trường hợp lệch về bên trái, trường hợp T1 lệch phải là phức tạp nhất. Các
trường hợp còn lại giải quyết rất đơn giản.
Sau đây, ta sẽ khảo sát và giải quyết từng trường hợp nêu trên
T/h 1.1: cây T1 lệch về bên trái. Ta thực hiện phép quay đơn Left-Left
T/h 1.2: cây T1 không lệch. Ta thực hiện phép quay đơn Left-Left
47
Ttoán quay đơn Left-Left:
B1: T là gốc; T1 = T->pLeft;
T->pLeft = T1->pRight;
T1->pRight = T;
B2:// đặt lại chỉ số cân bằng
Nếu T1->balFactor = LH thì:
T->balFactor = EH;
T1->balFactor = EH;
break;
Nếu T1->balFactor = EH thì:
T->balFactor = LH;
T1->balFactor = RH;
break;
B3:// T trỏ đến gốc mới
T = T1;
T/h 1.3: cây T1 lệch về bên phải. Ta thực hiện phép quay kép Left-Right
Do T1 lệch về bên phải ta không thể áp dụng phép quay đơn đã áp dụng trong 2 trường
hợp trên vì khi đó cây T sẽ từ trạng thái mất cân bằng do lệch trái thành mất cân bằng do
lệch phải.
Hình vẽ dưới đây minh họa phép quay kép áp dụng cho trường hợp này:
48
T2->pRight = T;T1->pRight = T2->pLeft;T2->pLeft = T1;
B2: //đặt lại chỉ số cân bằng
Nếu T2->balFactor = LH thì:
T->balFactor = RH; T1->balFactor = EH; break;
Nếu T2->balFactor = EH thì:
T->balFactor = EH; T1->balFactor = EH; break;
Nếu T2->balFactor = RH thì:
T->balFactor = EH; T1->balFactor = LH; break;
B3:
T2->balFactor = EH;
T = T2;
Lưu ý rằng, trước khi cân bằng cây T có chiều cao h+2 trong cả 3 trường hợp 1.1, 1.2
và 1.3.
Sau khi cân bằng, trong 2 trường hợp 1.1 và 1.3 cây có chiều cao h+1; còn ở trường
hợp 1.2 cây vẫn có chiều cao h+2. Và trường hợp này cũng là trường hợp duy nhất sau khi
cân bằng nút T cũ có chỉ số cân bằng khác 0.
Thao tác cân bằng lại trong tất cả các trường hợp đều có độ phức tạp O(1).
Với những xem xét trên, xét tương tự cho trường hợp cây T lệch về bên phải, ta có thể
xây dựng 2 hàm quay đơn và 2 hàm quay kép sau:
TToán: Giả sử cần thêm vào một nút mang thông tin X.
1. Tìm kiếm vị trí thích hợp để thêm nút X (đưa ra thông báo nếu đã có nút X rồi)
2. Thêm nút X vào cây
3. Cân bằng lại cây.
49
Tuy nhiên trong một số trường hợp cây tìm kiếm nhị phân có một số hạn chế. Nó hoạt
động tốt nếu dữ liệu được chèn vào cây theo thứ tự ngẫu nhiên. Tuy nhiên, nếu dữ liệu được
chèn vào theo thứ tự đã đuợc sắp xếp sẽ không hiệu quả. Khi các trị số cần chèn đã đuợc sắp
xếp thì cây nhị phân trở nên không cân bằng. Khi cây không cân bằng, nó mất đi khả năng
tìm kiếm nhanh (hoặc chèn hoặc xóa) một phần tử đã cho.
Chúng ta khảo sát một cách giải quyết vấn đề của cây không cân bằng: đó là cây đỏ
đen, là cây tìm kiếm nhị phân có thêm một vài đặc điểm .
Có nhiều cách tiếp cận khác để bảo đảm cho cây cân bằng: chẳng hạn cây 2-3-4. Tuy
vậy, trong phần lớn trường hợp, cây đỏ đen là cây cân bằng hiệu quả nhất, ít ra thì khi dữ
liệu được lưu trữ trong bộ nhớ chứ không phải trong những tập tin.
Trước khi khảo sát cây đỏ đen, hãy xem lại cây không cân bằng được tạo ra như thế
nào.
50
Hình 2. Một ví dụ về cây đỏ đen
Số lượng node đen trên một đường dẫn từ gốc đến lá được gọi là chiều cao đen (black
height). Ta có thể phát biểu quy tắc (4) theo một cách khác là mọi đường dẫn từ gốc đến lá
phải có cùng chiều cao đen.
Khai báo cấu trúc:
typedef int Data; /* Kiểu dữ liệu khoá */
typedef enum { BLACK, RED } nodeColor;
typedef struct NodeTag {
nodeColor color; /* Màu node (BLACK, RED) */
Data info; /* Khoá sử dụng tìm kiếm */
struct NodeTag *left; /* Con trái */
struct NodeTag *right; /* Con phải */
struct NodeTag *parent; /* Cha */
} NodeType;
typedef NodeType *iterator;
Bổ đề:
Một cây đỏ đen n-node có chiều cao h <= 2 log2(n+1)
3. PHÉP QUAY
Thực ra quay không có nghĩa là các node bị quay mà để chỉ sự thay đổi quan hệ giữa
chúng. Một node được chọn làm "đỉnh" của phép quay. Nếu chúng ta đang thực hiện một
phép quay qua phải, node "đỉnh" này sẽ di chuyển xuống dưới và về bên phải, vào vị trí của
node con bên phải của nó. Node con bên trái sẽ đi lên để chiếm lấy vị trí của nó.
Phải đảm bảo trong phép quay phải, node ở đỉnh phải có node con trái. Nếu không
chẳng có gì để quay vào điểm đỉnh. Tương tự, nếu làm phép quay trái, node ở đỉnh phải có
node con phải.
4. THÊM NODE MỚI
51
Chúng ta sẽ xem xét việc mô tả qui trình chèn. Gọi X, P, và G để chỉ định nhãn những
node liên quan. X là node vi phạm quy tắc (X có thể là một node mới được chèn, hoặc node
con khi node cha và node con xung đột đỏ-đỏ, nghĩa là có cùng màu đỏ).
− X là một node cho trước.
− P là node cha của X.
− G là node ông bà của X (node cha của P).
Trong quá trình thêm vào node mới có thể vi phạm các quy tắc của cây đỏ đen, chúng
ta sẽ thực hiện các thao tác sau đây:
− Các phép lật màu trên đường đi xuống.
− Các phép quay khi node đã được chèn.
− Các phép quay trên đường đi xuống.
Hình 4a. trước khi lật màu, Hình 4b sau khi lật màu.
Chúng ta nhận thấy sau khi lật màu chiếu cao đen của cây không đổi. Như vậy phép lật
màu không vi phạm quy tắc (4).
52
Mặc dù quy tắc (4) không bị vi phạm qua phép lật, nhưng quy tắc 3 (một node con và
node cha không thể đồng màu đỏ) lại có khả năng bị vi phạm. Nếu node cha của P là đen,
không có vấn đề vi phạm khi P được đổi từ đen sang đỏ, nhưng nếu node cha của P là đỏ, thì
sau khi đổi màu, ta sẽ có hai node đỏ trên một hàng.
Điều này cần phải được chuẩn bị truớc khi đi xuống theo cây để chèn node mới. Chúng
ta có thể giải quyết trường hợp này bằng một phép quay.
Đối với node gốc thì phép lật màu node gốc và hai node con của nó vẫn làm cho node
gốc cũng như hai node con có màu đen. Điều này tránh sự vi phạm quy tắc 2 và quy tắc 3
(xung đột đỏ-đỏ). Trong trường hợp này, chiều cao đen trên mỗi đường đi từ node gốc tăng
lên 1, do đó quy tắc 4 cũng không bị vi phạm.
4.2. Các phép quay khi chèn node
Thao tác chèn node mới có thể làm cho quy tắc đỏ-đen bị vi phạm. Do vậy sau khi
chèn, cần phải kiểm tra xem có phạm quy tắc không và thực hiện những thao tác hợp lý.
Như đã xét ở trên, node mới được chèn mà ta gọi là node X, luôn luôn đỏ. Node X có
thể nằm ở những vị trí khác nhau đối với P và G, như trong hình 5.
X là một node cháu ngoại nếu nó nằm cùng bên node cha P và P cùng bên node cha G.
Điều này có nghĩa là, X là node cháu ngoại nếu hoặc nó là node con trái của P và P là node
con trái của G, hoặc nó là node con phải của P và node P là node con phải của G. Ngược lại,
X là một node cháu nội.
Nếu X là node cháu ngoại, nó có thể hoặc bên trái hoặc bên phải của P, tùy vào việc
node P ở bên trái hay bên phải node G. Có hai khả năng tương tự nếu X là một node cháu
nội. Bốn trường hợp này được trình bày trong hình 5.
53
Thao tác phục hồi quy tắc đỏ-đen được xác định bởi các màu và cấu hình của node X
và những bà con của nó. Có 3 khả năng xảy ra được xem xét như sau:(hình 6)
55
hình 8.c
Hình 8. Khả năng 3: P đỏ và X là node cháu nội
Chỉnh lại sự sắp xếp này cũng khá rắc rối hơn. Nếu ta cố quay phải node ông bà G (25)
ở đỉnh, như ta đã làm trong khả năng 2, node cháu trong X (18) đi ngang hơn là đi lên, như
thế cây sẽ không còn cân bằng như trước. (Thử làm điều này, rồi quay trở lại, với node 12 ở
đỉnh, để phục hồi cây nhu cũ). Phải cần một giải pháp khác.
Thủ thuật cần dùng khi X là node cháu nội là tiến hành hai phép quay hơn là một phép.
Phép quay đầu biến X từ một node cháu nội thành node cháu ngoại, như trong hình 8b. Bây
giờ, trường hợp là tương tự như khả năng 1, và ta có thể áp dụng cùng một phép quay, với
node ông bà ở đỉnh, như đã làm trước đây. Kết quả như trong hình 8c.
Chúng ta cũng cần tô màu lại các nút. Ta làm điều này trước khi làm bất cứ phép quay
nào (thứ tự không quan trọng, nhưng nếu ta đợi đến khi sau khi quay mới tô màu lại node thì
khó mà biết phải gọi chúng như thế nào). Các bước là:
- Đổi màu node ông bà của node X ( node 25).
- Đổi màu node X ( node X đây là node 18).
- Quay trái với node P - node cha của X - ở đỉnh ( node cha đây là 12).
56
- Quay lần nữa với node ông bà của X (25) ở đỉnh, về hướng nâng X lên (quay phải).
5. LOẠI BỎ NODE
Trong cây BST chúng ta thấy rằng phép loại bỏ phức tạp hơn so với phép thêm vào.
Trong cây đỏ đen phép loại bỏ càng phức tạp hơn rất nhiều so với phép thêm vào vì yêu cầu
đảm bảo quy tắc đỏ đen. Chúng ta có thể tham khảo trong phần cài đặt.
• Nếu xóa một nút đỏ thì chiều cao đen của cây không đổi
• Nếu xóa một nút đen thì chúng ta phải cân bằng lại cây.
57
Các số 2, 3 và 4 trong cụm từ cây 2-3-4 có ý nghĩa là khả năng có bao nhiêu liên kết
đến các node con có thể có được trong một node cho trước. Đối với các node không phải là
lá, có thể có 3 cách sắp xếp sau:
Một node với một mục dữ liệu thì luôn luôn có 2 con.
Một node với hai mục dữ liệu thì luôn luôn có 3 con.
Một node với ba mục dữ liệu thì luôn luôn có 4 con.
Như vậy, một node không phải là lá phải luôn luôn có số node con nhiều hơn 1 so với
số mục dữ liệu của nó. Nói cách khác, đối với mọi node với số con là k và số mục dữ liệu là
d, thì : k = d + 1
Hình 2. các trường hợp của cây 2-3-4
Với mọi node lá thì không có node con nhưng có thể chứa 1, 2 hoặc 3 mục dữ liệu,
không có node rỗng.
Một cây 2-3-4 có thể có đến 4 cây con nên được gọi là cây nhiều nhánh bậc 4.
Trong cây 2-3-4 mỗi node có ít nhất là 2 liên kết, trừ node lá (node không có liên kết
nào).
Hình 2 trình bày các trường hợp của cây 2-3-4. Một node với 2 liên kết gọi là một 2-
node, một node với 3 liên kết gọi là một 3-node, và một node với 4 liên kết gọi là một 4-
node, nhưng ở đây không có node là 1-node.
58
Trong cây tìm kiếm nhị phân, tất cả node của cây con bên trái có khoá nhỏ hơn khóa
của node đang xét và tất cả node của cây con bên phải có khoá lớn hơn hoặc bằng khóa của
node đang xét. Trong cây 2-3-4 thì nguyên tắc cũng giống như trên, nhưng có thêm một số
điểm sau:
Tất cả các node con của cây con có gốc tại node con thứ 0 thì có các giá trị khoá
nhỏ hơn khoá 0.
Tất cả các node con của cây con có gốc tại node con thứ 1 thì có các giá trị khoá
lớn hơn khoá 0 và nhỏ hơn khoá 1.
Tất cả các node con của cây con có gốc tại node con thứ 2 thì có các giá trị khoá
lớn hơn khoá 1 và nhỏ hơn khoá 2.
Tất cả các node con của cây con có gốc tại node con thứ 3 thì có các giá trị khoá
lớn hơn khoá 2.
Trong cây 2-3-4, các nút lá đều nằm trên cùng một mức. Các node ở mức trên thường không
đầy đủ, nghĩa là chúng có thể chứa chỉ 1 hoặc 2 mục dữ liệu thay vì 3 mục.
Lưu ý rằng cây 2-3-4 là cây cân bằng. Nó vẫn giữ được sự cân bằng khi thêm vào các
phần tử có thứ tự (tăng dần hoặc giảm dần).
3. Tìm kiếm
Thao tác tìm kiếm trong cây 2-3-4 tương tự như thủ tục tìm kiếm trong cây nhị phân.
việc tìm kiếm bắt đầu từ node gốc và chọn liên kết dẫn đến cây con với phạm vi giá trị phù
hợp.
Ví dụ, để tìm kiếm mục dữ liệu với khoá là 64 trên cây ở hình 1, bạn bắt đầu từ gốc.
Tại node gốc không tìm thấy mục khoá này. Bởi vì 64 lớn 50, chúng ta đi đến node con 1,
(60/70/80)(lưu ý node con 1 nằm bên phải, bởi vì việc đánh số của các node con và các liên
kết bắt đầu tại 0 từ bên trái). Tại vị trí này vẫn không tìm thấy mục dữ liệu, vì thế phải đi
đến node con tiếp theo. Tại đây bởi vì 64 lớn hơn 60 nhưng nhỏ hơn 70 nên đi tiếp đến node
con 1. Tại thời điểm chúng ta tìm được mục dữ liệu đã cho với liên kết là 62/64/66.
4. Thêm vào
Các mục dữ liệu mới luôn luôn được chèn vào tại các node lá . Nếu mục dữ liệu được
thêm vào node mà có node con, thì số lượng của các node con cần thiết phải được biến đổi
59
để duy trì cấu trúc cho cây, đây là lý do tại sao phải có số node con nhiều hơn 1 so với các
mục dữ liệu trong một nút.
Việc thêm vào cây 2-3-4 trong bất cứ trường hợp nào thì quá trình cũng bắt đầu bằng
cách tìm kiếm node lá phù hợp.
Nếu không có node đầy nào (node có đủ 3 mục dữ liệu) được bắt gặp trong quá trình
tìm kiếm, việc chèn vào khá là dễ dàng. Khi node lá phù hợp được tìm thấy, mục dữ liệu mới
đơn giản là thêm vào nó. Hình 3 trình bày một mục dữ liệu với khoá 18 được thêm vào cây
2-3-4.
Việc chèn vào có thể dẫn đến phải thay đổi vị trí của một hoặc hai mục dữ liệu trong
node vì thế các khoá sẽ nằm với trật tự đúng sau khi mục dữ liệu mới được thêm vào. Trong
ví dụ này số 23 phải được đẩy sang phải để nhường chỗ cho 18.
60
Giả sử ta đặt tên các mục dữ liệu trên node bị phân chia là A, B và C. Sau đây là tiến
trình tách (chúng ta giả sử rằng node bị tách không phải là node gốc; chúng ta sẽ kiểm tra
việc tách node gốc sau này):
Một node mới (rỗng) được tạo. Nó là anh em với node sẽ được tách và được đưa
vào bên phải của nó.
Mục dữ liệu C được đưa vào node mới.
Mục dữ liệu B được đưa vào node cha của node được tách.
Mục dữ liệu A không thay đổi.
Hai node con bên phải nhất bị hủy kết nối từ node được tách và kết nối đến node
mới.
( Một cách khác để mô tả sự tách node là một 4-node được tách thành hai 2-nút)
61
Mục dữ liệu B được dịch đưa sang node gốc mới.
Mục dữ liệu A vẫn không đổi.
Hai node con bên phải nhất của node được phân chia bị hủy kết nối khỏi nó và
kết nối đến node mới bên phải.
Hình 5 chỉ ra việc tách node gốc. Tiến trình này tạo ra một node gốc mới ở mức cao
hơn mức của node gốc cũ. Kết quả là chiều cao tổng thể của cây được tăng lên 1.
Đi theo node được tách này, việc tìm kiếm điểm chèn tiếp tục đi xuống phía dưới của
cây. Trong hình 5 mục dữ liệu với khoá 41 được thêm vào lá phù hợp.
Thêm vào 75
Thêm vào 10
Biến đổi bất kỳ 2-node ở cây 2-3-4 sang node đen ở cây đỏ-đen.
Biến đổi bất kỳ 3-node sang node con C (với hai con của chính nó) và node cha
P (với các node con C và node con khác). Không có vấn đề gì ở đây khi một mục
trở thành node con và mục khác thành node cha. C được tô màu đỏ và P được tô
màu đen.
63
Biến đổi bất kỳ 4-node sang node cha P và cả hai node con C1, C2 màu đỏ.
Hình 4.7 trình bày các chuyển đổi này. Các node con trong các cây con được tô màu đỏ; tất
cả các node khác được tô màu đen.
Hình 4.8 trình bày cây 2-3-4 và cây đỏ-đen tương ứng với nó bằng cách áp dụng các
chuyển đổi này. Các đường chấm xung quanh các cây con được tạo ra từ 3-node và 4-nút.
Các luật của cây đỏ-đen tự động thoả mãn với sự chuyển đổi này. Kiểm tra rằng: Hai node
64
đỏ không bao giờ được kết nối, và số lượng các node đen là như nhau ở mọi đường dẫn từ
gốc đến lá (hoặc node con null).
65