thuật toán trên cấu trúc cây

32
C programming. 2003 - 2005 1 C programming. 2003 - 2005 2 THUT TOÁN THUT TOÁN TRÊN CU TRÚC CÂY

Upload: api-3710223

Post on 07-Jun-2015

4.553 views

Category:

Documents


4 download

TRANSCRIPT

Page 1: Thuật Toán Trên cấu Trúc cây

C programming. 2003 - 2005 1 C programming. 2003 - 2005 2

THUẬT TOÁN

THUẬT TOÁN TRÊN CẤU TRÚC CÂY

Page 2: Thuật Toán Trên cấu Trúc cây

C programming. 2003 - 2005 3

Giới thiệu

Thuật toán - Thuật giải (Algorithm) là phương pháp để giải một bài toán.

Cấu trúc dữ liệu (Data structure): cách lưu trữ thông tin.

Các thuật toán hiệu quả dùng những cấu trúc dữ liệu được tổ chức tốt.

Trong chương trình, chúng ta sẽ nghiên cứu các thuật toán sau:

- sắp xếp (sorting)

- tìm kiếm (searching)

- ...

Sử dụng máy tính trong công việc, con người luôn mong muốn

- máy tính chạy càng ngày càng nhanh hơn

- xử lý được nhiều dữ liệu hơn

- có thể giải quyết được những vấn đề tưởng chừng như không thể giải quyết được.

Công nghệ máy tính chỉ nâng cao được các thứ theo một hệ số cố định.

Hãy suy nghĩ

Một thuật toán được thiết kế cẩn thận có thể thực hiện được mức độ cải tiến lớn hơn nhiều:

- Abacus

- Bàn tính (Slide Rule)

- Máy tính (Calculator)

- Máy vi tính (Computer)

- Máy siêu tính (Supercomputer)

C programming. 2003 - 2005 4

Tuy nhiên, một thuật toán tồi dù có chạy trên một máy tính cực nhanh nhưng vẫn có thể xử lý bài toán chậm hơn so với một thuật toán tốt chạy trên máy Abacus.

Nghiên cứu thuật toán là vấn đề luôn tồn tại từ xưa đến nay.

Phân tích thuật toán

