Professional Documents
Culture Documents
Các thuật toán hiệu quả dùng những cấu trúc dữ liệu được tổ chức Phân tích thuật toán
tốt.
Người ta so sánh các thuật toán dựa trên các phép ước lượng chi
Trong chương trình, chúng ta sẽ nghiên cứu các thuật toán sau: phí (thời gian chạy của thuật toán, lượng bộ nhớ mà thuật toán
- sắp xếp (sorting) phải dùng) cho một thuật toán khi áp dụng thuật toán vào một bài
- tìm kiếm (searching) toán cụ thể.
- ... Luôn phải tinh chỉnh và kiểm tra các ước lượng để có được thuật
toán tốt nhất.
Sử dụng máy tính trong công việc, con người luôn mong muốn
Đối với một thuật toán, chúng ta thường quan tâm đến các yếu tố
- máy tính chạy càng ngày càng nhanh hơn sau.
- xử lý được nhiều dữ liệu hơn Kích thước, dung lượng của dữ liệu đầu vào: N
- có thể giải quyết được những vấn đề tưởng chừng như không Thời gian thực hiện. Thường tỉ lệ với:
thể giải quyết được.
1
log N
Công nghệ máy tính chỉ nâng cao được các thứ theo một hệ số cố
định. N
Hãy suy nghĩ N log N
Một thuật toán được thiết kế cẩn thận có thể thực hiện được mức N2
độ cải tiến lớn hơn nhiều: 2N
- Abacus
- Bàn tính (Slide Rule) Đôi khi chúng ta cũng gặp các tỉ lệ khác như:
- Máy tính (Calculator) log log N (log(log(N))
- Máy vi tính (Computer) log* N số các log cho đến khi đạt đến 1
- Máy siêu tính (Supercomputer)
Thường thì người ta sẽ viết ra các công thức ước lượng thời gian
chạy trong các trường hợp:
C programming. 2003 - 2005 3 C programming. 2003 - 2005 4
- xấu nhất (đây là trường hợp đảm bảo thuật toán không vượt Biểu Diễn Xếp Chồng (Stack) Bằng Cấu Trúc DSLK
qua ngưỡng này)
- trung bình (mang tính ước lượng) Các thao tác trên Stack đã được giới thiệu trong phần trước.
Việc phân tích phụ thuộc vào các thông tin chi tiết trên Chúng ta sẽ cài đặt Stack bằng DSLK
- chi phí của các thao tác cơ sở trong quá trình xử lý
- thuộc tính của dữ liệu đầu vào.
/* cach 2: cai dat khac cua PopStack, vua day phan Trong trường hợp sử dụng PopStack cách 2, đoạn mã trên cần
tu o dinh ra khoi stack vua lay gia tri cua
phan tu nay */
sửa lại như sau:
int PopStack (IntStack * ps, int * pi) ...
{ PushStack(&s, 1);
IntListPtr temp; ...
while(PopStack(&s,&I))
if (ps->top == NULL) {
{ printf(“%d\n”, I);
return 0; /* FAILURE */ }
} ...
struct intGenListNode
front {
data+next data+next data+next int data;
1 2 3 null struct intGenListNode * subList;
rear struct intGenListNode * next;
};
size
3 Head
1 null 2 null ? 5 null null
Size
3 null
Danh Sách Liên Kết Kép (Doubly-Linked List)
4 null null
front Với cấu trúc danh sách tổng quát, chúng ta có thể sử dụng đệ quy
Prev+Data+Next Prev+Data+Next Prev+Data+Next
null 1 2 3 null để duyệt và hiển thị nội dung toàn bộ danh sách.
rear
void DisplayIntgenList (struct intGenListNode * glptr)
{
size while (glptr != NULL)
3 {
if (glptr->subList == NULL)
{ /* atomic value */
printf ("%d\n", glptr->data);
}
Danh Sách Liên Kết Hai Đầu (Double Ended Queue) else
{ /* sub-list */
DisplayIntGenList (glptr->subList);
}
glptr = glptr->next;
}
}
Đây là những thuật toán sắp xếp đơn giản, dễ cài đặt.
Chạy rất nhanh với những tập tin dữ liệu kích thước nhỏ.
Trong một số trường hợp đặc biệt, các thuật toán này chạy rất hiệu
quả.
A S O R T I N G E X A M P L E
A S O R T I N G E X A M P L E A S O R T I N G E X A M P L E
A S O R T I N G R X A M P L E A O S R T I N G E X A M P L E
A A O R T I N G E X S M P L E A O R S T I N G E X A M P L E
A A E R T I N G O X S M P L E A O R S T I N G E X A M P L E
A A E E T I N G O X S M P L R A I O R S T N G E X A M P L E
A A E E G I N T O X S M P L R A I N O R S T G E X A M P L E
A A E E G I N T O X S M P L R A G I N O R S T E X A M P L E
A A E E G I L T O X S M P N R A E G I N O R S T X A M P L E
A A E E G I L M O X S T P N R A E G I N O R S T X A M P L E
A A E E G I L M N X S T P O R A A E G I N O R S T X M P L E
A A E E G I L M N O S T P X R A A E G I M N O R S T X P L E
A A E E G I L M N O P T S X R A A E G I M N O P R S T X L E
A A E E G I L M N O P R S X T A A E G I L M N O P R S T X E
A A E E G I L M N O P R S X T A A E E G I L M N O P R S T X
A A E E G I L M N O P R S T X A A E E G I L M N O P R S T X
A A E E G I L M N O P R S T X
void selection (itemType a[], int l, int r) for (i = l+1; i <= r; i++)
{ {
int i, j; itemType v = a[i];
for (i = l; i < r; i++) j = i;
{ while (j>l && less(v, a[j-1]))
int min = i; { a[j] = a[j-1]; j--; }
for ( j = i+1; j <= r; j++) a[j] = v;
if (less (a[j], a[min])) min = j; }
exch(a[i], a[min]); }
}
}
Với bộ dữ liệu trong đó các bản ghi có kích thước lớn, khóa nhỏ
selection sort tăng tuyến tính theo số các bản ghi
void bubble (itemType a[], int l, int r) N bản ghi M từ (khóa là một từ)
{
int i, j; số lần so sánh: N^2/2
for (i = l; i < r; i++) số lần hoán vị: NM
for (j = r; j > i; j--)
compexch(a[j], a[j-1]); if N tỉ lệ với M
}
chi phí và số bản ghi tỉ lệ với:
Thuật toán nổi bọt được cải tiến để chạy nhanh hơn:
Với các tập tin có các bản ghi gần như đã theo thứ tự
- thêm kiểm tra điều kiện dừng nếu không có hoán vị.
bubble sort và insertion sort có thể đạt mức tuyến tính
- nổi bọt hai chiều. (trong trường hợp này thuật toán sắp xếp nhanh quicksort lại
có mức độ phức tạp bình phương)
Khi sắp xếp các bản ghi lớn, có nhiều trường, nên thực hiện cách Sắp xếp theo khóa thứ nhất, sau đó sắp tiếp theo khóa thứ 2
hoán vị các tham chiếu đến các bản ghi thay vì phải hoán vị toàn
bộ nội dung bản ghi.
Aaron 4 Fox 1
Andrews 3 Quilici 1
1 9 Fox 1
Battle 4 Chen 2
2 4 Quilici 1
Chen 2 Furia 3
3 8 Chen 2
Fox 1 Kanaga 3
4 3 Furia 3
Furia 3 Andrews 3
5 1 Kanaga 3
Gazsi 4 Rohde 3
6 5 Andrews 3
Kanaga 3 Battle 4
7 10 Rohde 3
Quilici 1 Aaron 4
8 6 Battle 4
Rohde 3 Gazsi 4
9 2 Aaron 4
10 7 Gazsi 4
Sắp xếp theo khóa thứ 2, nếu bằng nhau thì dừng lại, thực hiện
sắp xếp theo khóa thứ nhất cho các bản ghi cùng khóa 2.
Việc cài đặt chỉ cần thay đổi chút ít trong phần so sánh giá trị giữa
các phần tử.
Fox 1
Trong trường hợp dùng mảng
Quilici 1
Chen 2
typedef int itemType
Andrews 3
#define less(A, B) (data[A].key < data[B].key)
#define exch(A, B) {itemType t = A; A = B; B = t;}
Furia 3
Kanaga 3
Trong trường hợp dùng con trỏ đến các bản ghi Rohde 3
Aaron 4
typedef dataType* itemType Battle 4
#define less(A, B) (*A.key < *B.key)
#define exch(A, B) {itemType t = A; A = B; B = t;} Gazsi 4
Ta chia tập dữ liệu thành 4 phần: Sử dụng thuật toán sắp xếp chèn với bước tăng là 4
- cứ đến phần tử thứ 4 tính từ phần tử thứ nhất.
- cứ đến phần tử thứ 4 tính từ phần tử thứ hai. A S O R T I N G E X A M P L E
- cứ đến phần tử thứ 4 tính từ phần tử thứ ba. A I O R T S N G E X A M P L E
A I N R T S O G E X A M P L E
- cứ đến phần tử thứ 4 tính từ phần tử thứ tư. A I N G T S O R E X A M P L E
A I N G E S O R T X A M P L E
A S O R T I N G E X A M P L E A I N G E S O R T X A M P L E
A S O R E I N G T X A M P L E A I A G E S N R T X O M P L E
A S O R E I N G P X A M T L E A I A G E S N M T X O R P L E
A I A G E S N M P X O R T L E
A I O R E S N G P X A M T L E A I A G E L N M P S O R T X E
A I O R E S N G P X A M T L E A I A G E L E M P S N R T X O
A I O R E L N G P S A M T X E
Cài đặt thuật toán
A I N R E L O G P S A M T X E
A I A R E L N G P S O M T X E
h = 4;
A I A R E L E G P S N M T X O for(i = 1+h; i<=r; i++)
{
A I A G E L E R P S N M T X O itemType v = a[i];
A I A G E L E M P S N R T X O j = i;
while(j >= 1+h && less(v, a[j-h]))
A I A G E L E M P S N R T X O { a[j] = a[j-h]; j -= h; }
a[j] = v;
}
Qua mỗi bước, thứ tự của danh sách ngày càng rõ hơn.
Giả sử chúng ta sắp xếp tăng dần một danh sách bằng thuật toán #define SHRINKFACTOR 1.3
nổi bọt. comb_sort(itemType a[], int size)
{
Thuật toán sắp xếp nổi bọt có một nhược điểm là nếu phần tử int switches, i, j, top, gap;
tương đối nhỏ nằm ở gần cuối danh sách thì sẽ di chuyển rất chậm
về phía đầu (có thể gọi đây là con rùa). Còn phần tử có trị khóa gap = size;
lớn, nằm gần đầu danh sách thì lại di chuyển rất nhanh về phía vị do {
trí của nó (hãy cứ gọi phần tử loại thế này là con thỏ). gap = (int) ((float)gap/SHRINKFACTOR);
switch (gap)
{
case 0: /* the smallest gap is 1 bubble sort */
gap = 1;
break;
case 9:
case 10:
gap = 11;
break;
default:
break;
}
switches = 0; /* dirty pass flag */
top = size - gap;
for(i=0; i<top; ++i)
{
j = i + gap;
if(a[i] > a[j])
{ /* swap */
exch(a[i], a[j]);
++switches;
}
}
} while(switches || (gap>1));
}
Sắp xếp nổi bọt
Đây không phải là thuật toán Shellsort.
Một cải tiến nhỏ, trong đó khoảng cách giữa các phần tử cần so (tham khảo Stephen Lacey, Richard Box. Byte 4,1991)
sánh lớn hơn 1 sẽ cho phép biến các “con rùa” thành “con thỏ”.
A S O R T I N G E X A M P L E A S
A A E E T I N G O X S M P L R A M P L
A A E A A S M P L E
A A
A O
L I N G O P M R X T S E X
L I G M O P N A A E O X S M P L E
G I L
I L R
I E R T I N G
N P O
O P A A E E T I N G O X S M P L R
P
S T X
T X Cài đặt thuật toán phân vùng
T
A A E E G I L M N O P R S T X
v: phần tử mốc
i: vị trí dò từ trái sang phải
j: vị trí dò từ phải sang trái.
1. Phạm vi tìm kiếm ban đầu là toàn bộ danh sách 1. Phạm vi tìm kiếm ban đầu là toàn bộ danh sách k=0 đến
2. Lấy phần tử chính giữa của phạm vi tìm kiếm (a[j]) rồi so sánh m=n-1.
với x. 2. Lấy phần tử chính giữa của phạm vi tìm kiếm (a[j]) rồi so sánh
• Nếu a[j] = x. trả về chỉ số j. STOP. với x.
• Nếu a[j] > x. Phạm vi tìm kiếm mới là các phần tử có chỉ • Nếu a[j] = x. trả về chỉ số j. STOP.
số nhỏ hơn j. • Nếu a[j] > x. Phạm vi tìm kiếm mới là các phần tử có chỉ
• Nếu a[j] < x. Phạm vi tìm kiếm mới là các phần tử có chỉ số nhỏ hơn j. Gọi đệ quy hàm tìm kiếm với phạm vi mới
số lớn hơn j. là (k, j-1)
3. Nếu còn tồn tại phạm vi tìm kiếm thì lặp lại bước 2, • Nếu a[j] < x. Phạm vi tìm kiếm mới là các phần tử có chỉ
ngược lại, không tìm thấy x. STOP. số lớn hơn j. Gọi đệ quy hàm tìm kiếm với phạm vi mới là
(j+1, m)
3. Điều kiện dừng: x=a[j] hoặc k > m.
Cài đặt thuật toán
b c d e
b c d e
b c b c
f g h
f g h
g g
Mỗi nút có đúng hai con trỏ chỉ tới nút con bên trái và nút con bên
Vì số lượng các nút con của một nút không xác định trước nên sẽ phải của nó.
rất khó khăn nếu đưa các liên kết trực tiếp từ nút đến các nút con
của nó. struct nodetype
Thay vào đó, chúng ta có thể quản lý các nút con của một nút bằng {
int key;
một danh sách liên kết. int info;
struct nodetype* left;
struct nodetype* right;
};
typedef struc nodetype* NODEPTR;
NODEPTR tree;
Tạo cây
void Insert(NODEPTR root, int x, int a)
{
Khởi tạo (inititalize) NODEPTR p;
Khởi động cây nhị phân, cho chương trình biết hiện tại cây nhị if(x == root->key) // key duplicated, STOP
phân rỗng. {
printf("bi trung khoa, khong them nut nay duoc");
return;
void Inititialize (NODEPTR *root) }
{
*root = NULL; // Stop condition
} if(x < root->info && root->left == NULL)
{
p = New_Node();
Gọi hàm: p->key =x;
Initialize (&tree); p->info = a;
p->left = NULL;
p->right = NULL;
root->left=p;
Cấp phát vùng nhớ (New_Node)
return;
Cấp phát một nút cho cây nhị phân. Hàm New_Node trả về địa chỉ }
của nút vừa cấp phát.
// stop condition
if(x > root->info && root->right == NULL)
NODEPTR New_Node(void) {
{ p = New_Node();
NODEPTR p; p->key =x;
p = (NODEPTR)malloc(sizeof(struct nodetype)); p->info = a;
return(p); p->left = NULL;
} p->right = NULL;
root->right=p ;
return;
Gọi hàm: }
p = New_Node();
if(x < root->info) // recursion step
Insert(root->left, x,a); // recursion on left
else
Insert(root->right, x,a); // recursion on right
}
Cập nhật cây Trường hợp 2: Nút p cần xóa có một cây con. Chọn nút con của p
là nút thay thế (rp) vào vị trí của p. Sau đó, tạo liên kết từ nút cha
Giải phóng vùng nhớ (Free_Node)
của p đến rp. Cuối cùng, hủy p.
void Free_Node(NODEPTR p)
{
free(p);
}
Gọi hàm:
Free_Node (p);
Gọi hàm:
Remove(p);
Hàm Remove xóa nút p, trả về con trỏ chỉ tới nút thay thế (rp).
Do hàm Remove trả về địa chỉ của nút thay thế nên nếu dùng
NODEPTR Remove(NODEPTR p) Remove để xóa nút gốc, nút thay thế sẽ là nút gốc mới của cây. Khi
{ đó ta gọi.
NODEPTR rp, f; tree = Remove(tree);
if(p == NULL)
printf(“p is not real. Cannot delete!\n”);
else
C programming. 2003 - 2005 47 C programming. 2003 - 2005 48
Tìm kiếm (Search) Duyệt cây
Tìm nút có khóa x trên BST có nút gốc là root. Nếu tìm thấy thì trả
về địa chỉ của nút có khóa x, ngược lại, trả về trị NULL. Duyệt theo thứ tự NLR (Preorder)
Tìm kiếm bằng phương pháp nhị phân
void Preorder (NODEPTR root)
{
NODEPTR Search(NODEPTR root, int x) if(root != NULL)
{ {
NODEPTR p; printf("%d ", root->info);
p = root; Preorder(root->left);
while(p != NULL && x != p->key) Preorder (root->right);
if(x < p->key) }
p = p->left; }
else
p = p->right;
return (p);
}
Duyệt theo thứ tự LNR (Inorder)
Gọi hàm:
void Inorder(NODEPTR root)
p = Search(tree, x);
{
if(root != NULL)
{
Inorder(root->left);
printf("%d ", root->info);
Inorder(root->right);
}
}
4 Định nghĩa:
Chỉ số cân bằng (balance factor) của một nút p trên cây AVL:
2 5 bf(p) = lh(p) – rh(p)
lh(p): chiều cao của nhánh cây con trái của p
7 rh(p): chiều cao của nhánh cây con phải của p.
1 3
Để đảm bảo tốc độ tìm kiếm thì cây BST phải có các nút có hai
nhánh trái và phải cân đối.
Cây BST cần phải được tổ chức lại cho cân bằng. Tổ chức thành
cây AVL.
4
2 5
1 3 7 Minh họa các vị trí có thể thêm nút lá vào cây AVL, khi thêm vào một trong
các vị trí B thì cây vẫn cân bằng. Khi thêm nút lá vào một trong các vị trí U
thì cây sẽ mất cân bằng. Các số tại các nút trên cây là chỉ số cân bằng của các
6 nút trước khi thêm vào nút mới
Thêm nút Hai trường hợp khi thêm nút khóa x vào cây AVL làm cây mất cân
Thêm nút có khoá x, nội dung a vào AVL sao cho sau khi thêm, cây bằng là khi thêm nút vào sau bên trái nút có bf = 1 và thêm nút vào
nhị phân vẫn là AVL. sau bên phải nút có bf = -1.
return p;
}
Phép xoay phải (Rotate_Right): xoay phải cay nhị phân tìm kiếm có
nút gốc là root, yêu cầu root phải có nút con bên trái (p). Sau khi
xoay phải thì p trở thành nút gốc, nút gốc cũ trở thành nút con bên
phải của nút gốc mới.
Hàm xoay phải trả về con trỏ chỉ nút gốc mới.
if(root == NULL)
printf("Khong the xoay phai vi cay bi rong.");
else
if(root->left == NULL)
Cài đặt giải thuật: printf("Khong the xoay phai vi khong \
co nut con ben trai.");
Phép xoay trái (Rotate_Left): xoay trái cây nhị phân tìm kiếm có nút else
gốc là root, yêu cầu root phải có nút con bên phải (p). Sau khi xoay {
trái thì p trở thành nút gốc, nút gốc cũ trở thành nút con bên trái p = root->left;
của nút gốc mới. root->left = p->right;
p->right = root;
Hàm xoay trái trả về con trỏ chỉ tới nút gốc mới. }
return p;
NODEPTR Rotate_Left(NODEPTR root) }
{
NODEPTR p;
if(root == NULL)
printf("Khong the xoay trai vi cay bi rong.");
else
if(root->right == NULL)
printf("Khong the xoay trai vi khong \
co nut con ben phai.");
else
{
C programming. 2003 - 2005 57 C programming. 2003 - 2005 58
Thêm nút (Insert) q->key =x;
q->info = a;
Thêm nút khóa x, nội dung a vào cây AVL q->bf = 0;
- Thêm nút theo giải thuật thêm nút vào cây nhị phân tìm kiếm. q->left = NULL;
q->right = NULL;
- Cân bằng lại cây bằng cách xoay đơn hay xoay kép.
if(x < fp->info)
fp->left = q;
void Insert(NODEPTR &pavltree, int x, int a)
else
{
fp->right = q;
NODEPTR fp, p, q, // fp: p’s father, q: p’s child
fya, ya, /* ya: the closest node that is
/* Adjust the balance factor for nodes from ya to q.
possible to be imbalanced */
If left imbalanced, bf of those nodes would be 1.
/* fya: ya’s father */
If right imbalanced, bf of those nodes would be –1.*/
s; // s: ya’s child in the imbalanced branch
int imbal; /* imbal = 1 if left imbalanced
if(x < ya->info)
-1 if right imbalanced */
p = ya->left;
else
// initializing
p = ya->right;
fp = NULL;
s = p; // s is a child of ya
p = pavltree;
while(p != q)
fya = NULL;
{
ya = p;
if(x < p->info)
{
/* search for fp, ya and fya, inserted node will be
p->bf = 1;
fp’s child */
p = p->left;
while(p != NULL)
}
{
else
if(x == p->info) // duplicated -> STOP
{
return;
p->bf = -1;
if (x < p->info)
p = p->right;
q = p->left;
}
else
}
q = p->right;
// where to go?
if(q != NULL)
if(x < ya->info)
if(q->bf != 0)
imbal = 1;
{
else
fya = p;
imbal = -1;
ya = q;
}
if(ya->bf == 0)
fp = p;
{
p = q;
ya->bf = imbal;
}
return;
}
// Insert new node
if(ya->bf != imbal)
q = New_Node();
C programming. 2003 - 2005 59 C programming. 2003 - 2005 60
{ else
ya->bf = 0; if(ya == fya->right)
return; fya->right = p;
} else
if(s->bf == imbal) // single rotation fya->left = p;
{ }
if(imbal == 1) // right rotating
p = Rotate_Right(ya);
else // left rotating
p = Rotate_Left(ya);
Để tạo cây AVL, ta dùng giải thuật sau.
ya->bf = 0;
s->bf = 0; void Create_AVLTree(NODEPTR &root)
} {
else // double rotation int khoa, noidung;
{ char so[10];
if(imbal == 1) // right-right double rotation NODEPTR p;
{
ya->left = Rotate_Left(s); do {
p = Rotate_Right(ya); printf("Nhap khoa :");
} gets(so) ;
else // right-left double rotation khoa = atoi(so);
{ if (khoa !=0)
ya->right = Rotate_Right(s); {
p = Rotate_Left(ya); printf("Nhap noi dung :");
} gets(so) ;
if(p->bf == 0) // p is the new node noidung = atoi(so);
{ if (root==NULL)
ya->bf = 0; {
s->bf = 0; p = New_Node();
} p->key = khoa;
else p->info = noidung;
if(p->bf == imbal) p->bf = 0 ;
{ p->left = NULL;
ya->bf = -imbal; p->right = NULL;
s->bf = 0; root =p;
} }
else else
{ Insert(root,khoa,noidung);
ya->bf = 0; }
s->bf = imbal; } while (khoa!=0); // STOP
} }
p->bf = 0;
}
Để tạo cây nhị phân tìm kiếm do biến tree quản lý, lời gọi hàm là:
if(fya == NULL) Create_AVLTree(&tree);
pavltree = p;
C programming. 2003 - 2005 61 C programming. 2003 - 2005 62
Cập nhật cây + gọi đệ quy để xóa nút x ở nhánh trái của root:
Remove(root->left, x);
Tìm kiếm (Search)
Tìm nút có khóa x trên cây AVL có gốc là root. Nếu tìm thấy thì trả + Gọi balance_left để cân bằng lại cây có nút gốc root
về địa chỉ của nút có khóa bằng x, ngược lại, nếu không tìm thấy nếu nhánh cây con bên trái bị giảm độ cao.
thì trả về giá trị NULL. Nếu (x > root->info)
Cây AVL là một BST nên ta tìm kiếm nhanh bằng phương pháp tìm + gọi đệ quy để xóa nút x ở nhánh phải của root:
nhị phân. Hơn nữa, cây AVL luôn cân bằng nên thời gian tìm kiếm
trong mọi trường hợp nhanh hơn nhiều so với việc tìm kiếm trên Remove(root->right, x);
cây BST thông thường. + Gọi balance_right để cân bằng lại cây có nút gốc root
nếu nhánh cây con bên phải bị giảm độ cao.
NODEPTR search(NODEPTR root, int x) Nếu (x == root->info)
{
NODEPTR p; Xóa nút root như phép toán xóa trên cây BST.
p = root;
while(p != NULL && x!=p->key)
if(x < p->key) Cài đặt:
p = p->left; (Tự cài đặt)
else
p = p->right;
return(p);
}
Duyệt cây
Cây AVL cũng là cây nhị phân nên ta sẽ áp dụng các phương pháp
Gọi hàm: duyệt Preorder, Inorder và Postorder vào cây AVL.
Search(tree, x);
Xóa nút khóa x trên cây AVL sao cho sau khi xóa, cây vẫn là cây
AVL.
Giải thuật:
Nếu (root == NULL)
Thông báo (“Không thể xóa nút khóa x trên cây”);
Nếu (root != NULL)
Nếu (x < root->info)
C programming. 2003 - 2005 63 C programming. 2003 - 2005 64