Tải bản đầy đủ (.pdf) (32 trang)

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

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

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
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
N
2

2
N


Đô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:
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à n
0
, sao cho với mọi N > n
0
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 * N
2
+ 5.
Dễ thấy rằng, với c = 4 và n
0
= 2, ta có:

3 * N
2
+ 5 ≤ 4 * N
2

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



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)
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);
}

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+next
2
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;
}
}
Hea
d


Size
1 null 2 null ? 5 null null
3 null
4 null null
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





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


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)



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


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 O
A 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;
}









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

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

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

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,








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)




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
TX
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[i
0
]
• quét mảng từ bên phải sang để tìm phần tử nhỏ hơn a[i
0

],
• quét mảng từ bên trái sang để tìm phần tử lớn hơn a[i
0
].
• 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( ; ; )
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.

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);
}
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);
}











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



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.






















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









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


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);


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);
}
}


×