Bài 24.518 phút đọc
Tìm trong BST
Sau bài này bạn sẽ làm được
- Cài tìm bằng vòng lặp thay vì đệ quy
- Tìm nút nhỏ nhất và lớn nhất
- Tìm nút kế tiếp theo thứ tự trung thứ tự
- Đếm số phép so sánh thực tế
Tìm trong BST là đi từ gốc xuống theo đúng một đường, mỗi bước loại bỏ một nửa cây còn lại. Cùng ý tưởng đó cho tìm nhỏ nhất, lớn nhất, và nút kế tiếp theo thứ tự, tất cả trong O(chiều cao).
#Tìm bằng vòng lặp
bst.c
/* Tìm nút có data bằng v. Trả về NULL nếu không có.
Bản LẶP: O(1) bộ nhớ, không sợ tràn ngăn xếp. */
TNode *bst_tim(TNode *r, int v)
{
while (r != NULL) {
if (v == r->data) return r;
else if (v < r->data) r = r->left;
else r = r->right;
}
return NULL;
}
/* Bản đệ quy, để so sánh. Cùng độ phức tạp nhưng tốn ngăn xếp. */
TNode *bst_tim_de_quy(TNode *r, int v)
{
if (r == NULL || v == r->data) return r;
return v < r->data ? bst_tim_de_quy(r->left, v)
: bst_tim_de_quy(r->right, v);
}| Tìm giá trị | Đường đi | Số phép so sánh |
|---|---|---|
| 50 | 50 | 1 |
| 30 | 50 -> 30 | 2 |
| 60 | 50 -> 70 -> 60 | 3 |
| 20 | 50 -> 30 -> 20 | 3 |
| 45 | 50 -> 30 -> 40 -> NULL | 4, không thấy |
| 100 | 50 -> 70 -> 80 -> NULL | 4, không thấy |
#Nhỏ nhất và lớn nhất
bst.c (tiếp)
/* Nút nhỏ nhất: đi hết sang trái. Trả về NULL nếu cây rỗng. */
TNode *bst_nho_nhat(TNode *r)
{
if (r == NULL) return NULL;
while (r->left != NULL) r = r->left;
return r;
}
/* Nút lớn nhất: đi hết sang phải. */
TNode *bst_lon_nhat(TNode *r)
{
if (r == NULL) return NULL;
while (r->right != NULL) r = r->right;
return r;
}#Nút kế tiếp và nút liền trước
Nút kế tiếp của x là nút nhỏ nhất trong số các nút lớn hơn x, tức nút đứng ngay sau x trong duyệt trung thứ tự. Không có con trỏ cha thì phải tìm từ gốc.
bst.c (tiếp)
/* Nút kế tiếp của giá trị v theo thứ tự tăng dần.
Không cần v phải có trong cây. Trả về NULL nếu không có nút nào lớn hơn v. */
TNode *bst_ke_tiep(TNode *r, int v)
{
TNode *ung_vien = NULL;
while (r != NULL) {
if (r->data > v) {
ung_vien = r; /* ứng viên tốt nhất tới lúc này */
r = r->left; /* thử tìm ứng viên nhỏ hơn nữa bên trái */
} else {
r = r->right; /* r->data <= v, mọi thứ bên trái đều loại */
}
}
return ung_vien;
}
/* Nút liền trước, đối xứng hoàn toàn. */
TNode *bst_lien_truoc(TNode *r, int v)
{
TNode *ung_vien = NULL;
while (r != NULL) {
if (r->data < v) {
ung_vien = r;
r = r->right;
} else {
r = r->left;
}
}
return ung_vien;
}terminal
./ke-tiep
cay: 20 30 40 50 60 70 80 ke tiep cua 20 -> 30 ke tiep cua 45 -> 50 ke tiep cua 50 -> 60 ke tiep cua 80 -> khong co lien truoc cua 20 -> khong co lien truoc cua 45 -> 40 lien truoc cua 50 -> 40 lien truoc cua 100 -> 80
Bản có con trỏ cha
Khi nút biết cha của nó
typedef struct TNodeP {
int data;
struct TNodeP *left, *right, *cha;
} TNodeP;
/* Nút kế tiếp của một nút CỤ THỂ, không phải của một giá trị. */
TNodeP *ke_tiep_cua_nut(TNodeP *x)
{
if (x == NULL) return NULL;
/* Trường hợp 1: có cây con phải thì kế tiếp là nút nhỏ nhất trong đó */
if (x->right != NULL) {
x = x->right;
while (x->left != NULL) x = x->left;
return x;
}
/* Trường hợp 2: không có cây con phải thì đi lên cho tới khi
gặp một tổ tiên mà x nằm trong cây con TRÁI của nó */
TNodeP *p = x->cha;
while (p != NULL && x == p->right) {
x = p;
p = p->cha;
}
return p;
}#Truy vấn theo khoảng
Đây là thứ bảng băm ở Chương 25 hoàn toàn không làm được, và là lý do chính để chọn cây thay vì bảng băm.
bst.c (tiếp)
/* Gọi ham cho mọi giá trị trong khoảng [thap, cao], theo thứ tự tăng dần.
Cắt tỉa: không đi vào cây con chắc chắn nằm ngoài khoảng. */
void bst_duyet_khoang(const TNode *r, int thap, int cao,
void (*ham)(int, void *), void *ctx)
{
if (r == NULL) return;
/* Chỉ đi trái nếu còn giá trị nào trong đó có thể lớn hơn hoặc bằng thap */
if (r->data > thap)
bst_duyet_khoang(r->left, thap, cao, ham, ctx);
if (r->data >= thap && r->data <= cao)
ham(r->data, ctx);
/* Chỉ đi phải nếu còn giá trị nào trong đó có thể nhỏ hơn hoặc bằng cao */
if (r->data < cao)
bst_duyet_khoang(r->right, thap, cao, ham, ctx);
}
/* Đếm số nút trong khoảng, cùng cách cắt tỉa. */
size_t bst_dem_khoang(const TNode *r, int thap, int cao)
{
if (r == NULL) return 0;
size_t d = 0;
if (r->data > thap) d += bst_dem_khoang(r->left, thap, cao);
if (r->data >= thap && r->data <= cao) ++d;
if (r->data < cao) d += bst_dem_khoang(r->right, thap, cao);
return d;
}#Đếm số phép so sánh thực tế
dem-so-sanh.c
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "bst.h"
static long tong_so_sanh;
static TNode *tim_dem(TNode *r, int v)
{
while (r != NULL) {
++tong_so_sanh;
if (v == r->data) return r;
else if (v < r->data) r = r->left;
else r = r->right;
}
return NULL;
}
int main(void)
{
srand(2024);
printf("%10s %10s %12s %12s\n",
"n", "chieu cao", "so sanh TB", "log2(n)");
for (long n = 1000; n <= 1000000; n *= 10) {
TNode *goc = NULL;
int *khoa = malloc((size_t)n * sizeof *khoa);
if (khoa == NULL) return 1;
for (long i = 0; i < n; ++i) {
khoa[i] = rand();
if (bst_chen2(&goc, khoa[i]) < 0) { tree_huy(goc); return 1; }
}
tong_so_sanh = 0;
long lan = 100000;
for (long i = 0; i < lan; ++i)
tim_dem(goc, khoa[rand() % n]);
printf("%10ld %10d %12.1f %12.1f\n",
n, tree_chieu_cao(goc),
(double)tong_so_sanh / lan, log2((double)n));
free(khoa);
tree_huy(goc);
}
return 0;
}terminal
gcc -std=c17 -O2 bst.c tree.c dem-so-sanh.c -o t -lm && ./t
n chieu cao so sanh TB log2(n)
1000 21 12.4 10.0
10000 30 16.1 13.3
100000 38 19.9 16.6
1000000 47 23.8 19.9Tự làm thử
- Cài
bst_timbản lặp và bản đệ quy, xác nhận chúng cho cùng kết quả trên một nghìn cây ngẫu nhiên. - Cài
bst_nho_nhatvàbst_lon_nhat, kiểm với cây rỗng, cây một nút, và cây chỉ có nhánh phải. - Cài
bst_ke_tieptheo mẫu ứng viên tốt nhất, rồi dùng nó duyệt cả cây theo thứ tự tăng dần mà không đệ quy. - Cài
bst_duyet_khoangcó cắt tỉa và bản không cắt tỉa, đo thời gian trên cây một triệu nút. - Chạy
dem-so-sanh.ctrên máy bạn, rồi làm lại với khóa chèn theo thứ tự tăng dần và so hai bảng.
Trình chấm điểm tự động sẽ được bổ sung ở giai đoạn sau. Hiện tại bạn tự chạy thử trên máy.
Tóm tắt
- Tìm trong BST là đi một đường từ gốc xuống, mỗi bước loại bỏ một nhánh. Chi phí là O(chiều cao).
- Hàm tìm nên viết bản lặp, vì bản đệ quy là đệ quy đuôi thuần túy và chuẩn C không hứa tối ưu nó.
- Nhỏ nhất là đi hết sang trái, lớn nhất là đi hết sang phải. Nút nhỏ nhất không nhất thiết là lá.
- Nút kế tiếp tìm bằng mẫu ứng viên tốt nhất, cùng mẫu với
lower_boundở Bài 26.2. - Truy vấn theo khoảng có cắt tỉa tốn O(chiều cao cộng số kết quả). Đây là thứ bảng băm không làm được.