Người ta so sánh các thuật toán dựa trên các phép ước lượng chi phí (thời gian chạy của thuật toán, lượng bộ nhớ mà thuật toán 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 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.

Đối với một thuật toán, chúng ta thường quan tâm đến các yếu tố sau.

Kích thước, dung lượng của dữ liệu đầu vào: N

Thời gian thực hiện. Thường tỉ lệ với:

1

log N

N

N log N

N2

2N

Đôi khi chúng ta cũng gặp các tỉ lệ khác như: log log N (log(log(N))

log* N số các log cho đến khi đạt đến 1

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:

Page 3: Thuật Toán Trên cấu Trúc cây

C programming. 2003 - 2005 5

- xấu nhất (đây là trường hợp đảm bảo thuật toán không vượt qua ngưỡng này)

- trung bình (mang tính ước lượng)

Việc phân tích phụ thuộc vào các thông tin chi tiết trên

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

Để phân tích độ phức tạp của thuật toán, người ta sử dụng ký pháp chữ O lớn (big O-notation).

T(N) được gọi là có mức độ phức tạp O(F(N)) nếu tồn tại một giá trị c (hằng) và n0 , sao cho với mọi N > n0 ta có:

T(N) ≤ c * F(N)

T(N) là độ phức tạp chính xác của một thuật toán được đề nghị để giải bài toán có kích thước N. F(N) là giới hạn trên, nghĩa là các thời gian/không gian hay nói chung là các chi phí cho bài toán có kích thước N không vượt qua mức F(N).

Trên thực tế, bao giờ người ta cũng muốn tính được giá trị F(N) nhỏ nhất – chi phí nhỏ nhất phải có.

Ví dụ: T(N) = 3 * N2 + 5.

Dễ thấy rằng, với c = 4 và n0 = 2, ta có:

3 * N2 + 5 ≤ 4 * N2

nghĩa là T(N) là O(N2).

C programming. 2003 - 2005 6

Biểu Diễn Xếp Chồng (Stack) Bằng Cấu Trúc DSLK

Các thao tác trên Stack đã được giới thiệu trong phần trước.

Chúng ta sẽ cài đặt Stack bằng DSLK

typedef struct intListNode * IntListPtr; typedef struct intListNode { int data; IntListPtr next; } IntListNode; typedef struct intStack { IntListPtr top; unsigned size; } IntStack; int MakeStack (IntStack * ps) { ps->top = NULL; ps->size = 0; return 1; /* success */ } int IsEmptyStack (IntStack * ps) { return (ps->top == NULL); } int PushStack (IntStack * ps, int num) {

Con trỏ đến nút đầu tiên trong danh sách các phần tử của stack

Số lượng phần tử trong danh sách (# số phần tử của Stack)

Page 4: Thuật Toán Trên cấu Trúc cây

C programming. 2003 - 2005 7

IntListPtr new; new = malloc (sizeof(IntListNode)); if (new == NULL) { return 0; /* FAILURE */ } new->data = num; new->next = ps->top; ps->top = new; ps->size++; return 1; /* success */ } /* Cach 1 */ int PopStack (IntStack * ps) { IntListPtr temp; if (ps->top == NULL) { return 0; /* FAILURE */ } temp = ps->top; ps->top = ps->top->next; free (temp); ps->size--; return 1; /* success */ } int TopStack (IntStack * ps) { /* assume that stack is not empty */ return ps->top->data; } /* cach 2: cai dat khac cua PopStack, vua day phan tu o dinh ra khoi stack vua lay gia tri cua phan tu nay */ int PopStack (IntStack * ps, int * pi) { IntListPtr temp; if (ps->top == NULL) { return 0; /* FAILURE */ }

C programming. 2003 - 2005 8

*pi = ps->top->data; temp = ps->top; ps->top = ps->top->next; free (temp); ps->size--; return 1; /* success */ }

Sử dụng Stack

#include “intStack.h” ... int main (void) { IntStack s; MakeStack(s); PushStack(&s, 1); PushStack(&s, 1); PushStack(&s, 1); while(!IsEmptyStack(&s)) { printf(“%d\n”, TopStack(&s)); PopStack(&s); // su dung PopStack cach 1 } FreeStack(&s); return EXIT_SUCCESS; }

Trong trường hợp sử dụng PopStack cách 2, đoạn mã trên cần sửa lại như sau: ... PushStack(&s, 1); ... while(PopStack(&s,&I)) { printf(“%d\n”, I); } ...

Page 5: Thuật Toán Trên cấu Trúc cây

C programming. 2003 - 2005 9

Hàng Đợi Bằng Cấu Trúc DSLK

Tương tự như cách biểu diễn Xếp chồng bằng cấu trúc DSLK, hãy biểu diễn Hàng Đợi cũng bằng cấu trúc DSLK.

Danh Sách Liên Kết Kép (Doubly-Linked List)

Danh Sách Liên Kết Hai Đầu (Double Ended Queue)

front rear size

3

data+next 1

data+next2

data+next 3 null

front rear size

3

Prev+Data+Next null 1

Prev+Data+Next

2 Prev+Data+Next

3 null

C programming. 2003 - 2005 10

Danh Sách Liên Kết Tổng Quát

Trong danh sách thường, mỗi phần tử mang dữ liệu riêng.

(1,2,3,4)

Trong danh sách tổng quát, mỗi phần tử có thể là một danh sách. (1,2,(3,4),5)

struct intGenListNode { int data; struct intGenListNode * subList; struct intGenListNode * next; };

Với cấu trúc danh sách tổng quát, chúng ta có thể sử dụng đệ quy để duyệt và hiển thị nội dung toàn bộ danh sách.

void DisplayIntgenList (struct intGenListNode * glptr) { while (glptr != NULL) { if (glptr->subList == NULL) { /* atomic value */ printf ("%d\n", glptr->data); } else { /* sub-list */ DisplayIntGenList (glptr->subList); } glptr = glptr->next; } }

Head Size

1 null 2 null ? 5 null null

3 null

4 null null

Page 6: Thuật Toán Trên cấu Trúc cây

C programming. 2003 - 2005 11

Sắp xếp (Sorting)

Các thuật toán sắp xếp cơ bản Insertion Sort - Sắp xếp bằng cách chèn Selection Sort - Sắp xếp bằng cách chọn Bubble Sort - Sắp xếp theo nguyên lý nổi bọt Shellsort - Sắp xếp Đâ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ả.

Khái niệm và điều kiện:

- Các tập tin (Files) chứa các bản ghi (Records) phân biệt với nhau bởi khóa (Keys).

- Nội dung của tập tin chứa được trong bộ nhớ

typedef int itemType #define less (A, B) ( A < B ) #define exch (A, B) {itemType t = A; A = B; B = t; }

C programming. 2003 - 2005 12

Trên đây chúng ta dùng cách khai báo macro, không phải là định nghĩa hàm con (subroutine).

- Macro: đơn giản, chi phí thấp

- Hàm con: tổng quát hơn, nhưng tốn kém hơn

Page 7: Thuật Toán Trên cấu Trúc cây

C programming. 2003 - 2005 13

Selection sort - Sắp xếp bằng cách chọn

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 A O R T I N G E X S M P L E A A E R T I N G O X S M P L E A A E E T I N G O X S M P L RA A E E G I N T O X S M P L RA A E E G I N T O X S M P L RA A E E G I L T O X S M P N RA A E E G I L M O X S T P N RA A E E G I L M N X S T P O RA A E E G I L M N O S T P X RA A E E G I L M N O P T S X RA A E E G I L M N O P R S X TA A E E G I L M N O P R S X TA 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) { int i, j; for (i = l; i < r; i++) { int min = i; for ( j = i+1; j <= r; j++) if (less (a[j], a[min])) min = j; exch(a[i], a[min]); } }

C programming. 2003 - 2005 14

Insertion sort - Sắp xếp bằng cách chèn

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 O S R T I N G E X A M P L E A O R S T I N G E X A M P L E A O R S T I N G E X A M P L E A I O R S T N G E X A M P L E A I N O R S T G E X A M P L E A G I N O R S T E X A M P L E A E G I N O R S T X A M P L E A E G I N O R S T X A M P L E A A E G I N O R S T X M P L E A A E G I M N O R S T X P L E A A E G I M N O P R S T X L E 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 T X A A E E G I L M N O P R S T X

void insertion (itemType a[], int l, int r) { int i, j; for (i = l+1; i <= r; i++) { itemType v = a[i]; j = i; while (j>l && less(v, a[j-1])) { a[j] = a[j-1]; j--; } a[j] = v; } }

Page 8: Thuật Toán Trên cấu Trúc cây

C programming. 2003 - 2005 15

Bubble sort - Sắp xếp theo nguyên lý nổi bọt

A S O R T I N G E X A M P L E A A S O R T I N G E X E M P L A A E S O R T I N G E X L M P A A E E S O R T I N G L X M P A A E E G S O R T I N L M X P A A E E G I S O R T L N M P X A A E E G I L S O R T M N P X A A E E G I L M S O R T N P X A A E E G I L M N S O R T P X A A E E G I L M N O S P R T X A A E E G I L M N O P S R 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 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 bubble (itemType a[], int l, int r) { int i, j; for (i = l; i < r; i++) for (j = r; j > i; j--) compexch(a[j], a[j-1]); }

Thuật toán nổi bọt được cải tiến để chạy nhanh hơn:

- thêm kiểm tra điều kiện dừng nếu không có hoán vị.

- nổi bọt hai chiều.

C programming. 2003 - 2005 16

Tính chất của các giải thuật sắp xếp cơ bản

Thời gian chạy là bậc 2.

Selection sort:

số lần so sánh: N-1 + N-2 + . . . + 2 + 1 = N^2/2

số lần hoán vị: N

Insertion sort:

số lần so sánh: (N-1 + N-2 + . . . + 2 + 1) / 2 = N^2/4

số lần hoán vị: N^2/4

Bubble sort:

số lần so sánh: N-1 + N-2 + . . . + 2 + 1 = N^2/2

số lần hoán vị: khoảng N^2/2

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

N bản ghi M từ (khóa là một từ)

số lần so sánh: N^2/2

số lần hoán vị: NM

if N tỉ lệ với M

chi phí và số bản ghi tỉ lệ với:

Với các tập tin có các bản ghi gần như đã theo thứ tự

bubble sort và insertion sort có thể đạt mức tuyến tính (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)

Page 9: Thuật Toán Trên cấu Trúc cây

C programming. 2003 - 2005 17

Sắp xếp con trỏ

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

1 9 Fox 1

2 4 Quilici 1

3 8 Chen 2

4 3 Furia 3

5 1 Kanaga 3

6 5 Andrews 3

7 10 Rohde 3

8 6 Battle 4

9 2 Aaron 4

10 7 Gazsi 4

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

Trong trường hợp dùng mảng

typedef int itemType #define less(A, B) (data[A].key < data[B].key) #define exch(A, B) {itemType t = A; A = B; B = t;}

Trong trường hợp dùng con trỏ đến các bản ghi

typedef dataType* itemType #define less(A, B) (*A.key < *B.key) #define exch(A, B) {itemType t = A; A = B; B = t;}

C programming. 2003 - 2005 18

Sắp xếp với các bản ghi có hai khóa

Sắp xếp theo khóa thứ nhất, sau đó sắp tiếp theo khóa thứ 2

Aaron 4 Fox 1

Andrews 3 Quilici 1

Battle 4 Chen 2

Chen 2 Furia 3

Fox 1 Kanaga 3

Furia 3 Andrews 3

Gazsi 4 Rohde 3

Kanaga 3 Battle 4

Quilici 1 Aaron 4

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

Fox 1

Quilici 1

Chen 2

Andrews 3

Furia 3

Kanaga 3

Rohde 3

Aaron 4

Battle 4

Gazsi 4

Page 10: Thuật Toán Trên cấu Trúc cây

C programming. 2003 - 2005 19

Sắp xếp chia 4

Ta chia tập dữ liệu thành 4 phần:

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

- cứ đến phần tử thứ 4 tính từ phần tử thứ ba.

- cứ đến phần tử thứ 4 tính từ phần tử thứ tư.

A S O R T I N G E X A M P L E A S O R E I N G T X A M P L E A S O R E I N G P X A M T L E

A I O R E S N G P X A M T L E A I O R E S N G P X A M T L E A I O R E L N G P S A M T X E

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 A I A R E L E G P S N M T X O

A I A G E L E R P S N M T X OA I A G E L E M P S N R T X O

A I A G E L E M P S N R T X O

C programming. 2003 - 2005 20

Sắp xếp đan xen bộ 4

Sử dụng thuật toán sắp xếp chèn với bước tăng là 4

A S O R T I N G E X A M P L E 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 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 I N G E S O R T X A M P L E A I A G E S N R T X O M P 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 A G E L N M P S O R T X E A I A G E L E M P S N R T X O

Cài đặt thuật toán

h = 4; for(i = 1+h; i<=r; i++) { itemType v = a[i]; j = i; while(j >= 1+h && less(v, a[j-h])) { a[j] = a[j-h]; j -= h; } a[j] = v; }

Page 11: Thuật Toán Trên cấu Trúc cây

C programming. 2003 - 2005 21

Shellsort Sử dụng các bước tăng giảm dần

A S O R T I N G E X A M P L EA S O R T I N G E X A M P L EA E O R T I N G E X A M P L S

A E O R T I N G E X A M P L SA E O R T I N G E X A M P L SA E N R T I O G E X A M P L SA E N G T I O R E X A M P L SA E N G E I O R T X A M P L SA E N G E I O R T X A M P L SA E A G E I N R T X O M P L SA E A G E I N M T X O R P L SA E A G E I N M P X O R T L SA E A G E I N M P L O R T X SA E A G E I N M P L O R T X S

A E A G E I N M P L O R T X SA A E G E I N M P L O R T X SA A E G E I N M P L O R T X SA A E E G I N M P L O R T X SA A E E G I N M P L O R T X SA A E E G I N M P L O R T X SA A E E G I M N P L O R T X SA A E E G I M N P L O R T X SA A E E G I L M N P O R T X SA A E E G I L M N O P R T X SA A E E G I L M N O P R T X SA A E E G I L M N O P R T X SA A E E G I L M N O P R T X SA A E E G I L M N O P R S T XA A E E G I L M N O P R S T X

Qua mỗi bước, thứ tự của danh sách ngày càng rõ hơn.

13

4

1

C programming. 2003 - 2005 22

Cài đặt thuật toán

void shellshort(itemType a[], int l, int r) { int i,j; int incs[16] = {1391376, 463792, 198768, 86961, 33936, 13776, 4592, 1968, 861, 336,112, 48, 21, 7, 3, 1 }; for(k = 0; ; < 16; k++) { int h = incs[k]; for(i = l+h; i <= r; i++) { itemType v = a[i]; j = i; while(j >= h && less(v, a[j-h])) { a[j] = a[j-h]; j -= h; } a[j] = v; } } }

Thuật toán Shellsort có tốc độ sắp xếp nhanh, cài đặt đơn giản; rất thích hợp với các tập tin nhỏ và vừa, với các tập tin lớn, thuật toán Shellsort vẫn có hiệu suất thực hiện rất cao.

Tỉ lệ tăng nào là thích hợp?

• có nhiều tỉ lệ đã được chứng minh hiệu quả.

• dễ chọn nhất là dùng: 1, 4, 13, 40, 121, 363, 1090, ...

Page 12: Thuật Toán Trên cấu Trúc cây

C programming. 2003 - 2005 23

Thuật toán CombSoft

Giả sử chúng ta sắp xếp tăng dần một danh sách bằng thuật toán nổi bọt.

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ử 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 lớn, nằm gần đầu danh sách thì lại di chuyển rất nhanh về phía vị trí của nó (hãy cứ gọi phần tử loại thế này là con thỏ).

Một cải tiến nhỏ, trong đó khoảng cách giữa các phần tử cần so sánh lớn hơn 1 sẽ cho phép biến các “con rùa” thành “con thỏ”.

Sắp xếp nổi bọt

C programming. 2003 - 2005 24

Cài đặt thuật toán

#define SHRINKFACTOR 1.3 comb_sort(itemType a[], int size) { int switches, i, j, top, gap; gap = size; do { 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));

}

Đây không phải là thuật toán Shellsort.

(tham khảo Stephen Lacey, Richard Box. Byte 4,1991)

Page 13: Thuật Toán Trên cấu Trúc cây

C programming. 2003 - 2005 25

Quicksort

Để sắp xếp một mảng, đầu tiên chia mảng thành 3 phần:

Các phần tử a[i] có giá trị bằng nhau không thay đổi vị trí

Các phần tử không lớn hơn a[i] nằm ở bên trái phần tử thứ i

Các phần tử không nhỏ hơn a[i] nằm ở bên phải phần tử thứ i.

Sau đó, thực hiện việc sắp xếp các phần bên trái và bên phải. Việc sắp xếp này thực hiện đệ quy.

A S O R 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 A E A A A L I N G O P M R X T S L I G M O P N G I L I L I N P O O P P S T X T X T

A A E E G I L M N O P R S T X

C programming. 2003 - 2005 26

Phân vùng (partitioning) Để phân vùng một mảng trước khi thực hiện sắp xếp, đầu tiên, chúng ta chọn ra một phần tử làm mốc a[i0]

• quét mảng từ bên phải sang để tìm phần tử nhỏ hơn a[i0],

• quét mảng từ bên trái sang để tìm phần tử lớn hơn a[i0].

• hoán vị hai phần tử tìm được.

• lặp lại 3 bước trên cho đến khi các vị trí dò tìm vượt qua mốc.

A S O R T I N G E X A M P L E

A S A M P L

A A S M P L E O E X

A A E O X S M P L E R E R T I N G

A A E E T I N G O X S M P L R

Cài đặt thuật toán phân vùng

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.

int partition(Item a[], int l, int r) { int i, j; Item v; v = a[r]; i = l-1; j = r; for( ; ; )

Page 14: Thuật Toán Trên cấu Trúc cây

C programming. 2003 - 2005 27

{ while(less(a[++i], v)); while(less(v, a[--j])); if(j == l) break; if(i >= j) break; exch(a[i], a[j]); } exch(a[i], a[r]); return i; }

Cài Đặt Quicksort

quicksort (Item a[], int l, int r) { int i; if(r > l) { i = partition(a, l, r); quicksort(a, l, i-1); quicksort(a, i+1, r); } }

Vấn đề phát sinh:

Sẽ có quá nhiều lần gọi đệ qui.

thời gian chạy phụ thuộc vào đầu vào.

Trong trường hợp xấu nhất

- thời gian tăng theo tỉ lệ bình phương.

- bộ nhớ tăng tuyến tính.

C programming. 2003 - 2005 28

Thuật Toán Quicksort Không Đệ Qui.

Chúng ta có thể dùng stack để khử tính đệ quy trong cài đặt thuật toán Quicksort ở trên.

#define push2(A, B) push(A); push(B); void quicksort(Item a[], int l, int r) { int i; stackinit(); push2(l, r); while(!stackempty()) { r = pop(); l = pop(); if(r <= l) continue; i = partition(a, l, r); if(i-l > r-i) { push2(l, i-1); push2(i+1, r); } else { push2(i+1; r); push2(l, i-1); } } }

Với cài đặt trên, trong trường hợp xấu nhất, chúng ta có thể đạt được mức chi phí bộ nhớ nhỏ hơn (lg N) nhưng thời gian chạy thì vẫn ở tỉ lệ bình phương.

Phân tích thuật toán

Tổng thời gian chạy là tổng của

chi_phí * tần_suất

của tất cả các phép toán cơ bản.

chi_phí (cost) phụ thuộc vào kiểu máy tính

tần_suất (frequency) phụ thuộc vào thuật toán và loại dữ liệu đầu vào.

Page 15: Thuật Toán Trên cấu Trúc cây

C programming. 2003 - 2005 29

Đối với thuật toán Quicksort, gọi

A là số lần thực hiện phân vùng.

B là số lần hoán vị

C là số lần so sánh.

Số lần so sánh trong trường hợp xấu nhất là

N + (N-1) + (N-2) + . . . = N(N-1)/2

Giả sử rằng các dữ liệu được đưa vào theo thứ tự ngẫu nhiên, nghĩa là

• mỗi phần tử đều có khả năng được chọn làm phần tử phân vùng ngang nhau

• các tập con cũng có thứ tự ngẫu nhiên.

Số lần so sánh trung bình được xác định theo công thức:

C(N) = N+1

+ (C(1) + C(N-1))/N

+ (C(2) + C(N-2))/N

. . .

+ (C(N-1) + C(1))/N

C(N) = N+1 + 2( C(1) + C(2) + . . . + C(N-1) )/N

NC(N) = N(N+1) + 2( C(1) + C(2) + . . . + C(N-1) )

NC(N) – (N-1)C(N-1) = 2N + 2C(N-1)

NC(N) = (N+1)C(N-1) + 2N

C(N) / (N+1) = C(N-1) /N + 2 / (N+1)

= 2( 1 + 1/2 + 1/3 + . . . + 1/(N+1) )

= 2 ln N + (eps)

Như vậy, trung bình, thuật toán Quicksort dùng khoảng 2N ln N = (1.38...) N lg phép so sánh (và số lần hoán vị khoảng bằng 1/6 số lần so sánh).

C programming. 2003 - 2005 30

Tìm Kiếm Trên Mảng Đã Có Thứ Tự Bài toán: tìm x trong một mảng.

Điều kiện: mảng đã được sắp thứ tự tăng (giảm) dần

Các hàm tìm kiếm sẽ trả về -1 nếu x không xuất hiện trong dãy, ngược lại, các hàm tìm kiếm sẽ trả về chỉ số của x trong dãy.

Tìm kiếm theo phương pháp lặp

Do dãy số đã có thứ tự tăng dần, nên nếu tìm được phần tử đầu tiên có giá trị lớn hơn x thì có thể kết luận dãy số không chứa phần tử x. Vì vậy, chúng ta có thể rút ngắn thời gian tìm kiếm.

Cài đặt thuật toán

int search(int a[], int n, int x) { int i = 0; while(i < n && a[i] < x) i++; return (i<n && a[i]==x ? i : -1); } void main(void) { int x, pos, size; int a[]; // nhap day so va sap xep day so (tang/giam) dan size = enter_array(a); quicksort(a,size); // nhap gia tri can tim scanf(“%d”, &x); pos = search(a, size, x); if(pos != -1) printf(“%d has been found at %d\n”, x, pos); else printf(“%d is not in array\n”, x); }

Page 16: Thuật Toán Trên cấu Trúc cây

C programming. 2003 - 2005 31

Tìm Kiếm Nhị Phân

1. Phạm vi tìm kiếm ban đầu là toàn bộ danh sách

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 với x.

• Nếu a[j] = x. trả về chỉ số j. STOP.

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

• Nếu a[j] < x. Phạm vi tìm kiếm mới là các phần tử có chỉ số lớn hơn j.

3. Nếu còn tồn tại phạm vi tìm kiếm thì lặp lại bước 2, ngược lại, không tìm thấy x. STOP.

Cài đặt thuật toán

int Binary_Search(int a[], int n, int x) { // gia su ban dau chua tim duoc unsigned char found=FALSE; // Pham vi tìm kiem ban dau tu k=0 den m = n-1 int k=0; int m=n-1; int j; while (k<=m && !found) { j=(k+m) /2; // chi so phan tu giua if (a[j]==x) found=TRUE; else if (x>a[j]) k=j+1; //Pham vi tim moi la (j+1, m) else m=j-1; // Pham vi tim moi la (k, j-1) } return (found ? j : -1) ; }

C programming. 2003 - 2005 32

Tìm Kiếm Nhị Phân bằng Đệ Qui

1. Phạm vi tìm kiếm ban đầu là toàn bộ danh sách k=0 đến m=n-1.

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 với x.

• Nếu a[j] = x. trả về chỉ số j. STOP.

• 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 là (k, j-1)

• Nếu a[j] < x. Phạm vi tìm kiếm mới là các phần tử có chỉ 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

int Binary_Search2(int a[], int k,int m, int x) { int j=(k+m) /2; if (k>m) return -1 ; else if (a[j]==x) return j ; else Binary_Search2(a, (a[j]<x ? j+1:k), (a[j] > x ?j-1:m),x); }

Page 17: Thuật Toán Trên cấu Trúc cây

C programming. 2003 - 2005 33

Các Thuật Toán Trên Cấu Trúc Cây

Cây là một cấu trúc dữ liệu rất thông dụng và quan trọng trong nhiều phạm vi khác nhau của kỹ thuật máy tính.

Ví dụ: Tổ chức các quan hệ họ hàng trong một gia phả, mục lục của một cuốn sách, xây dựng cấu trúc cú pháp trong các trình biên dịch.

Khái niệm Cây là tập hợp các phần tử gọi là nút, (một nút có thể có kiểu bất kỳ) và tập các cạnh có định hướng kết nối các cặp nút trong cây.

Nút gốc (Root): là nút ở “trên cùng” trong một cây. Trên nút gốc không có nút nào nữa.

Nút con (child): nút kế tiếp (phía dưới) của một nút trong cây. Một nút có thể có nhiều nút con, các nút con này được nhìn theo thứ tự từ trái sang phải. Nút con tận cùng bên trái là nút đầu tiên và nút con tận cùng bên phải là nút con cuối cùng.

Nút cha (parent): nút liền kề (phía trên) của một nút trong cây. Một nút chỉ có duy nhất một nút cha.

Các nút anh em (siblings): các nút con của cùng một nút.

Các cạnh/nhánh (edge/branch): đường nối từ nút cha đến các nút con của nó.

Nút tổ tiên (Ancestors): Các nút tổ tiên của một nút bao gồm nút cha của nút, nút cha của nút cha, v.v đến trên cùng là nút gốc.

Nút hậu duệ (Descendant): Các nút hậu duệ của một nút bao gồm các nút con của nút, các nút con của nút con, v.v đến các nút lá của các nhánh thuộc nút.

Đường đi (Path): là chuỗi các cạnh nối từ một nút đến một trong số các nút hậu duệ của nó.

Chiều dài đường đi (Path length): số cạnh trong đường đi.

Nút lá (Leaf): Nút không có nút con.

Nút trung gian (Interior node): nút có ít nhất một nút con. C programming. 2003 - 2005 34

Độ sâu hay mức của một nút (Depth/level): được tính bằng chiều dài đường đi từ nút gốc đến nút đang xét.

Chiều cao của cây (height): chiều dài đường đi dài nhất trong cây.

Cây con (subtree): cây bao gồm nút và tất cả các nút hậu duệ của nó. Nút gốc và toàn bộ cây không được xem là cây con.

Ví dụ:

Số nút 11 Chiều cao của cây 5 Nút gốc (root) R Nút lá (leaves) B,C,D,K,M,N Nút trong D, H, I, R, L Nút ở mức 2 H,K,M,N Nút tổ tiên (Ancestors) của H I, R Nút hậu duệ (Decesdants) của H D, E, B, C, D Nút ở cây con nhánh trái của I H,E,B,C,D

Định nghĩa cây theo đệ quy: - Một nút đơn cũng chính là một cây.

- Các nút được gọi là ở cùng một cây khi có đường đi giữa các nút này.

- Một cây sẽ bao gồm một nút gốc (Root) và m cây con, trong mỗi cây con lại có một nút gốc và m1 cây con nhỏ hơn.

- Một cây không có một nút nào cả gọi là cây rỗng.

I

H K

R

L

M N

E

D B C

Page 18: Thuật Toán Trên cấu Trúc cây

C programming. 2003 - 2005 35

Cây Nhị Phân

Trong cây nhị phân, mỗi nút có tối đa hai nút con: nút con nhánh trái và nút con nhánh phải.

Khi một nút chỉ có một nút con, cần phải phân biệt là nút con bên nhánh trái, hay nút con bên nhánh phải, chứ không chỉ đơn thuần gọi là nút con.

Cây nhị phân

C programming. 2003 - 2005 36

Các cây nhị phân đặc biệt

Cây nhị phân đúng Một cây nhị phân được gọi là cây nhị phân đúng nếu nút gốc và tất cả các nút trung gian đều có 2 nút con.

Ghi chú: nếu cây nhị phân đúng có n nút lá thì cây này sẽ có tất cả 2n-1 nút.

Cây nhị phân đầy Một cây nhị phân được gọi là đầy với chiều cao d thì

- nó phải là cây nhị phân đúng

- tất cả các nút lá đều có mức d

Ghi chú: cây nhị phân đầy là cây nhị phân có số nút tối đa ở mỗi mức.

Page 19: Thuật Toán Trên cấu Trúc cây

C programming. 2003 - 2005 37

Cây nhị phân tìm kiếm (Binary search tree) Một cây nhị phân được gọi là cây nhị phân tìm kiếm nếu và chỉ nếu đối với mọi nút của cây thì khóa của một nút bất kỳ phải lớn hơn khóa của tất cả các nút trong cây con bên trái của nó và phải nhỏ hơn khóa của tất cả các nút trong cây con bên phải của nó.

Cây nhị phân cân bằng (AVL tree): Một cây nhị phân được gọi là cây nhị phân cân bằng nếu và chỉ nếu đối với mọi nút của cây thì chiều cao của cây con bên trái và chiều cao của cây con bên phải hơn kém nhau nhiều nhất là 1. (Theo Adelson Velski và Landis).

C programming. 2003 - 2005 38

Cây nhị phân cân bằng hoàn toàn Một cây nhị phân được gọi là cân bằng hoàn toàn nếu và chỉ nếu đối với mọi nút của cây thì số nút của cây con bên trái và số nút của cây con bên phải hon kém nhau nhiều nhất là 1.

Page 20: Thuật Toán Trên cấu Trúc cây

C programming. 2003 - 2005 39

Các phép duyệt cây nhị phân (Traverse) Là quá trình đi qua các nút đúng một lần.

Preorder (NLR) Duyệt qua nút gốc trước, sau đó qua cây con bên trái, dùng preorder cho cây con bên trái. Cuối cùng qua cây con bên phải và dùng preorder cho cây con bên phải.

If the node is NULL Return Else Visit the item in the node to do something Traverse (Preorder) the node’s left subtree Traverse (Preorder) the node’s right subtree

Ví dụ trong hình: 1 2 3 4 6 7 5 8 9

C programming. 2003 - 2005 40

Inorder (LNR) Qua cây con bên trái duyệt trước (theo thứ tự LNR). Sau đó duyệt qua nút gốc. Cuối cùng duyệt qua cây con bên phải (theo thứ tự LNR).

If the node is NULL Return Else Traverse the node’s left subtree (LNR) Visit the item in the node to do something Traverse the node’s right subtree (LNR) Ví dụ trong hình: 2 1 6 4 7 3 8 5 9

Postorder (LRN) Qua cây con bên trái duyệt trước (theo thứ tự LRN). Sau đó duyệt qua cây con bên phải (theo thứ tự LRN). Cuối cùng duyệt qua nút gốc.

If the node is NULL Return Else Traverse the node’s left subtree (LRN) Traverse the node’s right subtree (LRN) Visit the item in the node to do something

Ví dụ trong hình: 2 6 7 4 8 9 5 3 1

Page 21: Thuật Toán Trên cấu Trúc cây

C programming. 2003 - 2005 41

Biểu diễn cây Sử dụng khái niệm liên kết để biểu diễn cây.

Cây tổng quát: Mỗi nút có:

- dữ liệu của nút

- các con trỏ chỉ tới các nút con của nó.

Các nút có thể có số nút con khác nhau.

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ẽ 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ó.

Thay vào đó, chúng ta có thể quản lý các nút con của một nút bằng một danh sách liên kết.

a

b c d e

f g h

a

b c d e

f g h

C programming. 2003 - 2005 42

struct nodetype { int key; int info; struct nodetype* firstchild; struct nodetype* nextsiblings; }; typedef struc nodetype* NODEPTR; NODEPTR tree;

Cây nhị phân

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 phải của nó.

struct nodetype { int key; int info; struct nodetype* left; struct nodetype* right; }; typedef struc nodetype* NODEPTR; NODEPTR tree;

a

b c

g

a

b c

g

Page 22: Thuật Toán Trên cấu Trúc cây

C programming. 2003 - 2005 43

Các phép toán trên cây nhị phân

Tạo cây

Khởi tạo (inititalize) Khởi động cây nhị phân, cho chương trình biết hiện tại cây nhị phân rỗng.

void Inititialize (NODEPTR *root) { *root = NULL; }

Gọi hàm: Initialize (&tree);

Cấp phát vùng nhớ (New_Node) 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.

NODEPTR New_Node(void) { NODEPTR p; p = (NODEPTR)malloc(sizeof(struct nodetype)); return(p); }

Gọi hàm: p = New_Node();

C programming. 2003 - 2005 44

Tạo cây BST (Create_Tree) void Insert(NODEPTR root, int x, int a) { NODEPTR p; if(x == root->key) // key duplicated, STOP { printf("bi trung khoa, khong them nut nay duoc"); return; } // Stop condition if(x < root->info && root->left == NULL) { p = New_Node(); p->key =x; p->info = a; p->left = NULL; p->right = NULL; root->left=p; return; } // stop condition if(x > root->info && root->right == NULL) { p = New_Node(); p->key =x; p->info = a; p->left = NULL; p->right = NULL; root->right=p ; return; } if(x < root->info) // recursion step Insert(root->left, x,a); // recursion on left else Insert(root->right, x,a); // recursion on right } void Create_Tree(NODEPTR &root) { int khoa, noidung; char so[10];

Page 23: Thuật Toán Trên cấu Trúc cây

C programming. 2003 - 2005 45

NODEPTR p; do { printf("Nhap khoa :"); gets(so) ; khoa = atoi(so); if (khoa !=0) { printf("Nhap noi dung :"); gets(so) ; noidung = atoi(so); if (root==NULL) { p = New_Node(); p->key = khoa; p->info = noidung; p->left = NULL; p->right = NULL; root =p; } else Insert(root,khoa,noidung); } } while (khoa!=0); // Stop entering when key=0 }

Để tạo cây nhị phân do biến tree quản lý, ta gọi: Create_Tree (&tree);

Cập nhật cây

Giải phóng vùng nhớ (Free_Node) void Free_Node(NODEPTR p) { free(p); } Gọi hàm: Free_Node (p);

C programming. 2003 - 2005 46

Kiểm tra cây nhị phân rỗng (Empty) int Empty(NODEPTR root) {return(root == NULL ? TRUE : FALSE);}

Gọi hàm: Empty(&tree);

Hủy một nút trong cây BST (Remove) Xóa nút có địa chỉ p trong BST sao cho sau khi xóa, cây vẫn là BST. Có 3 trường hợp:

Trường hợp 1: nút p cần xóa là nút lá. Việc xóa nút chỉ đơn giản là gỡ nút ra khỏi cây và hủy nó đi.

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 của p đến rp. Cuối cùng, hủy p.

Page 24: Thuật Toán Trên cấu Trúc cây

C programming. 2003 - 2005 47

Trường hợp 3: Nút p có hai cây con. Do tính chất nút cực trái của cây con bên nhánh phải của p có khóa lớn hơn khóa của p, nên để loại p thì chọn nút cực trái làm nút thay thế (rp) cho vị trí của p. Sau đó, hủy p.

Hàm Remove xóa nút p, trả về con trỏ chỉ tới nút thay thế (rp).

NODEPTR Remove(NODEPTR p) { NODEPTR rp, f; if(p == NULL) printf(“p is not real. Cannot delete!\n”); else

C programming. 2003 - 2005 48

{ if(p->right == NULL) // no right subtree rp = p->left; else { if(p->left == NULL) // no left subtree rp = p->right; else // node has left and right subtrees { /* search for p’s replacement: the most left node of right subtree*/ /* f is parent of rp */ f = p; rp = p->right; while(rp->left != NULL) { f = rp; rp = rp->left; } if(f != p) { f->left = rp->right; rp->right = p->right; } rp->left = p->left; } } Free_Node(p); return (rp); } }

Gọi hàm: Remove(p);

Do hàm Remove trả về địa chỉ của nút thay thế nên nếu dùng 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. tree = Remove(tree);

Page 25: Thuật Toán Trên cấu Trúc cây

C programming. 2003 - 2005 49

Tìm kiếm (Search) 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.

Tìm kiếm bằng phương pháp nhị phân

NODEPTR Search(NODEPTR root, int x) { NODEPTR p; p = root; while(p != NULL && x != p->key) if(x < p->key) p = p->left; else p = p->right; return (p); }

Gọi hàm: p = Search(tree, x);

C programming. 2003 - 2005 50

Duyệt cây

Duyệt theo thứ tự NLR (Preorder) void Preorder (NODEPTR root) { if(root != NULL) { printf("%d ", root->info); Preorder(root->left); Preorder (root->right); } }

Duyệt theo thứ tự LNR (Inorder) void Inorder(NODEPTR root) { if(root != NULL) { Inorder(root->left); printf("%d ", root->info); Inorder(root->right); } }

Duyệt theo thứ tự LRN (Postorder) void Posorder(NODEPTR root) { if(root != NULL) { Posorder(root->left); Posorder(root->right); printf("%d ", root->info); } }

Page 26: Thuật Toán Trên cấu Trúc cây

C programming. 2003 - 2005 51

Cây Nhị Phân Tìm Kiếm Cân Bằng (AVL tree)

Tạo cây BST với mục đích là tìm nút nhanh. Tuy nhiên, nếu cây có nhánh dài và không cân bằng thì việc tìm kiếm một nút trở thành phép tìm tuần tự.

Để đả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

7

5

8

9

1 3

4

2

7

5

1 3

6

C programming. 2003 - 2005 52

Với cây AVL, việc thêm hay bỏ 1 nút trên cây có thể làm cây mất cân bằng. Khi đó ta phải cân bằng lại cây. Tuy nhiên việc cân bằng lại cây chỉ thực hiện ở phạm vi cục bộ tại nút mất cân bằng bằng cách xoay trái hoặc phải ở một vài nhánh cây con nên giảm thiểu chi phí cân bằng.

Định nghĩa: Chỉ số cân bằng (balance factor) của một nút p trên cây AVL:

bf(p) = lh(p) – rh(p)

lh(p): chiều cao của nhánh cây con trái của p

rh(p): chiều cao của nhánh cây con phải của p.

Các trường hợp:

bf(p) = 0 nếu lh(p) = rh(p); nút p cân bằng.

bf(p) = 1 nếu lh(p) = rh(p) + 1; nút p bị lệch trái.

bf(p) = -1 nếu lh(p) = rh(p) – 1; nút p bị lệch phải.

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 nút trước khi thêm vào nút mới

Page 27: Thuật Toán Trên cấu Trúc cây

C programming. 2003 - 2005 53

Các phép toán trên cây AVL Khai báo kiểu dữ liệu struct nodetype { int key; int info; int bf; struct nodetype* left; struct nodetyp* right; }; typedef struct nodetype* NODEPTR;

Thêm nút Thêm nút có khoá x, nội dung a vào AVL sao cho sau khi thêm, cây nhị phân vẫn là AVL.

Giải thuật:

- Thêm nút vào cây như bình thường. Nút thêm vào sẽ là nút lá.

- Tính lại chỉ số cân bằng của các nút bị ảnh hưởng.

- Kiểm tra cây có bị mất cân bằng? Nếu cây bị mất cân bằng thì cân bằng lại cây.

Các trường hợp làm cây mất cân bằng

C programming. 2003 - 2005 54

Trong hình trên

- Nếu thêm nút vào 1 trong 6 vị trí B trên cây thì cây vẫn cân bằng.

- Nếu thêm nút vào 1 trong các vị trí U1 U12 trên cây thì cây sẽ mất cân bằng.

o Thêm nút vào sau bên trái của nút A(bfA = 1) tại các vị trí U1 U4 thì cây sẽ mất cân bằng vì nút A đang bị lệch trái.

o Thêm nút vào sau bên phải nút C (bfC = -1) tại các vị trí U9 U12 thì cây sẽ mất cân bằng vì nút C đang bị lệch phải.

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 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 sau bên phải nút có bf = -1.

Cân bằng lại cây Gọi ya là nút trước gần nhất bị mất cân bằng khi thêm nút khóa x vào cây AVL. Không mất tính tổng quát, chỉ cần xét trường hợp bfya = 1 và nút lá thêm vào là nút sau bên phải của nút ya.

Page 28: Thuật Toán Trên cấu Trúc cây

C programming. 2003 - 2005 55

Nhận xét:

- Vì nút ya có có bfya = 1 nên nút ya chắc chắn có nút con bên trái s với bfs = 0.

- Vì nút ya là nút gần nhất có bf là 1 nên nút s và các nút trước khác của nút x (sẽ thêm vào) có bf là 0.

- Độ cao: h(T1) = h(T2) = h(T3).

Trường hợp 1a: Nếu thêm nút mới khóa x vào vị trí nút sau bên trái của s (thuộc nhánh T1) thì phải xoay phải quanh nút ya.

- Nút s sẽ là nút gốc mới của nhánh cây này với bfs = 0.

- Nút ya sẽ là nút con bên phải của s với bfya = 0.

Trường hợp 1b: Nếu thêm nút mới khóa x vào vị trí nút sau bên phải của s (thuộc nhánh T2) thì phải xoay 2 lần (xoay kép): xoay trái quanh nút s và xoay phải quanh nút ya.

- Nút p sẽ là nút gốc mới của nhánh cây này với bfp = 0.

- Nút ya sẽ là nút con bên phải của p với bfya = -1.

- Nút s là nút con bên trái của p với bfs = 0.

C programming. 2003 - 2005 56

Page 29: Thuật Toán Trên cấu Trúc cây

C programming. 2003 - 2005 57

Bảng phân biệt các trường hợp cây bị mất cân bằng khi thêm nút và các phép xoay tương ứng để cân bằng lại cây.

Cài đặt giải thuật:

Phép xoay trái (Rotate_Left): xoay trái cây 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 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 của nút gốc mới.

Hàm xoay trái trả về con trỏ chỉ tới nút gốc mới.

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 58

p = root->right; root->right = p->left; p->left = root; } 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.

NODEPTR Rotate_Right(NODEPTR root) { NODEPTR p; if(root == NULL) printf("Khong the xoay phai vi cay bi rong."); else if(root->left == NULL) printf("Khong the xoay phai vi khong \ co nut con ben trai."); else { p = root->left; root->left = p->right; p->right = root; } return p; }

Page 30: Thuật Toán Trên cấu Trúc cây

C programming. 2003 - 2005 59

Thêm nút (Insert) Thêm nút khóa x, nội dung a vào cây AVL

- Thêm nút theo giải thuật thêm nút vào cây nhị phân tìm kiếm.

- Cân bằng lại cây bằng cách xoay đơn hay xoay kép.

void Insert(NODEPTR &pavltree, int x, int a) { NODEPTR fp, p, q, // fp: p’s father, q: p’s child fya, ya, /* ya: the closest node that is possible to be imbalanced */ /* fya: ya’s father */ s; // s: ya’s child in the imbalanced branch int imbal; /* imbal = 1 if left imbalanced -1 if right imbalanced */ // initializing fp = NULL; p = pavltree; fya = NULL; ya = p; /* search for fp, ya and fya, inserted node will be fp’s child */ while(p != NULL) { if(x == p->info) // duplicated -> STOP return; if (x < p->info) q = p->left; else q = p->right; if(q != NULL) if(q->bf != 0) { fya = p; ya = q; } fp = p; p = q; } // Insert new node q = New_Node();

C programming. 2003 - 2005 60

q->key =x; q->info = a; q->bf = 0; q->left = NULL; q->right = NULL; if(x < fp->info) fp->left = q; else fp->right = q; /* Adjust the balance factor for nodes from ya to q. If left imbalanced, bf of those nodes would be 1. If right imbalanced, bf of those nodes would be –1.*/ if(x < ya->info) p = ya->left; else p = ya->right; s = p; // s is a child of ya while(p != q) { if(x < p->info) { p->bf = 1; p = p->left; } else { p->bf = -1; p = p->right; } } // where to go? if(x < ya->info) imbal = 1; else imbal = -1; if(ya->bf == 0) { ya->bf = imbal; return; } if(ya->bf != imbal)

Page 31: Thuật Toán Trên cấu Trúc cây

C programming. 2003 - 2005 61

{ ya->bf = 0; return; } if(s->bf == imbal) // single rotation { if(imbal == 1) // right rotating p = Rotate_Right(ya); else // left rotating p = Rotate_Left(ya); ya->bf = 0; s->bf = 0; } else // double rotation { if(imbal == 1) // right-right double rotation { ya->left = Rotate_Left(s); p = Rotate_Right(ya); } else // right-left double rotation { ya->right = Rotate_Right(s); p = Rotate_Left(ya); } if(p->bf == 0) // p is the new node { ya->bf = 0; s->bf = 0; } else if(p->bf == imbal) { ya->bf = -imbal; s->bf = 0; } else { ya->bf = 0; s->bf = imbal; } p->bf = 0; } if(fya == NULL) pavltree = p;

C programming. 2003 - 2005 62

else if(ya == fya->right) fya->right = p; else fya->left = p; } Để tạo cây AVL, ta dùng giải thuật sau. void Create_AVLTree(NODEPTR &root) { int khoa, noidung; char so[10]; NODEPTR p; do { printf("Nhap khoa :"); gets(so) ; khoa = atoi(so); if (khoa !=0) { printf("Nhap noi dung :"); gets(so) ; noidung = atoi(so); if (root==NULL) { p = New_Node(); p->key = khoa; p->info = noidung; p->bf = 0 ; p->left = NULL; p->right = NULL; root =p; } else Insert(root,khoa,noidung); } } while (khoa!=0); // STOP }

Để 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à: Create_AVLTree(&tree);

Page 32: Thuật Toán Trên cấu Trúc cây

C programming. 2003 - 2005 63

Cập nhật cây

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ả 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 thì trả về giá trị NULL.

Cây AVL là một BST nên ta tìm kiếm nhanh bằng phương pháp tìm 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 cây BST thông thường.

NODEPTR search(NODEPTR root, int x) { NODEPTR p; p = root; while(p != NULL && x!=p->key) if(x < p->key) p = p->left; else p = p->right; return(p); }

Gọi hàm: Search(tree, x);

Xóa nút (Remove)

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 64

+ gọi đệ quy để xóa nút x ở nhánh trái của root:

Remove(root->left, x);

+ Gọi balance_left để cân bằng lại cây có nút gốc root

nếu nhánh cây con bên trái bị giảm độ cao.

Nếu (x > root->info)

+ gọi đệ quy để xóa nút x ở nhánh phải của root:

Remove(root->right, x);

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

Nếu (x == root->info)

Xóa nút root như phép toán xóa trên cây BST.

Cài đặt:

(Tự cài đặt)

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 duyệt Preorder, Inorder và Postorder vào cây AVL.