BG Cau Truc Du Lieu Va Giai Thuat - Tiến Sĩ Ngô Hữu Phúc

You might also like

Download as pdf or txt
Download as pdf or txt
You are on page 1of 529

Cấu trúc dữ liệu và giải thuật

Bài 1. Giới thiệu chung

Lecturer: Dr. Ngo Huu Phuc


Tel: 0438 326 077
Mob: 098 5696 580
Email: ngohuuphuc76@gmail.com

1 @Copyrights by Dr. Ngo Huu Phuc, Le Quy Don Technical University


Tài liệu tham khảo
 Mastering Algorithms with C, Kyle Loudon, 1999.

 Introduction to Algorithms, Thomas H. Cormen, Charles

E. Leiserson, Ronald L. Rivest and Clifford Stein, The MIT


Press © 2001.

 Data Structures, Algorithms, and Object-Oriented

Programming. NXB McGraw Hill; Tác giả Gregory


Heilleman -1996

 Cấu trúc dữ liệu và giải thuật, Đỗ Xuân Lôi.

2 @Copyrights by Dr. Ngo Huu Phuc, Le Quy Don Technical University


Bài 1. Giới thiệu
Nội dung:
1.0. Đôi nét về khái niệm.

1.1. Giải thuật.

1.2. Dữ liệu và các cấu trúc dữ liệu.

1.3. Biểu diễn giải thuật.

1.4. Độ phức tạp của giải thuật.

Tham khảo:
Elliz Horowitz - Fundamentals of data structures,

Chapter 1: Introduction

3 @Copyrights by Dr. Ngo Huu Phuc, Le Quy Don Technical University


1.0. Đôi nét về khái niệm
 Để giải một bài toán, bắt đầu từ câu hỏi “phải làm gì?”, sau

đó trả lời câu hỏi “làm như thế nào?” → đó là cách tiếp cận
đến giải thuật và cấu trúc dữ liệu.

 Các bài toán trong thực tế không dễ giải bằng cách hiểu

thông thường và để giảm độ phức tạp, trong nhiều trường


hợp có thể mô hình hóa bài toán.

 Từ việc mô hình hóa, trong thực tế thấy rằng có nhiều bài

toán có cùng một mô hình hóa.

4 @Copyrights by Dr. Ngo Huu Phuc, Le Quy Don Technical University


1.0.1. Một số ví dụ (1)
Ví dụ 1: Tô màu bản đồ thế giới.

Yêu cầu:

 Ta c ần phải tô màu cho các nước trên bản đồ thế giới.

 Trong đó mỗi nước đều được tô một màu.

 Hai nước láng giềng (cùng biên giới) thì phải được tô bằng

hai màu khác nhau.

 Hãy tìm một phương án tô màu sao cho số màu sử dụng là

ít nhất.

5 @Copyrights by Dr. Ngo Huu Phuc, Le Quy Don Technical University


1.0.2. Một số ví dụ (2)
Hướng giải quyết bằng mô hình hóa:
 Ta có th ể xem mỗi nước trên bản đồ thế giới là một đỉnh của đồ thị.

 Hai nước láng giềng của nhau thì hai đỉnh ứng với nó được nối với

nhau bằng một cạnh.

Bài toán lúc này trở thành bài toán tô màu cho đồ thị như sau:
 Mỗi đỉnh đều phải được tô màu.

 Hai đỉnh có cạnh nối thì phải tô bằng hai màu khác nhau.

 Cần tìm một phương án tô màu sao cho số màu được sử dụng là ít

nhất.

6 @Copyrights by Dr. Ngo Huu Phuc, Le Quy Don Technical University


1.0.3. Một số ví dụ (3)
Ví dụ 2: Đèn giao thông

 Cho một ngã năm như hình 1.

 C và E là các đường một chiều theo chiều mũi tên.

 Các đường khác là hai chiều.

 Hãy thiết kế một bảng đèn hiệu điều khiển giao thông tại ngã năm này

một cách hợp lý: sao cho:

 Phân chia các lối đi tại ngã năm này thành các nhóm

 Mỗi nhóm gồm các lối đi có thể cùng đi đồng thời nhưng không xảy ra tai nạn

giao thông (các hướng đi không cắt nhau),

 Và số lượng nhóm là ít nhất có thể được.

7 @Copyrights by Dr. Ngo Huu Phuc, Le Quy Don Technical University


1.0.4. Hướng giải quyết (VD2)
 Ta có thể xem đầu vào (input) của bài toán là tất cả các lối

đi tại ngã năm này.

 Đầu ra (output) của bài toán là các nhóm lối đi có thể đi

đồng thời mà không xảy ra tai nạn giao thông.

 Mỗi nhóm sẽ tương ứng với một pha điều khiển của đèn

hiệu, vì vậy ta phải tìm kiếm lời giải với số nhóm là ít nhất
để giao thông không bị tắc nghẽn vì phải chờ đợi quá lâu.

8 @Copyrights by Dr. Ngo Huu Phuc, Le Quy Don Technical University


1.0.4. Hướng giải quyết (VD2) (t)
 Trước hết ta nhận thấy rằng tại ngã năm này có 13 lối đi: AB, AC, AD,

BA, BC, BD, DA, DB, DC, EA, EB, EC, ED.

 Thể hiện các lối có thể đi đồng thời.

 Ví dụ cặp AB và EC có thể đi đồng thời, nhưng AD và EB thì không, vì

các hướng giao thông cắt nhau.

 Sử dụng sơ đồ trực quan:

 Tên của 13 lối đi được viết lên mặt phẳng,

 Hai lối đi nào nếu đi đồng thời sẽ xảy ra đụng nhau (tức là hai hướng đi cắt
qua nhau) ta nối lại bằng một đoạn thẳng.
 Ta sẽ có một sơ đồ như hình 2. Như vậy, trên sơ đồ này, hai lối đi có cạnh nối
lại với nhau là hai lối đi không thể cho đi đồng thời.

9 @Copyrights by Dr. Ngo Huu Phuc, Le Quy Don Technical University


Hình 2

10 @Copyrights by Dr. Ngo Huu Phuc, Le Quy Don Technical University


1.0.4. Hướng giải quyết (VD2) (t)
 Với cách biểu diễn như vậy ta đã có một đồ thị (Graph), tức là ta đã mô

hình hoá bài toán giao thông ở trên theo mô hình toán là đồ thị.

 Mỗi lối đi trở thành một đỉnh của đồ thị, hai lối đi không thể cùng đi đồng

thời được nối nhau bằng một đoạn ta gọi là cạnh của đồ thị.

 Bây giờ ta phải xác định các nhóm, với số nhóm ít nhất, mỗi nhóm gồm

các lối đi có thể đi đồng thời, nó ứng với một pha của đèn hiệu điều
khiển giao thông.

 Giả sử rằng, ta dùng màu để tô lên các đỉnh của đồ thị này sao cho:

 Các lối đi cho phép cùng đi đồng thời sẽ có cùng một màu: Dễ dàng nhận

thấy rằng hai đỉnh có cạnh nối nhau sẽ không được tô cùng màu.

 Số nhóm là ít nhất: ta phải tính toán sao cho số màu được dùng là ít nhất.

11 @Copyrights by Dr. Ngo Huu Phuc, Le Quy Don Technical University


1.0.4. Hướng giải quyết (VD2) (t)
 "Tô màu cho đồ thị ở hình 2 sao cho:

 Hai đỉnh có cạnh nối với nhau (hai còn gọi là hai đỉnh kề nhau) không

cùng màu.

 Số màu được dùng là ít nhất."

Nhận xét:

 Hai bài toán thực tế “tô màu bản đồ thế giới” và “đèn giao

thông” xem ra rất khác biệt nhau nhưng sau khi mô hình
hóa, chúng thực chất chỉ là một, đó là bài toán “tô màu đồ
thị”.

12 @Copyrights by Dr. Ngo Huu Phuc, Le Quy Don Technical University


1.0.5. Thiết kế giải thuật để giải bài toán “ tô màu đồ thị”
 Bài toán “tô màu đồ thị không có giải thuật tối ưu.
 Để có kết quả tối ưu, cần sử dụng phương pháp vét cạn.
Phương pháp này tốt khi số phép thử là nhỏ.
 Thông thường, để đơn giản hóa, có thể:
 Thêm thông tin vào bài toán để đồ thị có một số tính chất đặc biệt và
dùng các tính chất đặc biệt này ta có thể dễ dàng tìm lời giải, hoặc
 Thay đổi yêu cầu bài toán một ít cho dễ giải quyết, nhưng lời giải tìm
được chưa chắc là lời giải tối ưu.

 Cách này được xem là "Cố gắng tô màu cho đồ thị bằng ít
màu nhất một cách nhanh chóng".
 Một giải pháp như thế gọi là một HEURISTIC.

13 @Copyrights by Dr. Ngo Huu Phuc, Le Quy Don Technical University


1.0.6. Giải thuật HEURISTIC

 HEURISTIC cho bài toán tô màu đồ thị, thường gọi là giải

thuật "háu ăn" (GREEDY) là:


 Chọn một đỉnh chưa tô màu và tô nó bằng một màu mới C nào đó.
 Duyệt danh sách các đỉnh chưa tô màu. Đối với một đỉnh chưa tô
màu, xác định xem nó có kề với một đỉnh nào được tô bằng màu C đó
không. Nếu không có, tô nó bằng màu C đó.

 Ý tưởng của Heuristic này là hết sức đơn giản: dùng một

màu để tô cho nhiều đỉnh nhất có thể được (các đỉnh được
xét theo một thứ tự nào đó), khi không thể tô được nữa với
màu đang dùng thì dùng một màu khác. Như vậy ta có thể
"hi vọng" là số màu cần dùng sẽ ít nhất
14 @Copyrights by Dr. Ngo Huu Phuc, Le Quy Don Technical University
Ví dụ về HEURISTIC

Ví dụ: Tô mầu đồ thị hình 3

Tô theo GREEDY
(xét lần lượt theo số thứ tự các đỉnh)
1: đỏ; 2: đỏ; 3: xanh;4: xanh; 5: vàng

Tối ưu
(thử tất cả các khả năng)
1,3,4 : đỏ; 2,5 : xanh

15 @Copyrights by Dr. Ngo Huu Phuc, Le Quy Don Technical University


Giải bài toán giao thông bằng HEURISTIC
Trở lại bài toán giao thông ở trên và áp dụng HEURISTIC
Greedy cho đồ thị trong hình 2 (theo thứ tự các đỉnh đã liệt
kê ở trên), ta có kết quả:
 Tô màu xanh cho các đỉnh: AB,AC,AD,BA,DC,ED
 Tô màu đỏ cho các đỉnh: BC,BD,EA
 Tô màu tím cho các đỉnh: DA,DB
 Tô màu vàng cho các đỉnh: EB,EC

Chú ý: 4 màu là 1 lời giải, nhưng chưa kết luận được có tối
ưu hay không.

16 @Copyrights by Dr. Ngo Huu Phuc, Le Quy Don Technical University


Chứng minh về số mầu cần thiết để tô là 4?
 Định lý:
Một đồ thị trong đó có k đỉnh mà mỗi cặp đỉnh bất kỳ trong k
đỉnh này đều được nối nhau thì không thể dùng ít hơn k
màu để tô cho đồ thị.
 Chứng minh:
Đồ thị trong hình I.2 có 4 đỉnh: AC,DA,BD,EB mà mỗi cặp đỉnh
bất kỳ đều được nối nhau vậy đồ thị hình I.2 không thể tô
với ít hơn 4 màu. Điều này khẳng định rằng lời giải vừa tìm
được ở trên trùng với lời giải tối ưu.

17 @Copyrights by Dr. Ngo Huu Phuc, Le Quy Don Technical University


1.1. Các giải thuật
1.1.1. Thế nào là giải thuật.
1.1.2. Ví dụ về giải thuật: Sắp xếp một dãy.
1.1.3. Định nghĩa giải thuật.

18 @Copyrights by Dr. Ngo Huu Phuc, Le Quy Don Technical University


1.1.1. Thế nào là giải thuật?
 Giải thuật là các ý tưởng đằng sau chương trình trên máy tính.

 Giải thuật không thay đổi khi viết trên các ngôn ngữ khác nhau.

 Giải thuật phải giải quyết được các vấn đề tổng quát cũng như
các vấn đề riêng của một bài toán.

 Giải thuật cần có đầu vào và đầu ra rõ ràng.

19 @Copyrights by Dr. Ngo Huu Phuc, Le Quy Don Technical University


1.1.2. Ví dụ về giải thuật: Sorting
 Input: Một dãy số gồm N phần tử.
a[1], a[2], … , a[n].
 Output: Sắp xếp lại dãy số trên
dãy số trên có dạng:
a[1] <= a[2] <= … <= a[n] .
• Cần tìm giải thuật chính xác và hiệu quả.

20 @Copyrights by Dr. Ngo Huu Phuc, Le Quy Don Technical University


1.1.3. Nghiên cứu giải thuật cần gì?

1. Máy tính để thực hiện giải thuật.

2. Ngôn ngữ lập trình để diễn tả giải thuật.

3. Cơ sở của giải thuật.

4. Phân tích giải thuật.

21 @Copyrights by Dr. Ngo Huu Phuc, Le Quy Don Technical University


1.1.4. Định nghĩa giải thuật.
Knuth (1973) định nghĩa giải thuật là một chuỗi hữu hạn các thao tác để
giải một bài toán nào đó. Các tính chất quan trọng của giải thuật là :
1. Input: Không có hoặc có một số, được đưa từ ngoài vào.
2. Output: Có ít nhất một đầu ra.
3. Xác định (Definiteness): mỗi bước của giải thuật phải được xác
định rõ ràng và phải được thực hiện chính xác, nhất quán.
4. Hữu hạn (Finiteness): giải thuật phải luôn luôn kết thúc sau một số
hữu hạn bước.
5. Hiệu quả (Effectiveness): các thao tác trong giải thuật phải được
thực hiện trong một lượng thời gian hữu hạn.

22 @Copyrights by Dr. Ngo Huu Phuc, Le Quy Don Technical University


1.2. Cấu trúc dữ liệu?
1.2.1. Là ngành khoa học máy tính – nghiên cứu
về dữ liệu.

1.2.2. Các kiểu dữ liệu.

1.2.3. Miền xác định của dữ liệu.

1.2.4. Các cấu trúc dữ liệu.

1.2.5. Sự thực thi dữ liệu.

23 @Copyrights by Dr. Ngo Huu Phuc, Le Quy Don Technical University


1.2.1. Là ngành khoa học máy tính –
nghiên cứu về dữ liệu
1. Máy tính lưu trữ dữ liệu.

2. Ngôn ngữ được sử dụng để thao tác với dữ liệu.

3. Dữ liệu được hiệu chỉnh, diễn tả hợp lý từ dữ liệu


gốc.

4. Cấu trúc để biểu diễn dữ liệu.

24 @Copyrights by Dr. Ngo Huu Phuc, Le Quy Don Technical University


1.2.2. Các kiểu dữ liệu.

 Kiểu dữ liệu là khái niệm về loại của dữ liệu cho

biến trong chương trình.

 Trong ngôn ngữ C kiểu dữ liệu có sẵn như: int,

float, char, double…

 Trong ngôn ngữ FORTRAN kiểu dữ liệu có sẵn

như: INTEGER, REAL, LOGICAL, COMPLEX,


DOUBLE PRECISION…

25 @Copyrights by Dr. Ngo Huu Phuc, Le Quy Don Technical University


1.2.3. Miền xác định của dữ liệu
• Miền xác định của dữ liệu thường ký hiệu D.

• Ví dụ: Với kiểu bool, D = {0, 1}.

• Ví dụ: Với kiểu int , D = {0, 1, 2, ...}.

• Với các ký tự hoặc xâu ký tự, D = {' ','A','B',

...,'Z',”AA”...}.

• Như vậy, D có thể là hữu hạn hoặc vô hạn, cần

xác định kiểu dữ liệu phù hợp với giải thuật.

26 @Copyrights by Dr. Ngo Huu Phuc, Le Quy Don Technical University


1.2.4. Các cấu trúc dữ liệu
 Một cấu trúc dữ liệu là:

 Miền D, xác định rõ,

 Tập các hàm F,

 Tập “tiên đề” X.

 Như vậy, một bộ (D, F, X) xác định cấu trúc dữ

liệu d.

27 @Copyrights by Dr. Ngo Huu Phuc, Le Quy Don Technical University


1.2.5. Sự thực thi dữ liệu.
 Sự thực thi của cấu trúc dữ liệu là ánh xạ cấu trúc dữ liệu

d vào cấu trúc dữ liệu đã có trước đó.

 Ví dụ: số nguyên được biểu diễn bởi chuỗi bít.

 Ví dụ: kiểu boolean được định nghĩa bởi số 0 hoặc 1.

 Ví dụ: mảng được định nghĩa các ô nhớ liên tục trong bộ

nhớ…

28 @Copyrights by Dr. Ngo Huu Phuc, Le Quy Don Technical University


1.3. Biểu diễn thuật toán
Có nhiều cách khác nhau để biểu diễn một giải thuật, thông
thường, có 3 cách:
1.3.1. Biểu diễn bằng cách liệt kê các bước;
1.3.2. Biểu diễn bằng sơ đồ khối;
1.3.3. Biểu diễn bằng giả mã lệnh.

29 @Copyrights by Dr. Ngo Huu Phuc, Le Quy Don Technical University


1.3.1. Biểu diễn giải thuật bằng liệt kê các bước

Liệt kê ra các bước cần phải thực hiện bằng ngôn ngữ tự
nhiên.

VD 1: Thuật toán tìm số lớn nhất trong 3 số a,b,c


 Giả thiết số lớn nhất là a: Max=a

 Nếu Max<b thì Max=b

 Nếu Max<c thì Max=c

 Ghi giá trị của Max.

30 @Copyrights by Dr. Ngo Huu Phuc, Le Quy Don Technical University


1.3.2. Biểu diễn giải thuật bằng sơ đồ khối

Sơ đồ khối là một ngôn ngữ dùng để biểu diễn thuật toán.


 Hai loại nút giới hạn Bắt đầu và Kết thúc thường có hình
dạng elip:

31 @Copyrights by Dr. Ngo Huu Phuc, Le Quy Don Technical University


1.3.2. Biểu diễn giải thuật bằng sơ đồ khối (tiếp)
 Khối nhập/xuất có dạng hình chữ nhật bị cắt vát.
 Nút chỉ các thao tác, công việc có dạng hình chữ nhật:

 Nút chỉ các điều kiện có dạng hình thoi. Trong các đường nối với nút
điều kiện này có hai đường đi ra ứng với hai trường hợp điều kiện đúng
hoặc điều kiện sai.
 Khối hình bình hành để in các giá trị.
 Các đường nối các nút với nhau.

32 @Copyrights by Dr. Ngo Huu Phuc, Le Quy Don Technical University


1.3.2. Ví dụ về sơ đồ khối

33 @Copyrights by Dr. Ngo Huu Phuc, Le Quy Don Technical University


1.3.3. Biểu diễn giải thuật bằng giả mã lệnh
Trong các trường hợp biểu diễn bằng sơ đồ khối quá phức tạp, hay quá
dài, có thể sử dụng giả mã lệnh để biểu diễn giải thuật.

Một số cú pháp cơ bản trong giả mã lệnh:

 Cấu trúc tuần tự: Liệt kê các công việc, các thao tác theo thứ tự. Để hỗ

trợ cho việc theo dõi được thuận tiện cũng có thể thêm số thứ tự.

 Cấu trúc rẽ nhánh:

 If (điều kiện) Then (công việc);

 If (điều kiện) Then (công việc 1) Else (công việc 2);

34 @Copyrights by Dr. Ngo Huu Phuc, Le Quy Don Technical University


1.3.3. Biểu diễn giải thuật bằng giả mã lệnh (tiếp)
 Cấu trúc lặp:
 For (biến):= (giá trị đầu) to (giá trị cuối) do (công việc);
 For (biến):= (giá trị đầu) downto (giá trị cuối) do (công việc);
 While (điều kiện) do (công việc);
 Repeat
 - (công việc 1);
 - (công việc 2);
 - ....
 - (công việc k);
 Until (điều kiện);

 VD: Thuật toán tìm số lớn nhất trong 3 số a,b,c


 Max:=a;
 If Max<b then Max:=b;
 If Max<c then Max:=c;
 println(‘Max=’, max);

35 @Copyrights by Dr. Ngo Huu Phuc, Le Quy Don Technical University


1.3.3. Ví dụ về giả mã lệnh cho thuật toán HEURISTIC
void GREEDY ( GRAPH *G, SET *Newclr )
{
/*1*/ Newclr = ∅;
/*2*/ for (mỗi đỉnh v chưa tô màu của G)
/*3*/ if (v không được nối với một đỉnh nào trong Newclr)
{
/*4*/ đánh dấu v đã được tô màu;
/*5*/ thêm v vào Newclr;
}
}

36 @Copyrights by Dr. Ngo Huu Phuc, Le Quy Don Technical University


1.4. Độ phức tạp của thuật toán

Khi đề xuất một thuật toán ngoài việc quan tâm đến tính đúng
đắn của thuật toán, người ta còn phải quan tâm đến một số
vấn đề:

 Tính phổ dụng;

 Độ phức tạp thuật toán - thời gian tính toán của thuật toán

phụ thuộc vào dữ liệu đầu vào như thế nào,…

Trước khi nghiên cứu độ phức tạp thuật toán, ta xem xét một
số khái niệm về cách xác định độ phức tạp cho thuật toán.

37 @Copyrights by Dr. Ngo Huu Phuc, Le Quy Don Technical University


1.4. Độ phức tạp của thuật toán (tiếp)

Một số khái niệm:

 Khái niệm về độ tăng của hàm: Một trong các khái niệm

thường được dùng để phân tích độ tăng của hàm là khái


niệm O (ô lớn) được định nghĩa như sau:
 Cho f(x) và g(x), ta nói f(x) là O(g(x)) nếu tồn tại một hằng số C và k

sao cho

 |f(x)|<=C|g(x)| với mọi x>k;

 Độ tăng của tổ hợp các hàm:

 Xét f1(x) là O(g1(x) và f2(x) là O(g2(x)), CMR: (f1(x)+f2(x)) là

O(max{g1(x),g2(x)})
38 @Copyrights by Dr. Ngo Huu Phuc, Le Quy Don Technical University
1.4. Độ phức tạp của thuật toán (tiếp)
Độ phức tạp của thuật toán
 Các thuật ngữ O(1)-độ phức tạp hằng số; O(n) độ phức tạp
tuyến tính; O(nb) – độ phức tạp hàm mũ.
 Độ phức tạp tính toán sẽ được tính theo số lần thực hiện
các phép toán sơ cấp:cộng, trừ, nhân, chia, phép gán,..

VD: Xét đoạn chương trình sau:


For (int i=1; i<n;i++)
{
…{có phép tính sơ cấp; ko có vòng lặp ở đây nữa}
}
=> số lần lặp sẽ là n=> O(n)

39 @Copyrights by Dr. Ngo Huu Phuc, Le Quy Don Technical University


Tóm tắt chương 1

40 @Copyrights by Dr. Ngo Huu Phuc, Le Quy Don Technical University


Cấu trúc dữ liệu và giải thuật
Bài 2. MẢNG

Lecturer: PhD. Ngo Huu Phuc


Tel: 0438 326 077
Mob: 098 5696 580
Email: ngohuuphuc76@gmail.com

1 @Copyright Dr. Ngo Huu Phuc, Le Quy Don Technical University


Bài 2. Mảng
Nội dung:
1. Khái niệm về mảng.
2. Biểu diễn mảng 1 chiều (1D).
3. Biểu diễn mảng 2 chiều (2D).
4. Các phép toán trên mảng 1D.
5. Các phép toán trên mảng 2D.
Tham khảo:
Deshpande Kakde – C and data structures
Chapter 18 – Arrays, Searching, and Sorting
Chapter 23 – Problem in Arrays, Searching, Sorting, and Hashing

2 @Copyright Dr. Ngo Huu Phuc, Le Quy Don Technical University


2.1. Khái niệm về mảng (1/2)
 Mảng là cấu trúc dữ liệu do người dùng định nghĩa,

có kích thước cố định và đồng nhất.


 Theo tính chất đồng nhất, các thành phần có cùng kiểu, được

gọi là element type hoặc base type.


 Theo tính chất có kích thước cố định, ta không thể thay đổi

kích thước của mảng khi đang sử dụng.

 Mảng có thể coi là cấu trúc dữ liệu cho phép truy cập

ngẫu nhiên thông qua chỉ số của chúng.

3 @Copyright Dr. Ngo Huu Phuc, Le Quy Don Technical University


2.1. Khái niệm về mảng (2/2)
 Các thành phần của mảng được truy cập thông qua chỉ số,

chỉ số là số nguyên để chỉ vị trí của thành phần đó trong


mảng.

 Như vậy, một mảng được hình thành bởi một cặp (value,

index);

 Nếu chỉ số là 1 số, mảng được gọi là mảng 1 chiều. Nếu

chỉ số có dạng {i1, i2, i3,….., in}, mảng được gọi là mảng n
chiều.

4 @Copyright Dr. Ngo Huu Phuc, Le Quy Don Technical University


2.2. Biểu diễn mảng 1 chiều (1D) (1/3)
• Mảng được thể hiện trong bộ nhớ bằng ánh xạ tuần
tự.
• Đặc tính cơ bản của ánh xạ tuần tự cho mỗi phần tử của
mảng có “khoảng cách” cố định với phần tử đầu của mảng.

• Như vậy, nếu phần tử thứ i ánh xạ tới vị trí a thì phần tử
thứ (i+1) ánh xạ tới vị trí (a+1).

5 @Copyright Dr. Ngo Huu Phuc, Le Quy Don Technical University


2.2. Biểu diễn mảng 1 chiều (1D) (2/3)

6 @Copyright Dr. Ngo Huu Phuc, Le Quy Don Technical University


2.2. Biểu diễn mảng 1 chiều (1D) (3/3)
• Địa chỉ của phần tử đầu tiên trong mảng được
gọi là địa chỉ cơ sở (base address - LB).
• Địa chỉ của phần tử thứ i được xác định:
Base address + offset of the ith element from
base address
trong đó, offset được tính:
Offset of the ith element = number of elements
before the ith * size of each element.
• Nếu LB là lower bound (cận dưới), offset
được xác định:
offset = (i − LB) * size.
7 @Copyright Dr. Ngo Huu Phuc, Le Quy Don Technical University
2.3. Biểu diễn mảng 2 chiều (2D) (1/5)
• Mảng 2 chiều có thể được hiểu thông qua mảng

1D, trong đó, mỗi phần tử của nó là mảng 1D –


Mảng của Mảng.

• Mảng 2D có thể xem là 1 cột của các hàng

• Cách biểu diễn này được gọi là row-major

representation.

8 @Copyright Dr. Ngo Huu Phuc, Le Quy Don Technical University


2.3. Biểu diễn mảng 2 chiều (2D) (2/5)

9 @Copyright Dr. Ngo Huu Phuc, Le Quy Don Technical University


2.3. Biểu diễn mảng 2 chiều (2D) (3/5)
 Địa chỉ của phần tử tại hàng i, cột j được xác định:
addr(a[i, j]) = ( number of rows placed before ith row *
size of a row) + (number of elements placed before the
jth element in the ith row * size of element)
trong đó:
 Number of rows placed before ith row = (i – LB1), với LB1 là lower
bound của chiều thứ nhất.
 Size of a row = number of elements in a row * a size of element.
 Number of elements in a row = (UB2 – LB2+1), với UB2 và LB2 là
cận trên và cận dưới của chiều thứ 2.
 Như vậy:
addr(a[i, j]) = ((i−LB1)*(UB2−LB2+1)*size)+((j−LB2)*size)

10 @Copyright Dr. Ngo Huu Phuc, Le Quy Don Technical University


2.3. Biểu diễn mảng 2 chiều (2D) (4/5)
 Mảng 2D có thể

xem là một hàng


các cột.

 Cách biểu diễn này

được gọi là column-


major
representation.

11 @Copyright Dr. Ngo Huu Phuc, Le Quy Don Technical University


2.3. Biểu diễn mảng 2 chiều (2D) (5/5)
 Địa chỉ của phần tử có hàng i, cột j được xác định:
addr(a[i, j]) = ( number of columns placed before jth
column * size of a column) + (number of elements
placed before the ith element in the jth column * size of
each element)
 Number of columns placed before jth column = (j − LB2), với LB2 là
cận dưới của chiều thứ 2.
 Size of a column = number of elements in a column * size of element
 Number of elements in a column = (UB1 – LB1 + 1), với UB1 và LB1
là cận trên và cận dưới của chiều thứ nhất.
 Như vậy:
addr(a[i, j]) = ((j − LB2) * (UB1 − LB1+1) * size) + ((i − LB1)*size)
hoặc
addr(a[i, j]) = ((i−LB1)*(UB2−LB2+1)*size)+((j−LB2)*size)

12 @Copyright Dr. Ngo Huu Phuc, Le Quy Don Technical University


2.4. Một số ví dụ về mảng (1/14)
Ví dụ 1: Chương trình cho phép truy cập tới void read(int c[], int i) {
các thành phần của danh sách.
int j;
#include <stdio.h>
for(j=0;j<i;j++)
#include <conio.h>
scanf("%d",&c[j]);
void read(int *,int);
void dis(int *,int); fflush(stdin); }

void main() void dis(int d[],int i)


{ {
int a[5],i,sum=0; int j;
printf("Nhap cac phan tu cho for(j=0;j<i;j++)
mang \n");
printf("%d ",d[j]);
read(a,5);
printf("\n");
printf("Gia tri cua cac phan
tu trong mang \n"); }
dis(a,5);
getch();
}

13 @Copyright Dr. Ngo Huu Phuc, Le Quy Don Technical University


2.4. Một số ví dụ về mảng (2/14)
Ví dụ 2: Tính tổng các phần tử trong mảng. void read(int c[],int i)
#include<stdio.h>
{
#include<conio.h>
int j;
void read(int *,int);
void dis(int *,int); for(j=0;j<i;j++)
void main() scanf("%d",&c[j]);
{
fflush(stdin);
int a[5],i,sum=0;
printf("Nhap gia tri cho mang \n");
}
read(a,5); void dis(int d[],int i)
printf("Cac gia tri da nhap \n"); {
dis(a,5);
int j;
for(i=0;i<5;i++)
sum+=a[i]; for(j=0;j<i;j++)
printf("Tong cac phan tu trong mang printf("%d ",d[j]);
%d\n",sum);
printf("\n");
getch();
} }

14 @Copyright Dr. Ngo Huu Phuc, Le Quy Don Technical University


2.4. Một số ví dụ về mảng (3/14)
Ví dụ 3: Tính tổng trong của 2 mảng. void add(int a[],int b[],int c[],int n) {
#include <stdio.h> int i;
#include <conio.h> for(i=0;i<n;i++)
void read(int *,int); void dis(int *,int);
c[i]=a[i]+b[i];
void add(int *,int *,int * ,int);
}
void main() {
void read(int c[],int n) {
int a[5],b[5],c[5];
int i;
printf("Nhap gia tri cho mang thu nhat \n");
read(a,5); for(i=0;i<n;i++)

printf("Cac gia tri da nhap cua mang 1 \n"); scanf("%d",&c[i]);


dis(a,5); fflush(stdin);
printf("Nhap gia tri cho mang thu hai \n"); }
read(b,5); void dis(int d[],int n) {
printf("Cac gia tri da nhap cua mang 2 \n"); int i;
dis(b,5);
for(i=0;i<n;i++)
add(a,b,c,5);
printf("%d ",d[i]);
printf("Gia tri cua mang ket qua \n");
printf("\n");
dis(c,5); getch(); }
}
15 @Copyright Dr. Ngo Huu Phuc, Le Quy Don Technical University
2.4. Một số ví dụ về mảng (4/14)
Ví dụ 4: Đảo danh sách. void display(int d[],int n) {
#include <stdio.h> int i;
#include <conio.h> printf("Gia tri cac phan tu trong mang \n");
void read(int *,int);
for(i=0;i<n;i++)
void display(int *,int);
printf("%d ",d[i]);
void inverse(int *,int);
printf("\n");
void main() {
}
int a[5];
read(a,5); void inverse(int inver_a[],int n)

display(a,5); {
inverse(a,5); int i,temp;
display(a,5); for(i=0;i<(n/2);i++)
getch(); } {
void read(int c[],int n) { temp=inver_a[i];
int i;
inver_a[i]=inver_a[n-1-i];
printf("Nhap cac phan tu cho mang \n");
inver_a[n-1-i]=temp;
for(i=0;i<n;i++) scanf("%d",&c[i]);
}
fflush(stdin); }
}
16 @Copyright Dr. Ngo Huu Phuc, Le Quy Don Technical University
2.4. Một số ví dụ về mảng (5/14)
Ví dụ 5: Nối 2 mảng đã sắp xếp (1/3) sort(a,5);
#include <stdio.h> printf("Mang thu nhat duoc sap:\n");
#include <conio.h> display(a,5);
void read(int *,int);
sort(b,5);
void display(int *,int);
printf("Mang thu hai duoc sap:\n");
void sort(int *,int);
display(b,5);
void merge(int *,int *,int *,int,int);
merge(a,b,c,5,5); // noi hai mang
void main() {
int a[5],b[5],c[10]; printf("Cac phan tu trong mang moi, sau khi noi
\n");
printf("Nhap cac phan tu cua mang 1th \n");
display(c,10);
read(a,5);
getch();
printf("Cac phan tu da nhap cua mang 1th \n");
display(a,5); }
printf("Nhap cac phan tu cua mang 2rd \n");
read(b,5);
printf("Cac phan tu da nhap cua mang 2rd \n");
display(b,5);

17 @Copyright Dr. Ngo Huu Phuc, Le Quy Don Technical University


2.4. Một số ví dụ về mảng (6/14)
Ví dụ 5: Nối 2 mảng đã sắp xếp (2/3) void sort(int arr[] ,int n)
void read(int c[],int n) {
{ int temp;
int i;
int i,j;
for(i=0;i<n;i++)
for(i=0;i<n;i++)
scanf("%d",&c[i]);
{
fflush(stdin);
for(j=0;j<n-i-1;j++)
}
void display(int d[],int n) {

{ if(arr[j]>arr[j+1])
int i; {
for(i=0;i<n;i++) temp=arr[j];
printf("%d ",d[i]); arr[j]=arr[j+1];
printf("\n"); arr[j+1]=temp;
}
}
}
}
}
18 @Copyright Dr. Ngo Huu Phuc, Le Quy Don Technical University
2.4. Một số ví dụ về mảng (10/14)
Ví dụ 5: Nối 2 mảng đã sắp xếp(3/3) while(ptra<k1)
void merge(int a[],int b[],int c[],int k1, int k2) {
{ c[ptrc]=a[ptra];
int ptra=0,ptrb=0,ptrc=0;
ptra++;ptrc++;
while(ptra<k1 && ptrb<k2)
}
{
while(ptrb<k2)
if(a[ptra] < b[ptrb])
{
{
c[ptrc]=a[ptra]; c[ptrc]=b[ptrb];

ptra++; ptrb++;
} ptrc++;
else }
{ }
c[ptrc]=b[ptrb];
ptrb++;
}
ptrc++;
}

19 @Copyright Dr. Ngo Huu Phuc, Le Quy Don Technical University


2.4. Một số ví dụ về mảng (7/14)
Ví dụ 6: Tính tích vô hướng của 2 vecto void read(int c[],int n) {
#include <stdio.h> int i;
#include <conio.h> for(i=0;i<n;i++)
void read(int *,int); void dis(int *,int);
scanf("%d",&c[i]);
int multi(int *,int *,int);
fflush(stdin); }
void main() {
void dis(int d[],int n) {
int a[5],b[5]; int sum=0;
int i;
printf("Nhap gia tri cho mang 1 \n");
read(a,5); for(i=0;i<n;i++)

printf("Cac gia tri da nhap cua mang 1 \n"); printf("%d ",d[i]);


dis(a,5); printf("\n"); }
printf("Nhap gia tri cho mang 2 \n"); int multi(int a[],int b[],int n) {
read(b,5); int i, sum=0;
printf("Cac gia tri da nhap cua mang 2 \n"); for(i=0;i<n;i++)
dis(b,5);
{ sum+=a[i]*b[i]; }
sum=multi(a,b,5);
return sum;
printf("Gia tri cua tich vo huong: %d",sum);
}
getch(); }

20 @Copyright Dr. Ngo Huu Phuc, Le Quy Don Technical University


2.4. Một số ví dụ về mảng (8/14)
Ví dụ 7: Tính khoảng cách Euclide giữa 2 vecto void read(int c[],int n) {
#include <stdio.h> int i;
#include <conio.h> for(i=0;i<n;i++)
#include <math.h>
scanf("%d",&c[i]);
void read(int *,int); void dis(int *,int);
fflush(stdin); }
float euclide(int *,int *,int);
void dis(int d[],int n) {
void main() {
int i;
int a[5],b[5];
printf("Nhap gia tri cho mang 1 \n"); for(i=0;i<n;i++)

read(a,5); printf("%d ",d[i]);


printf("Cac gia tri da nhap cua mang 1 \n"); printf("\n"); }
dis(a,5); float euclide(int a[],int b[],int n) {
printf("Nhap gia tri cho mang 2 \n"); int i;
read(b,5); float e=0.0;
printf("Cac gia tri da nhap cua mang 2 \n");
for(i=0;i<n;i++) {
dis(b,5);
e+=pow((float)(a[i]-b[i]),2); }
printf("Gia tri cua tich vo huong: %f",euclide(a,b,5));
return sqrt(e);
getch(); }
}
21 @Copyright Dr. Ngo Huu Phuc, Le Quy Don Technical University
2.4. Một số ví dụ về mảng (9/14)
Ví dụ 8: Tính tích chập giữa 2 vecto void read(int c[],int n) {
#include <stdio.h> int i;
#include <conio.h> for(i=0;i<n;i++)
#include <math.h>
scanf("%d",&c[i]);
void read(int *,int); void display(int *,int);
fflush(stdin); }
void convolution(int *,int *,int *,int, int);
void display(int d[],int n) {
void main() {
int i;
int a[5],b[3],c[5];
printf("Nhap gia tri cho mang 1 \n"); for(i=0;i<n;i++)

read(a,5); printf("%d ",d[i]);


printf("Cac gia tri da nhap cua mang 1 \n"); printf("\n"); }
display(a,5); void convolution(int a[],int b[],int c[],int k1, int k2) {
printf("Nhap gia tri cho mang 2 \n"); read(b,3); int i,j;
printf("Cac gia tri da nhap cua mang 2 \n"); for(i=0;i<k1;i++) {
display(b,3);
c[i]=0;
convolution(a,b,c,5,3);
for(j=i-k2+1;j<=i;j++)
printf("Gia tri cua tich chap \n");
if(j>=0) c[i]+=a[j]*b[j+k2-1-i]; }
display(c,5); getch(); }
}
22 @Copyright Dr. Ngo Huu Phuc, Le Quy Don Technical University
2.4. Một số ví dụ về mảng (11/14)
Ví dụ 9: Chuyển vị ma trận (1/2) void read(int c[ROW][COL] ,int row ,int column)
#include <stdio.h> {
#include <conio.h> int i,j;
#define ROW 3
printf("Nhap ma tran \n");
#define COL 3
for(i=0;i<row;i++)
void read(int a[][COL],int,int);
for(j=0;j<column;j++)
void display(int a[][COL],int,int);
scanf("%d",&c[i][j]);
void trans(int a[][COL],int,int);
void main() { fflush(stdin);

int a[ROW][COL]; }
read(a,ROW,COL); void display(int d[ROW][COL],int row,int column) {
printf("\nMa tran da nhap \n"); int i,j;
display(a,ROW,COL); for(i=0;i<row;i++)
trans(a,ROW,COL); {
printf("Ma tran sau khi da chuyen vi\n");
for(j=0;j<column;j++) printf("%d ",d[i][j]);
display(a,ROW,COL);
printf("\n");
getch();
}
}
}
23 @Copyright Dr. Ngo Huu Phuc, Le Quy Don Technical University
2.4. Một số ví dụ về mảng (12/14)
Ví dụ 9: Chuyển vị ma trận (2/2)
void trans(int mat[][COL],int row ,int column)
{
int i,j,temp;
for(i=0;i<row;i++)
for(j=i+1;j<column;j++)
{
temp=mat[i][j];
mat[i][j]=mat[j][i];
mat[j][i]=temp;
}
}

24 @Copyright Dr. Ngo Huu Phuc, Le Quy Don Technical University


2.4. Một số ví dụ về mảng (13/14)
Ví dụ 10: Tìm điểm yên ngựa trong ma trận (1/2) void read(int c[][COL] ,int row ,int column)
#include "stdio.h" {
#include "conio.h" int i,j;
#include "stdlib.h"
printf("Nhap ma tran \n");
#define ROW 3
for(i=0;i<row;i++)
#define COL 3
for(j=0;j<column;j++)
void read(int c[][COL],int,int); scanf("%d",&c[i][j]);
void display(int d[][COL],int,int);
fflush(stdin);
int sadd_pt(int a[][COL],int,int,int *,int*);
}
void main() {
void display(int d[][COL],int row,int column) {
int i,a[ROW][COL],m=0,n=0;
int i,j;
read(a,ROW,COL);
printf("\nMa tran nhap vao \n"); for(i=0;i<row;i++) {
display(a,ROW,COL); for(j=0;j<column;j++)
printf("\t%d",d[i][j]);
i=sadd_pt(a,ROW,COL,&m,&n);
printf("Diem yen ngua %d tai hang : %d cot : printf("\n");
%d\n",i,m+1,n+1); }
getch(); } }

25 @Copyright Dr. Ngo Huu Phuc, Le Quy Don Technical University


2.4. Một số ví dụ về mảng (14/14)
Ví dụ 10: Tìm điểm yên ngựa trong ma trận (1/2) for(j=0;j<row;j++)
int sadd_pt(int mat[][COL],int row ,int column,int if(min>=mat[j][n])
*r,int *c)
p++;
{
if(p==COL)
int min,i=0,j,m,n,p=0;
{
while(i<row)
{ *r=m;
min=mat[i][0]; *c=n;
m=i; return(min);
p=0; }
n=0; i++;
for(j=0;j<column;j++)
}
{
printf("Khong co diem yen ngua\n");
if(mat[i][j]<min)
getch();
{
exit(0);
min=mat[i][j];
n=j; }

}
}
26/26 @Copyright Dr. Ngo Huu Phuc, Le Quy Don Technical University
Cấu trúc dữ liệu và giải thuật
Bài 3. Đệ quy (Recursion)

Lecturer: PhD. Ngo Huu Phuc


Tel: 0438 326 077
Mob: 098 5696 580
Email: ngohuuphuc76@gmail.com

1 @Copyright by PhD. Ngo Huu Phuc, Le Quy Don Technical University


Bài 3. Đệ quy
Nội dung:
 Khái niệm về đệ quy.
 Ví dụ về đệ quy.
 Đệ quy đuôi (Tail Recursion)
 Bài toán tháp Hanoi.

Tham khảo:
1. Kyle Loudon – Mastering Algorithms with C
Chapter 3 – Recursion
2. Hanoi Tower (Web page)
2 @Copyright by PhD. Ngo Huu Phuc, Le Quy Don Technical University
3.1. Khái niệm về đệ quy (1/6)
 Với phần lập trình viên, để giải quyết bài toán lớn, có

thể sử dụng 1 quá trình dạng đệ quy.

 Ta nói m ột đối tượng là đệ qui nếu đối tượng này bao

gồm chính nó như một bộ phận hoặc đối tượng được


định nghĩa dưới dạng của chính nó.

3 @Copyright by PhD. Ngo Huu Phuc, Le Quy Don Technical University


3.1. Khái niệm về đệ quy (2/6)
 Nguyên lý đệ quy cho phép hình thành bài toán từ chính

nó.

 Trong tính toán, để giải quyết vấn đề sử dụng hàm đệ quy

(hàm gọi chính nó với tham số thay đổi).

 Như vậy, hàm đệ quy là hàm gọi lại chính nó.

 Với mỗi bước, hàm thay đổi thông tin đầu vào và cho kết

quả ngày càng gần với mục tiêu của bài toán.

4 @Copyright by PhD. Ngo Huu Phuc, Le Quy Don Technical University


3.1. Khái niệm về đệ quy (3/6)
• Giả sử cần tính giai thừa của một số nguyên dương n.

• Giai thừa của n được viết: n!, tích các phần tử từ n đến 1.

• Ví dụ, 5! = (5)(4)(3)(2)(1).

• Có thể thực hiện tính giai thừa bằng vòng lặp thông thường

để tính tích.
• Một cách tiếp cận khác, sử dụng đệ quy, khi đó công thức

tính giai thừa được viết dạng:


n! = (n)(n - 1)(n - 2) . . . (1)

5 @Copyright by PhD. Ngo Huu Phuc, Le Quy Don Technical University


3.1. Khái niệm về đệ quy (4/6)
• Có thể định nghĩa n! theo cách khác, là tích của n và giai
thừa nhỏ hơn.
• Như vậy, ta có n! = n * (n – 1)!.
• Ta x ử lý (n - 1)! giống như n!, với tham số nhỏ hơn.
• Ta có, (n - 1)! = (n – 1) * (n - 2)!,
• Tương tự, (n - 2)! = (n – 2) * (n - 3)!, quá trình trên kết thúc
khi n=1.
• Với cách tiếp cận dạng đệ quy, có thể định nghĩa lại cách
tính giai thừa dạng:
n! = iif(n=0, 1, n * (n-1)!)

6 @Copyright by PhD. Ngo Huu Phuc, Le Quy Don Technical University


3.1. Khái niệm về đệ quy (5/6)
 Có thể hình dung đệ quy qua 2 bước chính:
quay xuôi (winding) và quay ngược
(unwinding).
 Tại bước winding, lời giải bài toán gọi lại chính
nó.
 Với bước winding, lời gọi chính nó dừng khi thỏa mãn
điều kiện dừng.
 Điều kiện dừng thông thường được định nghĩa là tại
bước đó đã đến bài toán cơ sở, có thể giải trực tiếp
được.
 Với mỗi hàm đệ quy cần có ít nhất một điều kiện
dừng, nếu không sẽ lặp vô hạn.

7 @Copyright by PhD. Ngo Huu Phuc, Le Quy Don Technical University


3.1. Khái niệm về đệ quy (6/6)
 Khi bước winding kết thúc, bước unwinding sẽ

được thực hiện, các bài toán nhỏ trên được xem
xét theo thứ tự ngược lại.
 Bước này dừng lại khi quá trình đến bài toán gốc. Quá

trình đệ quy kết thúc.

8 @Copyright by PhD. Ngo Huu Phuc, Le Quy Don Technical University


3.2. Ví dụ về đệ quy (1/6)
Ví dụ 1: Tính n!.
F(n) = n* F(n-1) nếu n > 1
F(n) = 1 nếu n = 1 hoặc n = 0
Tính F(4) = ?

Bước Winding Bước UnWinding


F(4) = 4 * F(3) F(4) = 4 * 6 = 24
F(3) = 3 * F(2) F(3) = 3 * 2 = 6
F(2) = 2 * F(1) F(2) = 2 * 1 = 2
F(1) = 1 F(1) = 1
9 @Copyright by PhD. Ngo Huu Phuc, Le Quy Don Technical University
Ví dụ 1: Tính n!
Ví dụ 1: Tính n! bằng đệ quy. long factorial(int n)
#include<stdio.h> {
#include<conio.h>
if((n==0)||(n==1)) return 1;
long factorial(int);
else return n*factorial(n-1);
void main()
}
{
int n;
printf("Nhap vao mot so: ");
scanf("%d",&n);
printf("Gia tri cua giai thua:
%ld",factorial(n));
getch();
}

10 @Copyright Dr. Ngo Huu Phuc, Le Quy Don Technical University


3.2. Ví dụ về đệ quy (2/6)
Ví dụ 2: Tìm max của một dãy số.
F(a,1) = a[1], F(a,2) = max(a[1],a[2])
F(a,n) = max(F(a,n-1),a[n]) if n > 2
Cho a[] = [3,2,5,7,1]. Tính F(a,5) = ?

Bước Winding UnWinding Phase


F(a,5) = max(F(a,4),a[5]) F(a,5) = max(7,1) = 7
F(a,4) = max(F(a,3),a[4]) F(a,4) = max(5,7) = 7
F(a,3) = max(F(a,2),a[3]) F(a,3) = max(3,5) = 5
F(a,2) = max(a[1],a[2]) F(a,2) = max(3,2)=3
11 @Copyright by PhD. Ngo Huu Phuc, Le Quy Don Technical University
Ví dụ 2: Tìm max của một dãy số
Ví dụ 2: Tìm max. int F(int *a, int n)
#include<stdio.h> {
#include<conio.h> if (n==1) return a[0];
int F(int *, int);
if (n==2) return max(a[0],a[1]);
int max(int, int);
if (n>2) return max(F(a,n-1),a[n-1]);
void main()
}
{
int max(int a, int b)
int n;
int a[100]; {

printf("Nhap vao so phan tu: "); if(a>b) return a;


scanf("%d",&n); else return b;
for(int i=0;i<n;i++) }
{
printf("Nhap phan tu a[%d]=",i);
scanf("%d",&a[i]);
}
printf("Gia tri max: %d",F(a,n));
getch(); }

12 @Copyright Dr. Ngo Huu Phuc, Le Quy Don Technical University


3.2. Ví dụ về đệ quy (3/6)
Ví dụ 3: Tính tổng của một dãy số.
F(a,1) = a[1]
F(a,n) = F(a,n-1)+a[n] if n > 1
Cho a[] = [1,7,3,5], Tính F(a,4) = ?

Bước Winding Bước UnWinding


F(a,4) = F(a,3) + a[4] F(a,4) = 11 + 5 = 16
F(a,3) = F(a,2) + a[3] F(a,3) = 8 + 3 = 11
F(a,2) = F(a,1) + a[2] F(a,2) = 1 + 7 = 8
F(a,1) = a[1] F(a,1) = 1
13 @Copyright by PhD. Ngo Huu Phuc, Le Quy Don Technical University
Ví dụ 3: Tính tổng của một dãy số
Ví dụ 3: Tính tổng một dãy số. long sum(int * a, int n)
#include<stdio.h> {
#include<conio.h> if(n==1) return a[0];
long sum(int*, int);
else return sum(a,n-1)+a[n-1];
void main()
}
{
int n;
int a[100];
printf("Nhap vao so pt: ");
scanf("%d",&n);
for(int i=0;i<n;i++)
{
printf("Nhap pt a[%d]=",i);
scanf("%d",&a[i]);
}
printf("Tong cua cac pt: %ld",sum(a,n));
getch();
}

14 @Copyright Dr. Ngo Huu Phuc, Le Quy Don Technical University


3.2. Ví dụ về đệ quy (4/6)
Ví dụ 4: Dãy số Fibonacci.
F(n) = 1 nếu n = 1 hoặc n = 2
F(n) = F(n-1) + F(n-2) nếu n > 2
Tính F(4) = ?

Bước Winding Bước UnWinding


F(4) = F(3) + F(2) F(4) = 2 + 1 = 3
F(3) = F(2) + F(1) F(3) = 1 + 1 = 2
F(2) = 1 F(2) = 1
F(1) = 1 F(1) = 1
15 @Copyright by PhD. Ngo Huu Phuc, Le Quy Don Technical University
Ví dụ 4: Tính số Fibonacci
Ví dụ 4: Tính số fibonacci. long fibonacci(int n)
#include<stdio.h> {
#include<conio.h> if((n==1) || (n==2)) return 1;
long fibonacci( int);
else return fibonacci(n-1)+fibonacci(n-2);
}
void main()
{
int n;
printf("Nhap vao n: ");
scanf("%d",&n);
printf("Fibonacci cua %d: %ld",n,fibonacci(n));
getch();
}

16 @Copyright Dr. Ngo Huu Phuc, Le Quy Don Technical University


3.2. Ví dụ về đệ quy (5/6)
Ví dụ 5: Hàm Ackermann.
F(m,0) = m + 1 nếu m >= 0
F(0,n) = F(1,n-1) nếu n > 0
F(m,n) = F(F(m-1,n),n-1) nếu m >= 1 và n > 0
Tính F(2,1) = ?

Bước Winding Bước UnWinding


F(2,1) = F(F(1,1),0) F(2,1) = F(3,0) = 4
F(1,1) = F(F(0,1),0) F(1,1) = F(2,0) = 3
F(0,1) = F(1,0) F(0,1) = 2
F(1,0) = 2 F(1,0) = 2
17 @Copyright by PhD. Ngo Huu Phuc, Le Quy Don Technical University
Ví dụ 5: Hàm Ackermann
Ví dụ 5: Hàm Ackermann int Ackermann(int m, int n)
#include<stdio.h> {
#include<conio.h> if((m>=0) && (n==0)) return m+1;
int Ackermann(int, int);
if((n>0) && (m==0)) return Ackermann(1,n-1);
if((m>=1) && (n>0)) return
void main() Ackermann(Ackermann(m-1,n), n-1);
{ }
int m, n;
printf("Nhap vao m= ");
scanf("%d",&m);
printf("Nhap vao n= ");
scanf("%d",&n);
printf("Ackermann cua %d va %d:
%d",m,n,Ackermann(m,n));
getch();
}

18 @Copyright Dr. Ngo Huu Phuc, Le Quy Don Technical University


3.2. Ví dụ về đệ quy (6/6)
Ví dụ 6: Pascal Identifier Definition

Character

Character

Digit

Identifiers: Name of Constants, Variables,


Functions, Procedure, Programs, Labels, …
19 @Copyright by PhD. Ngo Huu Phuc, Le Quy Don Technical University
3.3. Đệ quy đuôi - Tail Recursion (1/6)
 Hàm đệ quy được gọi là đệ quy đuôi - tail recursive – nếu
các lời gọi đệ quy trong hàm là công việc cuối cùng của
quá trình đệ quy.
 Trong quá trình đệ quy, các trạng thái của quá trình trước
được lưu cho quá trình sau. Tuy vậy, với đệ quy đuôi, việc
lưu trên là không cần thiết.
 Hàm đệ quy đuôi không có bước quay ngược.

 Tính chất trên không quá quan trọng vì phần lớn các bộ xử
lý hiện nay thực hiện được điều này tự động.

20 @Copyright by PhD. Ngo Huu Phuc, Le Quy Don Technical University


3.3. Đệ quy đuôi (2/6)
Ví dụ 1: Tính n!
F(n, a) = F(n-1, n*a) nếu n>1
F(n,a) = a nếu n = 1 hoặc n = 0
Tính F(4,1) = ?

Bước Winding Bước UnWinding


F(4,1) = F(3,4) Không thực hiện gì
F(3,4) = F(2,12)
F(2,12) = F(1,24)
F(1,24) = 24
21 @Copyright by PhD. Ngo Huu Phuc, Le Quy Don Technical University
Ví dụ 1: Tính n! bằng đệ quy đuôi
Ví dụ 1: Tính n! bằng đệ quy đuôi. long factorial_tail(int n, int a)
#include<stdio.h> {
#include<conio.h> if (n>1) return factorial_tail(n-1,n*a);
long factorial_tail(int, int);
else return a;
}
void main()
{
int n;
printf("Nhap vao mot so: ");
scanf("%d",&n);
printf("Gia tri cua giai thua:
%ld",factorial_tail(n,1));
getch();
}

22 @Copyright Dr. Ngo Huu Phuc, Le Quy Don Technical University


3.3. Đệ quy đuôi (3/6)
Ví dụ 2: Tìm phần tử Maximum trong chuỗi số.
F(a,1,b) = b,
F(a,n,b) = F(a,n-1,max(b,a[n-1]) nếu n > 1
Cho mảng a[5] = [3,9,5,7,1]. Tính F(a,5,a[5]) = ?

Bước Winding Bước UnWinding


F(a,5,a[n-1]) = F(a,4,7) Không thực hiện gì
F(a,4,7) = F(a,3,7)
F(a,3,7) =F(a,2,9)
F(a,2,9) = F(a,1,9)
F(a,1,9) = 9
23 @Copyright by PhD. Ngo Huu Phuc, Le Quy Don Technical University
Ví dụ 2: Tìm max bằng đệ quy đuôi
Ví dụ 2: Tìm max bằng đệ quy đuôi. int F_tail(int *a, int n, int b)
#include<stdio.h> {
#include<conio.h> if (n==1) return b;
int F_tail(int *, int, int);
else return F_tail(a,n-1,max(b,a[n-2]));
int max(int, int);
}
void main()
int max(int a, int b)
{
{
int n;
int a[100]; if(a>b) return a;

printf("Nhap vao so phan tu: "); else return b;


scanf("%d",&n); }
for(int i=0;i<n;i++)
{
printf("Nhap phan tu a[%d]=",i);
scanf("%d",&a[i]);
}
printf("Gia tri max: %d",F_tail(a,n,a[n-1]));
getch(); }

24 @Copyright Dr. Ngo Huu Phuc, Le Quy Don Technical University


3.3. Đệ quy đuôi (4/6)
Ví dụ 3: Tính tổng của chuỗi số.
F(a,1,b) = b
F(a,n,b) = F(a,n-1,b+a[n-1]) nếu n > 1
Cho a[4] = [1,7,3,5]. Tính F(a,4,a[4]) = ?

Bước Winding Bước UnWinding


F(a,4,5) = F(a,3,8) Không thực hiện gì
F(a,3,8) = F(a,2,15)
F(a,2,15) = F(a,1,16)
F(a,1,16) = 16

25 @Copyright by PhD. Ngo Huu Phuc, Le Quy Don Technical University


Ví dụ 3: Tính tổng bằng đệ quy đuôi
Ví dụ 3: Tính tổng bằng đệ quy đuôi long sum_tail(int * a, int n, long b)
#include<stdio.h> {
#include<conio.h> if(n==0)return b;
long sum_tail(int*, int, long);
else return sum_tail(a,n-1,b+a[n-1]);
void main()
}
{
int n;
int a[100];
printf("Nhap vao so pt: ");
scanf("%d",&n);
for(int i=0;i<n;i++)
{
printf("Nhap pt a[%d]=",i);
scanf("%d",&a[i]);
}
printf("Tong cua cac pt: %ld",sum_tail(a,n-1,a[n-1]));
getch();
}

26 @Copyright Dr. Ngo Huu Phuc, Le Quy Don Technical University


3.3. Đệ quy đuôi (5/6)
Ví dụ 4: Số Fibonacci.
F(n,a,b) = b nếu n = 1 hoặc n = 2
F(n,a,b) = F(n-1,b,a+b) nếu n > 2
Tính F(4,0,1) = ?

Bước Winding Bước UnWinding


F(4,0,1) = F(3,1,1) Không thực hiện gì
F(3,1,1) = F(2,1,2)
F(2,1,2) = F(1,2,3)
F(1,2,3) = 3

27 @Copyright by PhD. Ngo Huu Phuc, Le Quy Don Technical University


Ví dụ 4: Tính Fibonacci bằng đệ quy đuôi
Ví dụ 4: Tính Fibonacci bằng đệ quy đuôi. long fibonacci(int n, int a, int b)
#include <stdio.h> {
#include <conio.h> if (n==1) return b;
long fibonacci(int, int, int);
else return fibonacci(n-1,b,a+b);
}
void main()
{
int n;
printf("Nhap vao n: ");
scanf("%d",&n);
printf("Fibonacci cua %d:
%ld",n,fibonacci(n,0,1));
getch();
}

28 @Copyright Dr. Ngo Huu Phuc, Le Quy Don Technical University


3.3. Đệ quy đuôi (6/6)
Ví dụ 5: Euler GCD (greatest common divisor) Algorithm
(n > m)
F(n,m) = m nếu n div m = 0
F(n, m) = F(m, n mod m) nếu n mod m <> 0
Tính F(54,21) = ?
Bước Winding Bước UnWinding
F(54,21) = F(21,12) Không thực hiện gì
F(21,12) = F(12,9)
F(12,9) = F(9,3)
F(9,3) = 3
29 @Copyright by PhD. Ngo Huu Phuc, Le Quy Don Technical University
Ví dụ 5: Tính ước số chung lớn nhất
Ví dụ 5: Hàm Euler GCD int Ackermann(int m, int n)
#include<stdio.h> {
#include<conio.h> if((m>=0) && (n==0)) return m+1;
int Ackermann(int, int);
if((n>0) && (m==0)) return Ackermann(1,n-1);
if((m>=1) && (n>0)) return
void main() Ackermann(Ackermann(m-1,n), n-1);
{ }
int m, n;
printf("Nhap vao m= ");
scanf("%d",&m);
printf("Nhap vao n= ");
scanf("%d",&n);
printf("Ackermann cua %d va %d:
%d",m,n,Ackermann(m,n));
getch();
}

30 @Copyright Dr. Ngo Huu Phuc, Le Quy Don Technical University


3.4. Tháp Hà nội (1/8)
 Bài toán được nhà toán học người Pháp Edouard

Lucas đưa ra vào năm 1883.

 Bài toán Tháp Hà nội được giới thiệu trong nhiều

tài liệu về thuật toán. Bên cạnh đó, rất nhiều web
sites cũng đề cập tới bài toán này.

31 @Copyright by PhD. Ngo Huu Phuc, Le Quy Don Technical University


3.4. Tháp Hà nội (2/8)
Bài toán
 Có n chiếc đĩa có kích thước khác nhau và có 3 stacks có
tên A, B, C.
 Ban đầu, n đĩa được đặt tại stack A, sao cho không có đĩa
lớn nằm trên đĩa nhỏ.
 Nhiệm vụ đặt ra là chuyển tất cả các đĩa ở stack A sang
stack C. Sử dụng stack B làm trung gian trong quá trình
chuyển.
 Mỗi lần chuyển 1 đĩa và không có đĩa lớn nằm trên đĩa nhỏ.

32 @Copyright by PhD. Ngo Huu Phuc, Le Quy Don Technical University


3.4. Tháp Hà nội (3/8)

33 @Copyright by PhD. Ngo Huu Phuc, Le Quy Don Technical University


3.4. Tháp Hà nội (4/8)
 Để giải quyết bài toán trên, số bước chuyển đĩa ít

nhất là bao nhiêu?

 Để trả lời câu hỏi trên, cần đưa ra cách đệ quy tối ưu

và phân tích chúng.

 Có thể phân tích được không trong trường hợp tổng

quát hóa, không chỉ xét 3 đĩa?

34 @Copyright by PhD. Ngo Huu Phuc, Le Quy Don Technical University


3.4. Tháp Hà nội (5/8)
Giải pháp đệ quy:
 Ban đầu, đĩa lớn nhất được đặt tại đáy của stack A.

 Giả sử rằng nó được chuyển sang stack C (cách tốt nhất


để tiếp cận bài toán), như vậy n-1 đĩa nhỏ hơn phải được
chuyển sang stack B.
 Như vậy, cần chuyển n-1 đĩa nhỏ từ A sang B, sử dụng C
làm stack trung gian.
 Sau khi thực hiện được việc trên, cần chuyển n-1 đĩa còn
lại từ B sang C, sử dụng stack A làm stack trung gian.

35 @Copyright by PhD. Ngo Huu Phuc, Le Quy Don Technical University


3.4. Tháp Hà nội (6/8)
 Ký hiệu: "A ==> B" có nghĩa chuyển 1 đĩa trên đỉnh
của A sang B.
 Giải thuật:

Function HanoiTower (n, A,B,C)


1. if n < 0 then return
2. HanoiTower (n-1, A,C,B)
3. A ==> B
4. HanoiTower (n-1, C,B,A)
End Function

36 @Copyright by PhD. Ngo Huu Phuc, Le Quy Don Technical University


3.4. Tháp Hà nội (7/8)
Phân tích bài toán với n = 2

37 @Copyright by PhD. Ngo Huu Phuc, Le Quy Don Technical University


Tháp Hà nội
#include "stdio.h" void HanoiTower(int m, char a, char b, char c)
#include "conio.h" {
void HanoiTower(int, char, char, char); if(m>0)
{
void main()
HanoiTower(m-1,a,c,b);
{
printf("Chuyen dia %d tu %c sang
int m; %c\n",m,a,b);
printf("Nhap vao so dia m = ");
HanoiTower(m-1,c,b,a);
scanf("%d",&m);
}
printf("Ket qua cac buoc \n");
}
HanoiTower(m,'A','C','B');
getch();
}

38 @Copyright Dr. Ngo Huu Phuc, Le Quy Don Technical University


3.4. Tháp Hà nội (8/8)
Thời gian và giới hạn:
 Gọi T(n) là số phép chuyển đĩa trong giải thuật để
chuyển n đĩa.
 Từ giải thuật, có thể thấy:
T(n) = 0 nếu n < 0
T(n) = 2T(n-1) + 1 nếu n > 0
 Giải bài toán trên, ta có:
T(n) = 2n - 1 với mọi n > 0.
 Như vậy, T(n) là hữu hạn, và giải thuật sẽ dừng.

39/39 @Copyright by PhD. Ngo Huu Phuc, Le Quy Don Technical University
Cấu trúc dữ liệu và giải thuật
Bài 4: Kỹ thuật quay lui (Backtracking)

Lecturer: PhD. Ngo Huu Phuc


Tel: 0438 326 077
Mob: 098 5696 580
Email: ngohuuphuc76@gmail.com

1 @Copyright PhD. Ngo Huu Phuc, Le Quy Don Technical University


Bài 4. Kỹ thuật quay lui
Backtracking
Nội dung:
4.1. Khái niệm về kỹ thuật quay lui (6)

4.2. Bài toán 8 con hậu - Eight Queen Problem (7)

4.3. Bài toán mã đi tuần - Knight Tour Problem (5)

4.4. Bài toán chiếc ba lô - Knapsack Problem (6)

Tham khảo:
1. Lecture 11 – Backtracking.htm

2 @Copyright PhD. Ngo Huu Phuc, Le Quy Don Technical University


4.1. Khái niệm kỹ thuật quay lui (1/6)
 Kỹ thuật quay lui là quá trình phân tích từ trên xuống

trong không gian tìm kiếm.

 Trong trường hợp tổng quát, giả sử lời giải là một

vector:

a = (a[1], a[2], …, a[n])

trong đó, mỗi phần tử a[i] chọn từ tập hữu hạn S[i]
(các khả năng của a[i]).

3 @Copyright PhD. Ngo Huu Phuc, Le Quy Don Technical University


4.1. Khái niệm kỹ thuật quay lui (2/6)
 Ta s ẽ giải quyết bài toán với kích thước k, có dạng:

a = (a[1], a[2], …, a[k])


và cố gắng mở rộng bằng việc thêm phần tử tiếp theo
vào trong vector.
 Sau khi thêm phần tử, kiểm tra xem có thể thực hiện
tiếp được không.
 Nếu vẫn có khả năng mở rộng, tiếp tục thực hiện;

Nếu không, xóa phần tử a[k] và làm lại bài toán từ tập
S[k].
4 @Copyright PhD. Ngo Huu Phuc, Le Quy Don Technical University
4.1. Khái niệm kỹ thuật quay lui (3/6)
Gọi S[1], tập các khả năng của a tại bước đầu tiên.
k=1
While k > 0 do
While S[k]<>[] do (*advance*)
a[k] = an element in S[k]
If (a[1], a[2],…, a[k]) is solution, print it!
k=k+1
Tính S[k], tập khả năng của a tại bước k.
k = k - 1 (*backtrack*)

5 @Copyright PhD. Ngo Huu Phuc, Le Quy Don Technical University


4.1. Khái niệm kỹ thuật quay lui (4/6)
Sub Backtrack(a, k)
If a is a solution, get it
Else
k = k +1
compute S[k]
While S[k]<>[] do
a[k] = an element in S[k]
S[k] = S[k] – a[k]
Backtrack(a, k)
End Sub

6 @Copyright PhD. Ngo Huu Phuc, Le Quy Don Technical University


4.1. Khái niệm kỹ thuật quay lui (5/6)
 Kỹ thuật đệ quy có thể được dùng trong kỹ thuật
quay lui (đơn giản trong ứng dụng).
 Kỹ thuật quay lui chắc chắn đúng khi liệt kê các khả
năng có thể.
 Để tăng hiệu quả của kỹ thuật, có thể cắt bớt
không gian tìm kiếm.

7 @Copyright PhD. Ngo Huu Phuc, Le Quy Don Technical University


4.1. Khái niệm kỹ thuật quay lui (6/6)
Trong phần này, giải quyết một số bài toán, có sử dụng
kỹ thuật quay lui:

 Bài toán 8 hậu - Eight Queen Problem.

 Bài toán mã đi tuần - Knight Tour Problem.

 Bài toán chiếc ba lô - Knapsack Problem.

8 @Copyright PhD. Ngo Huu Phuc, Le Quy Don Technical University


4.2. Eight Queen Problem (1/7)
Bài toán: đặt 8 con hậu trên bàn cờ sao cho không có 2 con
hậu nằm trên cùng một hàng, cột và hàng ngang.

Một lời giải Không phải lời giải

9 @Copyright PhD. Ngo Huu Phuc, Le Quy Don Technical University


4.2. Eight Queen Problem (2/7)
Thực hiện:
• Trạng thái: Sắp đặt từ 0 đến 8
con hậu lên bàn cờ.
• Trạng thái khởi tạo: không có
con hậu nào đặt lên bàn cờ.
• Đặt từng con hậu lên bàn cờ.
• Kiểm tra kết quả: 8 con hậu đã
đặt lên bàn cờ, không có con
hậu nào ăn được nhau.

 648 cách đặt 8 con hậu


10 @Copyright PhD. Ngo Huu Phuc, Le Quy Don Technical University
4.2. Eight Queen Problem (3/7)
Một vài cách đặt 8 con hậu lên bàn cờ (trong số 92 lời giải)

11 @Copyright PhD. Ngo Huu Phuc, Le Quy Don Technical University


4.2. Eight Queen Problem (4/7)
Ý tưởng:
 Mỗi lần đệ quy, tìm cách đặt 1 con hậu lên một cột (hoặc
hàng) riêng.
 Với mỗi lần gọi, tình trạng bàn cờ đã biết (các con hậu đã
đặt)
 Nếu tại một cột, không đặt được con hậu mới, con hậu ở
cột (hàng) đó được bỏ ra khỏi bàn cờ và chuyển xuống
cột (hàng) trước đó.

12 @Copyright PhD. Ngo Huu Phuc, Le Quy Don Technical University


4.2. Eight Queen Problem (5/7)
 Nếu xét theo cột, tại một cột, tất cả các hàng đã
xét, quay trở lại bước trước đó (xét cột trước).
 Nếu con hậu không đặt được lên cột i, khi đó
không thử trên cột i+1, quay lại cột i-1, bỏ con
hậu đã đi sai.
 Với cách tiếp cận này, có thể giảm bớt phép thử.

13 @Copyright PhD. Ngo Huu Phuc, Le Quy Don Technical University


4.2. Eight Queen Problem (6/7)
#include "stdio.h" // In ma tran chua cac con hau
#include "conio.h" void printMatrix(int a[][N], int n) {
#include "math.h" int i, j;
#define N 8
for(i=0; i<n; i++) {
int k=0;
for(j=0; j<n; j++)
typedef enum {FALSE, TRUE} bools;
printf("%d ", a[i][j]);
void printMatrix(int a[][N], int n);
printf("\n"); }
int getMarkedCol(int a[][N], int n, int row);
bools feasible(int a[][N], int n, int row, int col); printf("\n"); }

void N_Queens(int a[][N], int n, int row); // Lay cac cot da danh dau trong hang row
void main() { int getMarkedCol(int a[][N], int n, int row) {
int a[N][N] ; int j;
int i=0, j=0; for(j=0; j<n; j++)
for(i=0; i<N; i++) if(a[row][j] == TRUE) return j;
for(j=0; j<N; j++) a[i][j]=0;
printf("Loi: Khong co cot danh dau trong hang
N_Queens(a,N,0); %d.\n", row);
getch(); return -1;
}
}

14 @Copyright Dr. Ngo Huu Phuc, Le Quy Don Technical University


4.2. Eight Queen Problem (6/7)
// Kiem tra xem con hau tiep theo co the dat o hang // Bai toan N hau
// row, cot col khong void N_Queens(int a[][N], int n, int row) {
bools feasible(int a[][N], int n, int row, int col) int j;
{
if(row < n) {
int i;
for(j=0; j<n; ++j)
int markedCol ;
if(feasible(a, n, row, j)) {
for(i=0; i<row; i++)
a[row][j] = TRUE;
{
markedCol = getMarkedCol (a,n,i); N_Queens (a, n, row+1);

if(markedCol == col || abs(row-i) == abs(col- a[row][j] = FALSE; }


markedCol)) }
return FALSE ;
else {
}
k++;
return TRUE;
printf("Trang thai thu %d\n",k);
}
printMatrix(a, n);
}
}

15 @Copyright Dr. Ngo Huu Phuc, Le Quy Don Technical University


4.3. Knight Tour Problem (1/5)

16 @Copyright PhD. Ngo Huu Phuc, Le Quy Don Technical University


4.3. Knight Tour Problem (2/5)
Bài toán:
 Trên bàn cờ 8 × 8 có một con mã (cách đi được
định nghĩa thông thường).
 Bắt đầu từ một góc của bàn cờ và thăm tất cả các
ô cờ khác (63 ô cờ còn lại), sao cho mỗi ô cờ chỉ
đến 1 lần.
 Bài toán này được gọi là “mã đi tuần”.

17 @Copyright PhD. Ngo Huu Phuc, Le Quy Don Technical University


4.3. Knight Tour Problem (3/5)
Cách tiếp cận bài toán:
 Có nhiều cách tiếp cận để giải bài
toán này.
 Một trong số đó là cách giải của J.C.
Warnsdorff được đưa ra năm 1823.
 Theo cách của J.C. Warnsdorff, con
mã đi theo theo thứ tự được mã hóa
(quy ước cách đi theo hình vẽ).
 Từ một vị trí, con mã có thể đi tối đa
đến 8 vị trí khác. Tùy theo vị trí trên
bàn cờ, các vị trí có thể này được mã
hóa theo thứ tự từ 1 đến 8.

18 @Copyright PhD. Ngo Huu Phuc, Le Quy Don Technical University


4.3. Knight Tour Problem (4/5)
#include "stdio.h" for(m=0;m<M;m++)
#include "conio.h" for(n=0;n<M;n++)
#include "stdlib.h“ a[m][n]=0 ;
#define M 5
a[i][j]=1;
#define SizeStep 8
knight(a, M, i, j, 2);
void printVariant(int a[][M], int n);
}
void knight(int a[][M], int n, int row, int col, int num);
getch();
//dinh nghia cach di cho con ma
int rowchange[] = {-2, -1, 1, 2, 2, 1,-1,-2}; }

int colchange[] = {-1, -2, -2,-1, 1, 2, 2, 1}; void printVariant(int a[][M], int n) {
void main() { // in ket qua
int a[M][M] ; int i, j;
int i=0, j=0; for(i=0; i<n; ++i) {
int m,n; for(j=0; j<n; j++)
for(i=0; i<M; i++)
printf("%2d ", a[i][j]);
for(j=0; j<M; j++)
printf("\n");
{
}
}
19 @Copyright Dr. Ngo Huu Phuc, Le Quy Don Technical University
4.3. Knight Tour Problem (5/5)
void knight(int a[][M], int n, int row, int col, int num) printf("\nNhan Enter de chon ket qua khac, nhan
{ ESC de thoat!\n");

// tim vi tri ke tiep if(char c=getch()==27)


// vi tri ke tiep co gia nho nhat exit(1);
// vi tri hien tai la (row,col) }
int i; else
for(i=0; i<SizeStep; i++) knight(a, n, newrow, newcol, num+1);
{
a[newrow][newcol]=0;
int newrow = row+rowchange[i];
}
int newcol = col+colchange[i];
}
if(newrow>=0 && newrow<n && newcol>=0
&& newcol<n && a[newrow][newcol]==0) }
{
a[newrow][newcol]=num;
if(num==n*n)
{
printVariant(a, M);

20 @Copyright Dr. Ngo Huu Phuc, Le Quy Don Technical University


4.4. Knapsack Problem (1/9)
 Bài toán:
 Cho N vật, mỗi vật có trọng lượng w(i) và giá trị v(i).
 Một chiếc ba lô có khả năng đựng tối đa (theo trọng
lượng) là K.
 Chọn các vật như thế nào để tổng giá trị của chúng là
lớn nhất và tổng trọng lượng không vượt quá K?
 Ý tưởng:
 Ta ch ọn các vật có giá trị cao nhất vào ba lô.
 Tuy nhiên, các vật có trọng lượng khác nhau, do đó cần
chọn một cách cẩn thận!!!

21 @Copyright PhD. Ngo Huu Phuc, Le Quy Don Technical University


4.4. Knapsack Problem (2/9)
 Trường hợp đơn giản:

 Giả sử các vật có cùng trọng lượng.

 Rõ ràng, dễ đưa ra lời giải!

Lấy các vật có giá trị lớn.

 Có thể sử dụng tỷ số giữa giá trị và trọng lượng và sử

dụng tỷ số này?

22 @Copyright PhD. Ngo Huu Phuc, Le Quy Don Technical University


4.4. Knapsack Problem (3/9)
 Xem xét ví dụ sau với K = 9.
Vật Trọng lượng Giá trị Tỷ lệ

1 3 7 7/3
2 6 16 8/3
3 7 19 19/7
4 5 15 3

 Với ví dụ trên, vật 4 có tỷ lệ cao nhất.


 Nếu ta lấy vật 4 này, khi đó, chỉ có thể đưa vật 1 thêm vào
ba lô. Tổng giá trị là: 7 + 15 = 22.
 Tuy nhiên, nếu chọn đúng, nên lấy vật 1 và vật 2, mỗi vật
chọn 1 lần, tổng giá trị là: 7 + 16 = 23.

23 @Copyright PhD. Ngo Huu Phuc, Le Quy Don Technical University


4.4. Knapsack 01 - Problem (4/9)
#include "conio.h" void main() {
#include "stdio.h" bools used[M];
#define M 10 bools pack[M];
typedef enum {FALSE, TRUE} bools;
int weght[M];
// Tham so
int value[M];
// K: trong luong toi da cua ba lo
int current[3];
// used[]: cac vat co trong ba lo,
int K;
// 0: chua lay, 1: da lay
// weight[]: trong luong cua tung vat for(int i=0;i<M;i++)

// value[]: gia tri cua tung vat used[i]=pack[i]=FALSE;


// current[0]: trong luong hien tai current[0]=current[1]=current[2]=0;
// current[1]: gia tri hien tai int n;
// current[2]: gia tri max printf("Nhap so vat: ");
// pack[]: cac vat da lay o trang thai toi uu scanf("%d",&n);
int nCalls=0;
printf("Nhap trong luong toi da cho ba lo:");
void knapSack(int K, bools used[], int item, int
weight[], int value[], bools pack[], int current[], int scanf("%d",&K);
n);

24 @Copyright Dr. Ngo Huu Phuc, Le Quy Don Technical University


4.4. Knapsack 01 - Problem (5/9)
for(int i=0;i<n;i++) void knapSack(int K, bools used[], int item, int
{ weight[], int value[], bools pack[], int current[], int n){

printf("Nhap trong luong vat %d:",i); nCalls++; // So loi goi


scanf("%d",&weght[i]); // Kiem tra da xem xet den vat cuoi
printf("Nhap gia tri vat %d:",i); if (item == n)
scanf("%d",&value[i]); {
} // Kiem tra lai truong hop da lay vat
knapSack(K,used,0,weght,value,pack,current,n);
if (current[1] > current[2])
{
printf("Tong so lan goi ham: %d\n",nCalls);
printf("Tong gia tri hien tai: %d\n",current[1]);
// Luu thong tin hien tai
getch();
} current[2] = current[1];
for (int k = 0; k < n; k++)
pack[k] = used[k];
printf("Cac vat duoc chon: ");
for(int i=0;i<n;i++)

25 @Copyright Dr. Ngo Huu Phuc, Le Quy Don Technical University


4.4. Knapsack 01 - Problem (6/9)
if(pack[i]) printf("%d, ",i); current[1] -= value[item];
printf("\n"); }
} // Khong lay duoc vat nay, tiep tuc thu vat khac.
}
knapSack(K, used, item+1, weight, value, pack,
else current, n);
{ }
// Khi chua kiem tra het cac vat }
if (current[0] + weight[item] <= K)
{
// Lay vat item nay
used[item] = TRUE;
current[0] += weight[item];
current[1] += value[item];
knapSack(K, used, item+1, weight, value,
pack, current, n);
// Khi khong lay vat nay
used[item] = FALSE;
current[0] -= weight[item];

26 @Copyright Dr. Ngo Huu Phuc, Le Quy Don Technical University


4.4. Knapsack Problem (7/9)
#include "conio.h" void main() {
#include "stdio.h" int used[M];
#define M 10 int pack[M];
// Tham so
int weght[M];
// K: trong luong toi da cua ba lo
int value[M];
// used[]: cac vat co trong ba lo,
int current[3];
// 0: chua lay, 1: da lay
int K;
// weight[]: trong luong cua tung vat
// value[]: gia tri cua tung vat for(int i=0;i<M;i++)

// current[0]: trong luong hien tai used[i]=pack[i]=0;


// current[1]: gia tri hien tai current[0]=current[1]=current[2]=0;
// current[2]: gia tri max int n;
// pack[]: cac vat da lay o trang thai toi uu printf("Nhap so vat: ");
int nCalls=0; scanf("%d",&n);
void knapSack(int K, int used[], int item, int weight[],
printf("Nhap trong luong toi da cho ba
int value[], int pack[], int current[], int n);
lo:");
scanf("%d",&K);

27 @Copyright Dr. Ngo Huu Phuc, Le Quy Don Technical University


4.4. Knapsack Problem (8/9)
for(int i=0;i<n;i++) void knapSack(int K, int used[], int item, int weight[],
{ int value[], int pack[], int current[], int n)

printf("Nhap trong luong vat %d:",i); {


scanf("%d",&weght[i]); nCalls++; // So loi goi
printf("Nhap gia tri vat %d:",i); // Kiem tra da xem xet den vat cuoi
scanf("%d",&value[i]); if (item == n)
} {
knapSack(K,used,0,weght,value,pack,current,n);
// Kiem tra lai truong hop da lay vat
if (current[1] > current[2])
printf("Tong so lan goi ham: %d\n",nCalls);
{
printf("Tong gia tri hien tai: %d\n",current[1]);
getch();
} // Luu thong tin hien tai
current[2] = current[1];
for (int k = 0; k < n; k++)
pack[k] = used[k];
printf("Cac vat duoc chon: ");

28 @Copyright Dr. Ngo Huu Phuc, Le Quy Don Technical University


4.4. Knapsack Problem (9/9)
for(int i=0;i<n;i++) current[0] -= weight[item];
if(pack[i]) printf("Vat %d, so lan chon %d current[1] -= value[item];
",i,pack[i]);
}
printf("\n");
// Neu khong lay vat do lan nao ca
}
knapSack(K, used, item+1, weight, value, pack,
}
current, n);
else
}
{
}
// Thu xem co the lay bao nhieu vat
used[item] = (K - current[0]) / weight[item];
current[0] += used[item] * weight[item];
current[1] += used[item] * value[item];
while ( used[item] > 0 )
{
knapSack(K, used, item+1, weight, value,
pack, current, n);
// Bo mot vat cuoi ra, neu khong lay duoc
used[item]--;

29/29 @Copyright Dr. Ngo Huu Phuc, Le Quy Don Technical University
Cấu trúc dữ liệu và giải thuật
Bài 5. Phương pháp sắp xếp đơn giản

Lecturer: PhD. Ngo Huu Phuc


Tel: 0438 326 077
Mob: 098 5696 580
Email: ngohuuphuc76@gmail.com

1 Ngo Huu Phuc, Le Quy Don Technical University


Bài 5: Các phương pháp sắp xếp đơn giản

Nội dung:
6.1. Khái niệm và vai trò của sắp xếp (13)
6.2. Sắp xếp chèn (6)
6.3. Sắp xếp chọn (4)
6.4. Sắp xếp nổi bọt (4)
Tham khảo:
1. Lecture 16 Introduction to Sorting.htm
2. Data Structures and Algorithms Sorting.htm
3. Tham khảo bài giảng của TS Nguyễn Nam Hồng

2 Ngo Huu Phuc, Le Quy Don Technical University


5.1. Khái niệm và vai trò của sắp xếp
5.1.1. Các thuật toán sắp xếp.

5.1.2. Vai trò của sắp xếp.

5.1.3. Các vấn đề của sắp xếp.

5.1.4. Một số ứng dụng của sắp xếp.

5.1.5. Ý tưởng sắp xếp và phương pháp thực hiện.

5.1.6. Phân tích hiệu quả của giải thuật sắp xếp.

3 Ngo Huu Phuc, Le Quy Don Technical University


5.1.1. Các thuật toán sắp xếp (1/2)
 Thế nào là sắp xếp?

 Đưa một dãy các đối tượng về dạng thứ bậc nào đó.

 Giải thuật sắp xếp dựa trên sự so sánh nào đó.

 Việc sắp xếp chỉ dựa trên phép toán so sánh.

 Các phép toán cơ bản của sắp xếp.

 So sánh.

 Tráo đổi giữa các phần tử.

4 Ngo Huu Phuc, Le Quy Don Technical University


5.1.1. Các thuật toán sắp xếp (1/2)
 Quy ước.

 Phương pháp sắp xếp của chương này là sắp xếp


trong.
 Các giải thuật có thể thay thế cho nhau được.

 Mỗi mảng có một số phần tử.

 Thành phần để xem xét trong sắp xếp có thể so


sánh được.
 N là số phần tử cần sắp xếp.

5 Ngo Huu Phuc, Le Quy Don Technical University


5.1.2. Vai trò của sắp xếp (1/2)
 Nếu các đối tượng trong một mảng nào đó đã được sắp
theo trật tự nào đó, có thể truy xuất thông tin nhanh chóng
và chính xác.
 Việc xây dựng giải thuật cho phép sắp xếp từng phần tử
của mảng sẽ mất nhiều thời gian, độ phức tạp của giải
thuật cỡ O(n2).
 ≈ 50,000,000,000,000 bước cho việc sắp một mảng có 10,000,000
phần tử
⇒ 500,000 giây = 58 ngày, v ới máy tính có thể thực hiện 100 triệu
phép tính toán/giây.
 Với giải thuật sắp xếp cho cả mảng, độ phức tạp của giải
thuật cỡ O(nlogn).
 ≈ 250,000,000 bước cho việc sắp một mảng có 10,000,000 phần tử
⇒ 2.5 giây, với máy tính có thể thực hiện 100 triệu phép tính
toán/giây.
6 Ngo Huu Phuc, Le Quy Don Technical University
5.1.2. Vai trò của sắp xếp (2/2)
 Như vậy, sắp xếp là giải thuật cơ bản.
 Thông thường, 25% khả năng của CPU dành
cho việc sắp xếp.
 Sắp xếp là bước cơ bản cho một số giải thuật
khác, ví dụ: tìm kiếm nhị phân.
 Có nhiều cách tiếp cận đến giải thuật sắp xếp,
từ đó, có nhiều giải thuật săp xếp khác nhau.

7 Ngo Huu Phuc, Le Quy Don Technical University


5.1.3. Các vấn đề của sắp xếp
 Sắp xếp theo trật tự tăng hay giảm?
 Với cùng một giải thuật sắp xếp, có thể dùng cho cả sắp
theo trật tăng hay giảm, bằng việc thay đổi phép so sánh:
<= và >=.
 Các khóa của giải thuật sắp xếp?
 Có thể dùng nhiều khóa cho cùng một giải thuật sắp xếp.
Cần lưu ý đến ý tưởng của bài toán.
 Với các dữ liệu không phải là số thì sao?
 Với chuỗi, sử dụng phép so sánh chuỗi, từ điển, hay quy
tắc nào đó.
 Ví dụ: Sắp chuỗi Brown-Williams, Brown America, Brown,
John?

8 Ngo Huu Phuc, Le Quy Don Technical University


5.1.4. Một số ứng dụng của sắp xếp (1/2)

 Với kết quả của quá trình sắp xếp, một số vấn đề

có thể được thực hiện dễ dàng.

 Nói chung, nếu có quá trình sắp xếp sẽ tăng tốc

cho tìm kiếm trong ứng dụng cụ thể.

 Ví dụ, nếu có một mảng đã sắp, có thể dễ dàng

tìm được phần tử lớn thứ k trong mảng, với thời


gian hằng số.

9 Ngo Huu Phuc, Le Quy Don Technical University


5.1.4. Một số ứng dụng của sắp xếp (2/2)
Một số ứng dụng có dùng sắp xếp.
 Các từ trong từ điển đã được sắp xếp.
 Thông thường, các Files trong thư mục được sắp
theo một trật tự nào đó.
 Trong thư viện, các quyển sách được sắp theo
một trật tự nào đó.
 Các khóa học của một trường đại học được sắp
theo khoa, theo mã của khóa học.
 Các sự kiện được sắp theo thời gian.
 …

10 Ngo Huu Phuc, Le Quy Don Technical University


5.1.5. Ý tưởng sắp xếp và phương pháp
thực hiện (1/2)
Có nhiều ý tưởng cho giải thuật sắp xếp:
 Chèn - Insertion: đặt các phần tử vào một vị trí thích hợp
của một dãy các phần tử đã sắp.
 Tráo đổi - Exchange: với mỗi cặp các phần tử, tráo đổi
chúng về đúng thứ tự, thực hiện cho đến khi không còn cặp
nào chưa đưa về đúng thứ tự.
 Chọn - Selection: chọn phần tử lớn nhất (nhỏ nhất) trong
danh sách, đưa về đúng vị trí.
 Phân loại - Distribution: chia nhỏ thành nhiều mảnh dựa
trên tiêu chí nào đó, sau đó sắp từng mảnh.
 Hợp - Merging: có thể nối 2 dãy đã sắp xếp thành 1 dãy
được sắp xếp.

11 Ngo Huu Phuc, Le Quy Don Technical University


5.1.5. Ý tưởng sắp xếp và phương pháp
thực hiện (2/2)
Phương pháp sắp xếp:
 Sắp xếp chọn:
 Tìm phần tử lớn nhất (nhỏ nhất) trong số các phần tử chưa
xét và đặt chúng vào đúng vị trí.
 Thực hiện cho đến khi các phần tử đều được xét.
 Sắp xếp chèn:
 Với mỗi phần tử, chèn chúng vào mảng con đã sắp đúng trật
tự.
 Thực hiện tương tự cho các phần tử còn lại.
 So sánh và tráo đổi – Sắp xếp nổi bọt:
 Tìm 2 phần tử trong dãy không đúng trật tự, tráo đổi vị trí của
chúng.
 Giữ lại danh sách đã hiệu chỉnh.

12 Ngo Huu Phuc, Le Quy Don Technical University


5.1.6. Phân tích hiệu quả của giải thuật
sắp xếp (1/2)
 Sự hiệu quả:
 Với trường hợp tồi nhất của giải thuật, độ phức tạp là
thế nào?
 Trong trường hợp trung bình có tốt hơn trường hợp tồi
nhất không?
 Yêu cầu của dữ liệu là gì?
 Có thể truy xuất ngẫu nhiên được không?
 Có cần thêm thao tác nào không ngoài “so sánh” và
“tráo đổi”?
 Không gian bộ nhớ sử dụng:
 Thuật toán có cần thêm không gian phụ nào nào không?

13 Ngo Huu Phuc, Le Quy Don Technical University


5.1.6. Phân tích hiệu quả của giải thuật
sắp xếp (2/2)

 Tính ổn định:

 Thuật toán có tính ổn định không?

 Sự hiệu quả, nếu dãy đã gần sắp:

 Thuật toán có hiệu quả hơn nếu như dãy đã cho

gần được sắp? (nhiều phần tử đã đúng thứ tự,


chỉ có một số chưa đúng thứ tự)

14 Ngo Huu Phuc, Le Quy Don Technical University


5.2. Sắp xếp chèn (1/6)
Ví dụ minh họa:
 Xem xét người chơi bài (sắp theo chiều giảm dần)
 Quân bài thứ nhất được sắp.
 Với các quân bài còn lại:
 Tìm từ cuối dãy cho đến khi tìm thấy quân bài lớn hơn quân bài
mới,
 Di chuyển các quân bài nhỏ về sau một bậc.
 Chèn quân bài mới vào vị trí đó.

         
A K 10 2 J 2 2 Q 9

9 
15 Ngo Huu Phuc, Le Quy Don Technical University
5.2. Sắp xếp chèn (2/6)
 Đây là phương pháp sắp hiệu quả nếu có ít phần tử cần
sắp.
 Mã lệnh cho phương pháp khá đơn giản.

 Cách thức làm việc của Sắp xếp chèn.


 Sử dụng biến để chia tập thành 2 vùng: vùng đẵ sắp và vùng chưa
sắp.
 Vùng đã sắp bắt đầu từ phần tử đầu tiên của dãy.

 Lấy phần tử đầu tiên của vùng chưa sắp và chèn vào vùng đã sắp.

 Như vậy, vùng đã sắp sẽ có thêm một phần tử.

 Thực hiện tương tư cho đến khi vùng chưa sắp không còn phần tử
nào.
16 Ngo Huu Phuc, Le Quy Don Technical University
5.2. Sắp xếp chèn (3/6)
Giả sử: Sắp xếp dãy
các số nguyên theo
Chuỗi đã sắp Phần tử sẽ
được chèn
phương pháp chèn.

8 5 2 6 9 4 6
Giá trị 5 nhỏ hơn 8, 5 sẽ thay thế cho 8, dãy đã sắp sẽ có kích thước
tăng lên 1 (có 2 phần tử đã được sắp.

Hoạt động của


Insertion Sort
5 8 2 6 9 4 6

17 Ngo Huu Phuc, Le Quy Don Technical University


5.2. Sắp xếp chèn (4/6)
#include <stdio.h> void inputdata(int* list,int n)
#include <conio.h> {
#define MAX 100 int i;
void inputdata(int* list,int n);
printf("Nhap cac phan tu cua mang\n");
void printlist(int* list,int n);
for(i=0;i<n;i++)
void insertionsort(int* list, int n);
scanf("%d",&list[i]);
void main() {
fflush(stdin);
int list[MAX], n;
printf("Nhap so phan tu cua mang, toi da = }
100\n"); void printlist(int* list,int n)
scanf("%d",&n); {
inputdata(list,n);
int i;
printf("Mang da nhap:\n");
printf("Cac phan tu cua mang: \n");
printlist(list,n);
for(i=0;i<n;i++)
insertionsort(list,n);
printf("%d\t",list[i]);
printf("Mang da sap xep:\n");
printlist(list,n); printf("\n");

getch(); } }

18 Ngo Huu Phuc, Le Quy Don Technical University


5.2. Sắp xếp chèn (5/6)
void insertionsort(int *list, int n)
{
int pos,i,x;
for(i=1;i<n;i++)
{
x=list[i];
pos=i-1;
while((pos>=0) && (list[pos]>x))
{
list[pos+1]=list[pos];
pos--;
}
list[pos+1]=x;
}
}

19 Ngo Huu Phuc, Le Quy Don Technical University


5.2. Sắp xếp chèn (6/6)
Phân tích sắp xếp chèn:
 Trường hợp tồi nhất:
 Với dữ liệu dạng nào cho kết quả tồi nhất?
 Dữ liệu được sắp theo chiều ngược lại.
 Độ phức tạp trong trường hợp này?
 O(N2) – phép so sánh: N2, phép tráo đổi: N2
 Trường hợp tốt nhất:
 Với dữ liệu dạng nào cho kết quả tốt nhất?
 Dữ liệu đã được sắp.
 Độ phức tạp trong trường hợp này?
 O(N) – phép so sánh: N, phép tráo đổi: none
 Trường hợp trung bình:
 Với dữ liệu dạng nào cho kết quả trung bình?
 Dữ liệu dạng ngẫu nhiên.
 Độ phức tạp trung bình?
 O(N2) – phép so sánh: N2, phép tráo đổi: N2

20 Ngo Huu Phuc, Le Quy Don Technical University


5.3. Sắp xếp chọn (1/5)
 Phương pháp sắp xếp chọn - Selection Sort
 Đây cũng là phương pháp sắp xếp đơn giản.
 Tại mỗi bước, chọn phần tử lớn nhất (nhỏ nhất) trong số
phần tử chưa sắp và đưa về đúng vị trí.
 Ý tưởng: (với sắp xếp tăng dần)
 Bước 1: Tìm phần tử nhỏ nhất trong dãy và đưa nó về vị
trí đầu tiên của dãy.
 Bước 2: Tìm phần tử nhỏ thứ hai và đưa nó về vị trí thứ 2
trong dãy.
 Bước tiếp: Lặp lại quá trình trên cho đến khi tất các phần
tử đã sắp đúng thứ tự.

21 Ngo Huu Phuc, Le Quy Don Technical University


5.3. Sắp xếp chọn (2/5)
Giả sử, cho 5 phần tử như sau, chưa sắp. Sắp lại dãy số trên theo
phương pháp sắp xếp chọn.
Tìm được phần tử có giá trị 2,
nhỏ nhất trong dãy.

6 4 2 9 3

Tráo đổi nó với phần tử đầu tiên


của dãy, phần tử có giá trị 6.

2 4 6 9 3 Tìm phần tử nhỏ nhất trong số


những phần tử chưa sắp, phần tử
có giá trị 3 và trao đổi nó với
phần tử đứng thứ 2 trong dãy, có
giá trị 4.
2 4 6 9 3
Hoạt động của Selection Sort
22
cách trực tiếp.
Ngo Huu Phuc, Le Quy Don Technical University
5.2. Sắp xếp chọn (3/5)
#include "stdio.h" void inputdata(int list[],int n)
#include "conio.h" {
#define MAX 100 int i;
void swap(int *x,int *y);
printf("Nhap cac phan tu cua mang\n");
void selectionsort(int list[], int n);
for(i=0;i<n;i++)
void inputdata(int list[],int n);
scanf("%d",&list[i]);
void printlist(int list[],int n);
fflush(stdin);
void main() {
int list[MAX], n; }

printf("Nhap so phan tu cua mang\n"); void printlist(int list[],int n)


scanf("%d",&n); {
inputdata(list,n); int i;
printf("Mang da nhap:\n"); printf("Cac phan tu cua mang: \n");
printlist(list,n); for(i=0;i<n;i++)
selectionsort(list,n);
printf("%d\t",list[i]);
printf("Mang da sap xep:\n");
printf("\n");
printlist(list,n);
}
getch(); }

23 Ngo Huu Phuc, Le Quy Don Technical University


5.2. Sắp xếp chọn (4/5)
void swap(int *x,int *y) {
int temp;
temp = *x;
*x = *y;
*y = temp; }
void selectionsort(int list[], int n) {
int i,j,minpos;
for(i=0;i<(n-1);i++)
{
minpos=i;
for(j=i+1;j<n;j++)
if (list[j]<list[minpos])
minpos=j;
if(minpos!=i)
swap(&list[minpos],&list[i]);
}
}
24 Ngo Huu Phuc, Le Quy Don Technical University
5.3. Sắp xếp chọn (5/5)
Phân tích hiệu quả của sắp xếp chọn:
 Độ phức tạp:
 Trong trường hợp tồi nhất: O(n2)

 Trung bình, độ phức tạp cũng là O(n2).

 Thuật toán thích hợp với sắp xếp dãy, với mỗi phần tử

trong dãy có kích thước lớn, vì đòi hỏi ít phép tráo đổi.
 Với dữ liệu đã gần được sắp, thuật toán không cho

thấy chúng tốt hơn.

25 Ngo Huu Phuc, Le Quy Don Technical University


5.4. Sắp xếp nổi bọt - Bubble Sort (1/5)
 Sắp xếp nổi bọt là kỹ thuật sắp xếp đơn giản, trong kỹ
thuật này tổ chức các phần tử của mảng thành các
cặp liền kề.
 Với các cặp liền kề, thứ i và thứ i+1, đổi chỗ các phần
tử này nếu chúng không đúng trật tự sắp xếp. Sau mỗi
lần thực hiện việc đổi chỗ, ta thu được 1 phần tử đã ở
đúng vị trí.
 Thực hiện lặp lại quá trình trên cho n-1 phần tử còn lại.

 Thuật toán dừng khi không còn cặp nào sai vị trí.

26 Ngo Huu Phuc, Le Quy Don Technical University


5.4. Sắp xếp nổi bọt (2/5)

3 2 2 2 2 2 2
2 3 3 3 3 3 1
4 4 4 1 1 1 3
1 1 1 4 4 4 4
5 5 5 5 5 5 5

2 1 1
1 2 2
3 3 3
4 4 4
5 5 5

27 Ngo Huu Phuc, Le Quy Don Technical University


5.2. Sắp xếp nổi bọt (3/5)
#include <stdio.h> void inputdata(int list[],int n)
#include <conio.h> {
#define MAX 100 int i;
void inputdata(int list[],int n);
printf("Nhap cac phan tu cua mang\n");
void printlist(int list[],int n);
for(i=0;i<n;i++)
void swap(int *x,int *y);
scanf("%d",&list[i]);
void bubblesort(int list[], int n);
fflush(stdin);
void main() {
int list[MAX], n; }

printf("Nhap so phan tu cua mang\n"); void printlist(int list[],int n)


scanf("%d",&n); {
inputdata(list,n); int i;
printf("Mang da nhap:\n"); printf("Cac phan tu cua mang: \n");
printlist(list,n); for(i=0;i<n;i++)
bubblesort(list,n);
printf("%d\t",list[i]);
printf("Mang da sap xep:\n");
printf("\n");
printlist(list,n);
}
getch(); }

28 Ngo Huu Phuc, Le Quy Don Technical University


5.2. Sắp xếp nổi bọt (4/5)
void swap(int *x,int *y) {
int temp;
temp = *x;
*x = *y;
*y = temp;
}
void bubblesort(int list[], int n)
{
int i,j;
for(i=0;i<(n-1);i++)
for(j=n-1;j>i;j--)
if(list[j]<list[j-1])
swap(&list[j],&list[j-1]);
}

29 Ngo Huu Phuc, Le Quy Don Technical University


5.4. Sắp xếp nổi bọt (5/5)
Phân tích hiệu quả của thuật toán sắp xếp nổi bọt:
 Độ phức tạp:
 Trong trường hợp tồi nhất: O(n2)

 Trong trường hợp trung bình: O(n2)

 Đối với dữ liệu:


 Dữ liệu cho phép truy cập ngẫu nhiên.

 Cần có phép toán so sánh và tráo đổi.

 Đối với dữ liệu đã gần được sắp:


 Số phép tráo đổi có thể ít, nhưng số phép so sánh nhiều. Tổng quát,

không tốt hơn so với dữ liệu random.

30 Ngo Huu Phuc, Le Quy Don Technical University


Nhận xét
 Trong bài 5, giới thiệu một số thuật toán sắp xếp đơn giản.

 Sắp xếp chèn,

 Sắp xếp chọn,

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

 Các giải thuật này được thực hiện với dữ liệu cho phép truy

cập ngẫu nhiên (sắp xếp trong).

 Độ phức tạp trong trường hợp trung bình là O(n2).

 Trong bài 6, giới thiệu một số giải thuật sắp xếp cho hiệu

quả cao hơn.


31 Ngo Huu Phuc, Le Quy Don Technical University
Cấu trúc dữ liệu và giải thuật
Bài 6. Sắp xếp nhanh - Quick Sorts

Lecturer: PhD. Ngo Huu Phuc


Tel: 0438 326 077
Mob: 098 5696 580
Email: ngohuuphuc76@gmail.com

1 Ngo Huu Phuc, Le Quy Don Technical University


Bài 6. Quick Sorts
Nội dung:
6.1. Thuật toán QuickSort (6)
6.2. Ví dụ về QuickSort (7)
6.3. Hoạt động của QuickSort (6)
6.4. Hiệu quả của QuickSort (6)

Tham khảo:
1. Intro to Algorithms Chapter 8 QuickSort.htm
2. Lecture 5 – quicksort.htm
3. Quick Sort.htm
4. Bài giảng của TS Nguyễn Nam Hồng
2 Ngo Huu Phuc, Le Quy Don Technical University
6.1. Thuật toán QuickSort (1/6)
 Giải thuật Quick-sort là
phương pháp sắp xếp dựa
trên chiến lược chia để trị. x
 Giải thuật gồm các bước:
 Phép chia: chọn ngẫu nhiên
một phần tử x làm khóa,
chia tập dữ liệu S ban đầu
thành 3 phần: x
 L chứa các phần tử nhỏ hơn x
 E chứa các phần tử bằng x L E G
 G chứa các phần tử lớn hơn x
 Bước lặp: sắp xếp 2 tập L
và G
 Hiệu chỉnh lại các tập L, E x
và G
3 Ngo Huu Phuc, Le Quy Don Technical University
6.1. Thuật toán QuickSort (2/6)
Các bước cơ bản của thuật toán:

 Chia tập dữ liệu ban đầu thành 2 tập con:

 sao cho, tất cả các phần tử bên trái nhỏ hơn tất

cả các phần tử bên phải.

 Sắp xếp 2 tập con một cách độc lập và nối chúng

lại với nhau:


 như vậy, ta được dãy đã sắp xếp.

4 Ngo Huu Phuc, Le Quy Don Technical University


6.1. Thuật toán QuickSort (3/6)
 Với mỗi tập con trên, mỗi tập chia thành 02 tập
con nếu có thể
 như vậy, ta có tối đa 04 tập con.

 tập các phần tử nhỏ nhất ở bên trái cùng, tập


các phần tử lớn nhất ở bên phải cùng.
 Lặp lại quá trình trên cho đến khi tập chỉ có 1
phần tử
 nối tất cả các tập với nhau ta được dãy đã sắp
xếp.
5 Ngo Huu Phuc, Le Quy Don Technical University
6.1. Thuật toán QuickSort (4/6)
 Ta chia t ập dữ liệu ban đầu như sau:
 Trên tập S, lấy mỗi phần tử y được lấy ra khỏi
tập
 Đưa phần tử y vào tập L, E hay G, tùy thuộc
vào phép so sánh với khóa x
 Với mỗi phép lấy 1 phần tử và đưa chúng vào tập
tương ứng, độ phức tạp của phép toán đó là
O(1).
 Như vậy, phép chia dữ liệu của thuật toán
QuickSort có độ phức tạp O(n).

6 Ngo Huu Phuc, Le Quy Don Technical University


6.1. Thuật toán QuickSort (5/6)
7 4 9 6 2 → 2 4 6 7 9

4 2 → 2 4 7 9 → 7 9

2→2 9→9

7 Ngo Huu Phuc, Le Quy Don Technical University


6.1. Thuật toán QuickSort (6/6)
Việc thực thi giải thuật
QuickSort được mô tả qua
cây nhị phân:
 Mỗi node biểu diễn một
lần gọi đệ quy và lưu giữ:
 Dãy chưa được sắp xếp và
khóa.
 Dãy sau khi sắp xếp.
 Gốc của cây là lần gọi đệ
quy đầu tiên.
 Các lá của cây là lần gọi
ứng với dãy con có kích
thước 0 hoặc 1.

8 Ngo Huu Phuc, Le Quy Don Technical University


6.2. Ví dụ về QuickSort (1/7)
 Chọn khóa

9 Ngo Huu Phuc, Le Quy Don Technical University


6.2. Ví dụ về QuickSort (2/7)
 Phân chia dãy ban đầu bằng gọi đệ quy với khóa đã chọn.

10 Ngo Huu Phuc, Le Quy Don Technical University


6.2. Ví dụ về QuickSort (3/7)
 Gọi đệ quy cho dãy con, với khóa mới

11 Ngo Huu Phuc, Le Quy Don Technical University


6.2. Ví dụ về QuickSort (4/7)
 Tương tự, gọi đệ quy, sau đó nối kết quả.

12 Ngo Huu Phuc, Le Quy Don Technical University


6.2. Ví dụ về QuickSort (5/7)
 Tương tự cho phần bên phải.

13 Ngo Huu Phuc, Le Quy Don Technical University


6.2. Ví dụ về QuickSort (6/7)
 Gọi đệ quy cho dãy con, với khóa mới

14 Ngo Huu Phuc, Le Quy Don Technical University


6.2. Ví dụ về QuickSort (7/7)
 Nối kết quả cho gốc của cây.

15 Ngo Huu Phuc, Le Quy Don Technical University


6.3. Hoạt động của QuickSort (1/6)
Có thể mô tả như sau:
 Với tập ban đầu, chọn 1 phần tử làm khóa.

 Đổi chỗ phần tử đầu tiên với khóa.

 Sử dụng 2 chỉ số i và j để duyệt phần còn lại trên dãy.

 Chỉ số i tăng đến khi tại vị trí i đó, phần tử này có giá trị lớn hơn
khóa. Chỉ số j giảm đến khi giá trị tại vị trí j nhỏ hơn khóa.
 So sánh giữa i và j, nếu i<j, đổi chỗ 2 phần tử này cho nhau.
Nếu không, đổi chỗ khóa (phần tử đầu tiên) với phần tử j.
 Khi đó, có thể chia mảng đã cho thành 2 mảng con, mảng thứ
nhất từ vị trí 1 đến vị trí j-1, mảng thứ 2 từ vị trí j+1 đến n. Sau
đó có thể lặp lại quá trình trên cho các mảng con.

16 Ngo Huu Phuc, Le Quy Don Technical University


6.3. Hoạt động của QuickSort (2/6)
Chọn khóa:
 Ta có th ể chọn bất kỳ phần tử nào trong mảng làm khóa.
 Nếu chọn phần tử đầu tiên của mảng làm khóa, lựa chọn này là tồi
nếu mảng đã sắp xếp. Khi đó, một mảng con có 0 phần tử. Thông
thường, chọn khóa gần với giữa của mảng với hi vọng lựa chọn này
sẽ cho 2 mảng con có số phần tử gần bằng nhau.
Hàm chọn vị trí có dạng: Việc chọn này cũng tùy ý, có
int getkeyposition(int i,int j) thể sử dụng hàm random để
chọn khóa. Hàm này có dạng:
{
int getkeyposition(int i, int j)
return(( i+j )/ 2);
{
}
return(random number in the
range of i to j);
}
17 Ngo Huu Phuc, Le Quy Don Technical University
6.3. Hoạt động của QuickSort (3/6)
Xây dựng hàm đệ quy:
 Hàm đệ quy cho giải thuật có thể gọi
QSort(S, a, b)
sắp dãy con trong dãy ban đầu S tử chỉ số a đến
chỉ số b.
 Với QSort(L, 0, n-1) sắp mảng L từ phần tử
đầu tiên đến phần tử cuối n-1.
 QSort(S, a, b) sẽ được gọi đệ quy cho các
mảng con nhỏ hơn, với chỉ số thay đổi.

18 Ngo Huu Phuc, Le Quy Don Technical University


6.3. Hoạt động của QuickSort (4/6)
Chia dữ liệu:
 Với khóa đã chọn x
 Tổ chức lại tập S sao cho:
 S[a]…S[t-1] là các phần tử < x
 S[t] = x
 S[t+1]…S[b] là các phần tử > p

19 Ngo Huu Phuc, Le Quy Don Technical University


6.3. Hoạt động của QuickSort (5/6)
#include "stdio.h" void inputdata(int list[],int n)
#include "conio.h" {
#define MAX 100 int i;
void swap(int *x,int *y);
printf("Nhap cac phan tu cho mang\n");
int getkeyposition(int i,int j);
for(i=0;i<n;i++)
void qsort(int list[],int m,int n);
scanf("%d",&list[i]);
void inputdata(int list[],int n);
fflush(stdin);
void printlist(int list[],int n);
void main() { }

int list[MAX], n; void printlist(int list[],int n)


printf("Nhap so phan tu cho mang\n"); {
scanf("%d",&n); int i;
inputdata(list,n); printf("Cac phan tu trong mang: \n");
printf("Cac phan tu da nhap:\n"); for(i=0;i<n;i++)
printlist(list,n);
printf("%d\t",list[i]);
qsort(list,0,n-1);
printf("\n");
printf("\nCac phan tu da sap xep:\n");
}
printlist(list,n); getch(); }

20 Ngo Huu Phuc, Le Quy Don Technical University


6.3. Hoạt động của QuickSort (6/6)
void swap(int *x,int *y) key = list[m];
{ i = m+1;
int temp; j = n;
temp = *x;
while(i <= j)
*x = *y;
{
*y = temp;
while((i <= n) && (list[i] <= key))
}
i++;
int getkeyposition(int i,int j )
{ while((j >= m) && (list[j] > key))

return((i+j) /2); j--;


} if( i < j)
void qsort(int list[],int m,int n) swap(&list[i],&list[j]);
{ }
int key,i,j,k; swap(&list[m],&list[j]);
if( m < n)
qsort(list,m,j-1);
{
qsort(list,j+1,n);
k = getkeyposition(m,n);
}
swap(&list[m],&list[k]);
}
21 Ngo Huu Phuc, Le Quy Don Technical University
6.4. Hiệu quả của Quicksort (1/6)
Trong trường hợp tốt nhất:
 Giả sử, tại mỗi lần chia, tập S được chia thành 2 tập con

có kích thước gần bằng nhau.

 Nếu số phần tử là n, lần đầu tiên sẽ chia thành 2 tập con

có kích thước gần bằng n/2.

 Tiếp theo, mỗi tập con lại được chia thành 2 tập con có

kích thước gần bằng n/4, thực hiện điều này đến khi kích
thước mỗi mảng bằng 1.

22 Ngo Huu Phuc, Le Quy Don Technical University


6.4. Hiệu quả của Quicksort (2/6)
Trong trường hợp tốt nhất (tiếp):
 Gọi T(n) là thời gian cần thiết để sắp n phần tử.

 Vì thời gian để sắp n phần tử là tổng thời gian cần để đưa

khóa về đúng vị trí và thời gian để sắp 2 tập con có kích


thước gần bằng n/2. Do đó:

T(n) = c*n + 2*T(n/2)

23 Ngo Huu Phuc, Le Quy Don Technical University


6.4. Hiệu quả của Quicksort (3/6)
Trong trường hợp tốt nhất (tiếp):
 Một cách tương tự, ta có:
T(n/2) = c*n/2 + 2*T(n/4)
T(n/4) = c*n/4 + 2*T(n/8), giả thiết T(1) = 1.
Như vậy:
 T(n) = c*n + 2(c*(n/2) + 2T(n/4))
 T(n) = c*n + c*n + 4T(n/4)) = 2*c*n + 4T(n/4) = 2*c*n + 4(c*(n/4) +
2T(n/8))
 T(n) = 2*c*n + c*n + 8T(n/8) = 3*c*n + 8T(n/8)
 T(n) = (log n)*c*n + n T(n/n)= (log n)*c*n + n T(1) = n + n*(log n) *c
T(n) = O( n log(n))

24 Ngo Huu Phuc, Le Quy Don Technical University


6.4. Hiệu quả của Quicksort (4/6)
Trong trường hợp tồi nhất:
 Xem xét trường hợp, khóa chính là phần tử lớn
nhất (hoặc) nhỏ nhất trên mỗi tập con.
 Như vậy, tất cả các phần tử sẽ nằm về một phía.
 Ví dụ, xem xét dãy sau:
 [5 3 2 1 4 6 8]
 1 được lấy làm khóa.
 rõ ràng, vẫn còn n – 1 phần tử chưa sắp.
 điều này tiếp tục xuất hiện với các tập con.

25 Ngo Huu Phuc, Le Quy Don Technical University


6.4. Hiệu quả của Quicksort (5/6)
 Như vậy, với mỗi lần thực hiện Quick Sort, chỉ có

1 phần tử được sắp.


 Thực hiện việc này n lần.

 Mỗi lần, thời gian thực hiện cho quá trình chia

mảng con là O(n).

 Như vậy, thời gian cho Quick sort trong trường

hợp này: O(n2).

26 Ngo Huu Phuc, Le Quy Don Technical University


6.4. Hiệu quả của Quicksort (6/6)
Đối với trường hợp trung bình?
 đã chứng minh được, độ phức tạp: O(nlogn)

 Trong thực tế, quicksort thường cho thấy sự hiệu

quả trong sắp xếp.


 có thể hình dung qua xác suất.

 với các tập có ít hơn 30 phần tử, quicksort

chưa cho thấy sự hiệu quả.

27 Ngo Huu Phuc, Le Quy Don Technical University


Cấu trúc dữ liệu và giải thuật
Bài 7. Các phương pháp sắp xếp khác

Giảng viên: TS. Ngo Huu Phuc


Tel: 0438 326 077
Mob: 098 5696 580
Email: ngohuuphuc76@gmail.com

1 Ngo Huu Phuc, Le Quy Don Technical University


Bài 7. Các phương pháp sắp xếp khác
Nội dung:
7.1. ShellSort (8)
7.2. MergeSort (9)
7.3. BucketSort (5)
7.4. RadixSort (6)

Tham khảo:
1. Bucket sort.htm
2. Merge Sort.htm
3. Radix sort.htm
4. ShellSort.htm
5. Bài giảng của TS Nguyên Nam Hồng

2 Ngo Huu Phuc, Le Quy Don Technical University


7.1. ShellSort (1/8)
 Phương pháp này được Donald Shell giới thiệu
năm 1959.
 Với phương pháp sắp xếp chèn: thực hiện ít phép
toán so sánh, nhưng sử dụng nhiều phép di
chuyển thừa.
 Với phương pháp sắp xếp chọn: thực hiện ít phép
toán di chuyển, nhưng sử dụng nhiều phép so
sánh.
 Có thể có phương pháp hiệu quả hơn không?
3 Ngo Huu Phuc, Le Quy Don Technical University
7.1. ShellSort (2/8)
 Phương pháp sắp xếp ShellSort còn được gọi là
phương pháp sắp xếp giảm độ tăng - diminishing
increment sort.
 Phương pháp sử dụng một dãy tăng: h1, h2, .. ht
 Dãy tăng được bắt đầu từ 1, tối đa đến N-1 (trong thực
tế đến N/2). Chưa có đề xuất dãy như thế nào tốt nhất.
 Trong dãy này, không nên ch ọn các số là bội của nhau.

 Dãy này còn được gọi là dãy khoảng cách, ví dụ 1,3,5.

4 Ngo Huu Phuc, Le Quy Don Technical University


7.1. ShellSort (3/8)
 Ví dụ: với 13 phần tử, dãy khoảng cách là 1,3,5

STT 0 1 2 3 4 5 6 7 8 9 10 11 12

Ban
81 94 11 96 12 35 17 95 28 58 41 75 15
đầu
KC
35 17 11 28 12 41 75 15 96 58 81 94 95
=5
KC
28 12 11 35 15 41 58 17 94 75 81 96 95
=3
KC
11 12 15 17 28 35 41 58 75 81 94 95 96
=1

5 Ngo Huu Phuc, Le Quy Don Technical University


7.1. ShellSort (4/8)
#include <stdio.h> for(int i=0;i<t;i++)
#include <conio.h> scanf("%d",&incs[i]);
#define MAX 100 shellsort(list,n,incs,t);
void inputdata(int* list,int n);
printf("Mang da sap xep:\n");
void printlist(int* list,int n);
printlist(list,n);
void shellsort(int *list, int n, int *incs, int t);
getch();
void main() {
}
int list[MAX], n;
int incs[MAX], t; void inputdata(int* list,int n)

printf("Nhap so phan tu cua mang:\n"); {


scanf("%d",&n); int i;
inputdata(list,n); printf("Nhap cac phan tu cua mang\n");
printf("Mang da nhap:\n"); for(i=0;i<n;i++)
printlist(list,n); scanf("%d",&list[i]);
printf("Nhap so phan tu cua mang khoang cach:");
fflush(stdin);
scanf("%d",&t);
}
printf("Nhap gia tri cua mang khoang cach:");

6 Ngo Huu Phuc, Le Quy Don Technical University


7.1. ShellSort (5/8)
void printlist(int* list,int n) void shellsort(int *list, int n, int *incs, int t)
{ {
int i; int i,j,k,h;
printf("Cac phan tu cua mang: \n");
int temp;
for(i=0;i<n;i++)
for(k=t-1; k>0; k--)
printf("%d\t",list[i]);
for(h=incs[k], i=h; i<n; i+=h) {
printf("\n");
temp=list[i];
}
j=i;
while(j>=h && list[j-h]>temp)
{
list[j]=list[j-h];
j-=h;
}
list[j]=temp;
}
}

7 Ngo Huu Phuc, Le Quy Don Technical University


7.1. ShellSort (6/8)
 Tính hiệu quả của thuật toán ShellSort?

 Phụ thuộc vào mảng khoảng cách.

 Dạng mặc định, do Shell đề xuất:

 ht = N/2, hk = hk+1/2 …

 Độ phức tạp của thuật toán - O(N2)

8 Ngo Huu Phuc, Le Quy Don Technical University


7.1. ShellSort (7/8)
 Dãy khoảng cách của Hibbards:
 Hk = 2k-1 i.e. 1, 3, 7,
 Độ phức tạp: O(N1.5)
 Dãy khoảng cách của Sedgewicks:
 Có một số dạng được giới thiệu, nổi tiếng trong
đó có 2 dạng:
9 * 4i – 9 * 2i + 1, và 4i – 3 * 2i + 1
 Độ phức tạp: O(N4/3)

9 Ngo Huu Phuc, Le Quy Don Technical University


7.1. ShellSort (8/8)
 Độ phức tạp của ShellSort trong trường hợp tồi nhất:

O(N2)

 Độ phức tạp của ShellSort trong trường hợp tốt nhất:

~ O(N)

 Độ phức tạp của ShellSort trong trường hợp trung

bình: ~ O(N7/6)

10 Ngo Huu Phuc, Le Quy Don Technical University


7.2. Mergesort (1/9)
 MergeSort là một thuật toán sắp xếp cho độ phức

tạp tương đương với Quick Sort.

Ý tưởng:

 Ý tưởng cơ bản của MergeSort là nối 2 mảng đã

sắp xếp với kích thước m và n thành mảng mới


có kích thước (m+n).

11 Ngo Huu Phuc, Le Quy Don Technical University


7.2. Mergesort (2/9)
Các bước thực hiện:
 Bắt đầu từ một mảng có 1 phần tử, nối với mảng thứ hai cũng
có 1 phần tử để tạo thành mảng mới có 2 phần tử.
 Một cách tương tự, ta nối mảng ba và mảng bốn để được
mảng mới có 2 phần tử, tiếp tục như trên cho đến khi hết, như
vậy ta đã thực hiện xong một lần sắp xếp (one pass).
 Tiếp theo, nối hai mảng có kích thước 2 phần tử lại để được
một mảng có kích thước 4 phần tử. Đến khi kết thúc, ta đã qua
lần sắp xếp thứ hai.
 Tiếp tục quá trình trên cho đến khi chỉ còn 1 mảng, khi đó mảng
đã được sắp xếp!!!

12 Ngo Huu Phuc, Le Quy Don Technical University


7.2. Mergesort (3/9)

Hoạt động của thuật toán MergeSort

13 Ngo Huu Phuc, Le Quy Don Technical University


7.2. Mergesort (4/9)
Để thực hiện được thuật toán này cần thiết xây
dựng:
 Một hàm cho phép nối 2 mảng có kích thước m và n

thành mảng có kích thước (m+n).


 Cần thêm một hàm cho biết đã nối các mảng liền kề hay

chưa?

14 Ngo Huu Phuc, Le Quy Don Technical University


7.2. Mergesort (5/9)
#include <conio.h> mergesort(list,n-1);
#include <stdio.h> printf("Cac phan tu cua mang sau khi sap
#include <stdlib.h> xep:\n");
#include <time.h> printlist(list,n);
#define MAX 100 getch();
void readlist(int list[],int n); }
void printlist(int list[],int n); void readlist(int list[],int n)
void merge(int list[],int listtemp[],int k,int m,int n);
{
void mpass(int list[],int listtemp[],int l,int n);
int i;
void mergesort(int list[], int n );
printf("Nhap cac phan tu cho mang\n");
void main() {
srand( (unsigned)time(NULL));
int list[MAX], n;
printf("Nhap so phan tu cho mang\n"); for(i=0;i<n;i++)
scanf("%d",&n); //scanf("%d",&list[i]);
readlist(list,n); list[i]=rand();
printf("Cac phan tu cua mang truoc khi sap }
xep:\n");
printlist(list,n);

15 Ngo Huu Phuc, Le Quy Don Technical University


7.2. Mergesort (6/9)
void printlist(int list[],int n) while( i <= m && j <= n)
{ {
int i; if(list[i] <= list[j])
printf("Cac phan tu trong danh sach: \n");
{
for(i=0;i<n;i++)
listtemp[k] = list[i];
printf("%d\t",list[i]);
i++;
printf("\n");
k++;
}
// Noi 2 mang da sap xep }

// Mang thu nhat: tu phan tu thu k den phan tu thu m else


// Mang thu nhat: tu phan tu thu m+1 den phan tu {
// thu n listtemp[k] = list[j];
void merge(int list[],int listtemp[],int k,int m,int n) j++;
{ k++;
int i,j;
}
i=k;
}
j = m+1;

16 Ngo Huu Phuc, Le Quy Don Technical University


7.2. Mergesort (7/9)
while(i <= m) // Voi do dai l cua moi mang con
{ // noi cac mang con thanh mang lon hon
listtemp[k] = list[i]; void mpass( int list[],int listtemp[],int l,int n) {
i++;
int i;
k++;
i = 0;
}
while( i <= (n-2*l+1)) {
while (j <= n )
merge(list,listtemp,i,(i+l-1),(i+2*l-1));
{
listtemp[k] = list[j]; i = i + 2*l; }

j++; if((i+l-1) < n)


k++; merge(list,listtemp,i,(i+l-1),n);
} else
} while (i <= n ) {
listtemp[i] = list[i];
i++;
}
}

17 Ngo Huu Phuc, Le Quy Don Technical University


7.2. Mergesort (8/9)
void mergesort(int list[], int n )
{
int l;
int listtemp[MAX];
l =1;
while (l <= n )
{
mpass(list,listtemp,l,n);
l = l*2;
mpass(listtemp,list,l,n);
l = l*2;
}
}

18 Ngo Huu Phuc, Le Quy Don Technical University


7.2. Mergesort (9/9)
Phân tích độ phức tạp của thuật toán MergeSort:
 Trong trường hợp tồi nhất: O(N log N)
 Trong trường hợp tốt nhất: O(N log N)
 Trong trường hợp trung bình: O(N log N)
Vì sao?
 Nếu n là kích thươc của mảng cần sắp xếp, mỗi lần sắp
các nhóm con, độ phức tạp sẽ là O(n), hơn nữa số lần lặp
lại quá trình sắp trên là log2n.
 Cho tất cả các trường hợp, độ phức tạp của Merge Sort là
O(n log2(n)), tuy nhiên cần sử dụng thêm một mảng có
kích thước n nữa

19 Ngo Huu Phuc, Le Quy Don Technical University


7.3. Bucket sort (1/6)
 Giả sử các giá trị cần sắp nằm trong khoảng: 0.. m

 Thuật toán Bucket Sort thực hiện:


 Khởi tạo m chiếc thùng (buckets) được đánh số: 0 … m.

 Kiểm tra từng phần tử S[i] trong danh sách S và đưa vào
thùng thứ i.
 Như vậy, ta có m thùng với các giá trị đã được đưa vào theo
đúng trật tự.
 Sau đó, sắp lại các phần tử theo từng thùng, dựa trên chỉ số
của thùng.
 Phương pháp này không cần phép toán so sánh.

20 Ngo Huu Phuc, Le Quy Don Technical University


7.3. Bucket sort (2/6)
4 2 1 2 0 3 2 1 4 0 2 3 0

2
0 1 2
0 1 2 3 4
0 2 3 4

0 0 0 1 1 2 2 2 2 3 3 4 4
21 Ngo Huu Phuc, Le Quy Don Technical University
7.3. Bucket sort (3/6)
#include <stdio.h> printlist(list,n);
#include <conio.h> getch();
#define MAX 100 }
int inputdata(int* list,int n);
int inputdata(int* list,int n)
void printlist(int* list,int n);
{
void bucketsort(int *list, int n, int m);
int i, m;
void main() {
int temp;
int list[MAX], n;
// So thung printf("Nhap cac phan tu duong cho mang\n");

int m; do {
printf("Nhap so phan tu cho mang, toi da = scanf("%d",&temp);
100\n");
} while (temp<0);
scanf("%d",&n);
m = temp;
m = inputdata(list,n);
list[0] = temp;
printf("Mang da nhap:\n");
for(i=1;i<n;i++)
printlist(list,n);
bucketsort(list,n,m); {

printf("Mang da sap xep:\n");

22 Ngo Huu Phuc, Le Quy Don Technical University


7.3. Bucket sort (4/6)
do { void bucketsort(int *list, int n, int m)
scanf("%d",&temp); {
} while (temp<0); int i,j,k;
list[i] = temp;
int bucket[MAX];
if (m<temp) m = temp;
// Khoi tao thung
}
for(j=0; j<=m; j++)
return m;
bucket[j] = 0;
}
void printlist(int* list,int n) for(i=0; i<n; i++)

{ bucket[list[i]]++;
int i; i = 0;
printf("Cac phan tu cua mang: \n"); for(j=0; j<=m; j++)
for(i=0;i<n;i++) for(k=0; k<bucket[j]; k++)
printf("%d\t",list[i]); {
printf("\n");
list[i] = j;
}
i++;
}
}
23 Ngo Huu Phuc, Le Quy Don Technical University
7.3. Bucket sort (5/6)
Đánh giá độ phức tạp của phương pháp:
 Để khởi tạo m thùng, thời gian cần thiết: O(m)
 Để đưa các phần tử từ danh sách vào thùng, thời gian
cần thiết: O(n)
 Để đưa các phần tử từ các thùng vào danh sách cần:O(n)
 Lưu ý: mặc dù, trong đoạn này vẫn có 2 vòng lặp lồng nhau, tuy
nhiên, số phần tử được xét vẫn chỉ là n phần tử.
 Nếu như m nhỏ hơn n (thông thường), độ phức tạp của
Bucket Sort là O(n).
 Tổng quát hóa, độ phức tạp của phương pháp là: O(n+m)

24 Ngo Huu Phuc, Le Quy Don Technical University


7.3. Bucket sort (6/6)
Một số vấn đề của Bucket Sort:
 Có thể áp dụng thuật toán cho các dãy chỉ có các số
nguyên dương không?
 Có thể áp dụng, tuy nhiên số thùng sẽ phụ thuộc vào giá
trị lớn nhất của dãy số đó.
 Nếu cần sắp 1000 số nguyên dương, giá trị lớn nhất là
999 999, khi đó cần 1 triệu thùng.
 Như vậy, với n<<m, độ phức tạp của thuật toán là O(m)
 Có thể có cách khác giải quyết vấn đề này được không?
 Thuật toán sẽ được giới thiệu ở phần sau.

25 Ngo Huu Phuc, Le Quy Don Technical University


7.4. Radix sort (1/6)
 Ý tưởng: lặp lại nhiều lần phương pháp sắp theo
Bucket dựa trên biểu diễn của số.
 Nếu giá trị lớn nhất là 999999, chỉ cần tối đa 10
bucket (thay cho 1 triệu bucket của phương pháp
Bucket Sort)
 Phương pháp Radix Sort được sử dụng khi khóa
trong quá trình sắp xếp là số nguyên và có giới hạn.
 Số lần gọi Bucket Sort (Number of passes) phụ thuộc
vào biểu diễn của phần tử lớn nhất.

26 Ngo Huu Phuc, Le Quy Don Technical University


7.4. Radix sort (2/6)
Ví dụ: First pass

12 58 37 64 52 36 99 63 18 9 20 88 47

58
12 37 18 9
20 52 63 64 36 47 88 99

20 12 52 63 64 36 37 47 58 18 88 9 99
27 Ngo Huu Phuc, Le Quy Don Technical University
7.4. Radix sort (3/6)
Example: Second pass

20 12 52 63 64 36 37 47 58 18 88 9 99

12 36 52 63
9 18 20 37 47 58 64 88 99

9 12 18 20 36 37 47 52 58 63 64 88 99
28 Ngo Huu Phuc, Le Quy Don Technical University
7.4. Radix sort (4/6)
 Nếu cố định p lần dùng Bucket Sort ( p = 6 nếu giá trị
lớn nhất không quá 999 999), độ phức tạp của Radix
Sort là O(n)
 Với mỗi lần dùng Bucket Sort, độ phức tạp là O(n).

 Nếu kể thêm thông tin về p, độ phức tạp thực sự là


O(pn), với p là số chữ số trong biểu diễn của số lớn
nhất.
 Chú ý, p = log10m, với m là số lớn nhất trong danh
sách.

29 Ngo Huu Phuc, Le Quy Don Technical University


7.4. Radix sort (5/6)
 Radix Sort hoạt động cứng, theo thứ tự trong mỗi lần dùng
Bucket Sort.
 Hoạt động cứng được hiểu theo nghĩa có thứ tự nhất định (từ
phải qua trái đối với biểu diễn số của các phần tử trong danh
sách)
 Ví dụ, 2 số có biểu diễn số đầu tiên giống nhau:

52 và 58
 Số 52 xuất hiện trước số 58 trong danh sách, số 52 sẽ có
trong danh sách kết quả trước số 58.

30 Ngo Huu Phuc, Le Quy Don Technical University


7.4. Radix sort (6/6)
 Chú ý: chỉ cần 10 bucket cho mỗi lần gọi Bucket Sort.

 Radix Sort có thể sử dụng cho chữ cái không? Có


thể được bằng cách:
 Mỗi từ có số chữ cái có giới hạn.

 Sử dụng 27 bucket (có thể thêm nếu có những ký


tự lạ) cho mỗi chữ cái và 01 bucket cho ký tự
“Space bar”.
 Thực hiện tương tự như đối với biểu diễn số, bằng
cách tách thành từng ký tự của chữ.

31/31 Ngo Huu Phuc, Le Quy Don Technical University


Cấu trúc dữ liệu và giải thuật
Bài 8: Các thuật toán tìm kiếm

Giảng viên: TS. Ngo Huu Phuc


Tel: 0438 326 077
Mob: 098 5696 580
Email: ngohuuphuc76@gmail.com

1 PhD. Ngo Huu Phuc, Le Quy Don Technical University


Bài 8. Các thuật toán tìm kiếm
Nội dung:
8.1. Khái niệm về tìm kiếm (3)
8.2. Phương pháp tìm kiếm tuần tự (7)
8.3. Phương pháp tìm kiếm nhị phân (8)
8.4. Phương pháp tìm kiếm nội suy (7)

Tham khảo:
1. Data structures and Algorithms Searching.htm
2. Kyle Loudon Mastering Algorithms, Chapter 12 Sorting and Searching
3. Lecture 19 Sequential and Binary Search.htm
4. Sedgewick Algorithms, Elementary Searching Methods
5. Bài giảng của TS Nguyễn Nam Hồng

2 PhD. Ngo Huu Phuc, Le Quy Don Technical University


8.1. Khái niệm về tìm kiếm (1/3)
 Trong thực tế, việc xác định vị trí của một phần tử nào đó
trong một danh sách ( đã sắp xếp hoặc chưa sắp xếp) có
ý nghĩa quan trọng và được dùng trong nhiều ứng dụng.
 Ví dụ 1: một chương trình tra cứu từ điển, chương trình
cần trả lời ngay nghĩa của một từ nào đó.
 Ví dụ 2: trong một danh sách thí sinh, chương trình cần
đưa ra tất cả thông tin của thí sinh thỏa mãn một số tiêu
chí nào đó.
 Những bài toán như vậy được gọi chung là bài toán tìm
kiếm.

3 PhD. Ngo Huu Phuc, Le Quy Don Technical University


8.1. Khái niệm về tìm kiếm (2/3)
 Trong thực tế, với các ý tưởng khác nhau dẫn

đến các phương pháp tìm kiếm khác nhau.

 Phương pháp tìm kiếm thông dụng nhất trong

thực tế là tìm kiếm tuần tự. Đây là phương pháp


đơn giản, tuy nhiên mất nhiều thời gian thực hiện
đối với dữ liệu lớn.

4 PhD. Ngo Huu Phuc, Le Quy Don Technical University


8.1. Khái niệm về tìm kiếm (3/3)
 Phương pháp tìm kiếm nhị phân: chia danh sách thành
các danh sách con và tìm kiếm trên đó. Với bộ dữ liệu lớn,
phương pháp này cho tốc độ tìm kiếm tốt hơn phương
pháp tuần tự.
 Phương pháp tìm kiếm nội suy: cũng giống như
phương pháp tìm kiếm nhị phân, phương pháp này cũng
chia danh sách thành các danh sách con và tìm kiếm trên
đó.
 Phương pháp tìm kiếm nội suy nhanh hơn phương pháp
nhị phân vì chúng dự đoán kết quả tìm kiếm trên phần nào
để thực hiện tìm kiếm.

5 PhD. Ngo Huu Phuc, Le Quy Don Technical University


8.1. Tìm kiếm tuần tự (1/7)
 Đây là phương pháp tìm kiếm đơn giản nhất.

 Ý tưởng chung của tìm kiếm tuần tự:

 Sử dụng vòng lặp để có thể duyệt cả danh sách, xuất

phát từ phần tử đầu tiên trong danh sách.


 Tại mỗi bước lặp, so sánh phần tử trong danh sách với

giá trị cần tìm ( theo khóa nào đó ). Quá trình sẽ dừng
nếu tìm thấy đối tượng cần tìm hoặc đã đi hết danh
sách.

6 PhD. Ngo Huu Phuc, Le Quy Don Technical University


8.1. Tìm kiếm tuần tự (2/7)
Sub LinearSearch(x:int, a[]: Int, loc: Int)
i:=1
While (i<=n) And (x<>a[i])
i:=i+1
End While
If i<=n Then loc = i Else loc = 0
End Sub
{loc is the subscript of the term that equals x, or is
0 if x is not found}

7 PhD. Ngo Huu Phuc, Le Quy Don Technical University


8.1. Tìm kiếm tuần tự (3/7)
 Giả sử cho danh sách số nguyên gồm:

17 23 5 11 2 29 3

 Ví dụ: tìm vị trí phần tử có giá trị bằng 11, việc tìm kiếm
bắt đầu từ phần tử có giá trị 17, đến 23, đến 5, đến 11:
đưa ra thông báo đã tìm thấy, trả về vị trí thứ 4 trong
danh sách.

 Ví dụ: tìm vị trí phần tử có giá trị bằng 7, việc tìm kiếm bắt
đầu từ phần tử có giá trị 17, qua cả danh sách, nhưng
không tìm thấy, trả về thông tin: không tìm thấy.

8 PhD. Ngo Huu Phuc, Le Quy Don Technical University


8.1. Tìm kiếm tuần tự (4/7)
 Một số ưu điểm của thuật toán tìm kiếm tuần tự:
 Rất đơn giản để nắm bắt.
 Đơn giản trong việc thực hiện.
 Danh sách ban đầu không cần thiết phải được sắp theo
một thứ tự nào đó.
 Nhược điểm của phương pháp tìm kiếm tuần tự:
 Hiệu quả của phương pháp rất kém.
 Ví dụ: Giả sử có một danh sách có 10 000 phần tử.
Phần tử cần tìm ở vị trí 10 000. Như vậy phải đi qua cả
danh sách mới tìm được phần tử đó!!!

9 PhD. Ngo Huu Phuc, Le Quy Don Technical University


8.1. Tìm kiếm tuần tự (6/7)
Use a Sentinel to Improve the Performance
Sub LinearSearch2(x:int, a[]: Int, loc: Int)
a[n+1] = x: n = n + 1: i = 1
While (x<>a[i])
i = i+1
End While
If i<=n Then loc = i Else loc = 0
End Sub

10 PhD. Ngo Huu Phuc, Le Quy Don Technical University


8.1. Tìm kiếm tuần tự (7/7)
Apply Linear Search to Sorted Lists
Sub LinearSearch3(x:int, a[]: Int, loc: Int)
i=1
While (x > a[i])
i = i+1
End While
If a[i] = x Then loc = i Else loc = 0
End Sub

11 PhD. Ngo Huu Phuc, Le Quy Don Technical University


8.2. Tìm kiếm nhị phân (1/8)
Với phương pháp tìm kiếm tuần tự, hiệu quả kém
với danh sách dài. Vậy, có cách nào hiệu quả hơn
không?
 Phương pháp tốt để tìm kiếm: Tìm kiếm nhị phân.
 Giả sử: Cho một danh sách được sắp theo một khóa
nào đó.
 Với danh sách này, sử dụng chiến lược “chia để trị” để
tìm kiếm một phần tử.
 Phương pháp này gần giống với phương pháp tra từ
điển trong thực tế.

12 PhD. Ngo Huu Phuc, Le Quy Don Technical University


8.2. Tìm kiếm nhị phân (2/8)
1. Giả sử dãy khóa đang xét là kl,…,kr và cần tìm
phần tử có khóa x.
2. Khóa ở giữa dãy là ki với i = (l+r) / 2.
3. So sánh khóa x với ki, có những khả năng sau:
 Nếu x = ki → Kết thúc quá trình tìm kiếm.
 Nếu x < ki → Thực hiện việc tìm kiếm trên dãy khóa kl, … ,
ki-1.
 Nếu x > ki → Thực hiện việc tìm kiếm trên dãy khóa ki+1, …
, kr.
4. Thực hiện cho đến khi tìm thấy hoặc khoảng dãy khóa
mới bị rỗng (không tìm thấy).

13 PhD. Ngo Huu Phuc, Le Quy Don Technical University


8.2. Tìm kiếm nhị phân (3/8)
Ví dụ về tìm kiếm nhị phân:
 Giả sử cho dãy số như sau (đã sắp xếp):

2 3 5 11 17 23 29

 Muốn tìm phần tử có khóa 11, tìm kiếm nhị phân


thấy giá trị 11, dừng tìm kiếm.

 Muốn tìm khóa 7, tìm kiếm nhị phân xét các giá trị
lần lượt là 11,3,5, và dừng vị không tìm thấy.

14 PhD. Ngo Huu Phuc, Le Quy Don Technical University


8.2. Tìm kiếm nhị phân (6/8)
Algorithm for Binary search
Sub BinarySearch(x:int, a[]: int, loc: Int)
i =1: j =n
while i<j
begin
m =(i + j) \ 2
if x > a[m] then i=m+1 else j=m
end
if x=a[i] then loc=i else loc=0
End Sub
15 PhD. Ngo Huu Phuc, Le Quy Don Technical University
8.2. Tìm kiếm nhị phân (7/8)
Đánh giá độ phức tạp của tìm kiếm nhị phân
 Trường hợp tốt nhất: chỉ mất một lần so sánh, O(1).
 Trường hợp xấu nhất:
• Gọi w(n) biểu thị số phép so sánh cho dãy có n phần tử.
• Ta có w(n) = 1 + w(n/2).
• Tương tự, w(n) = 1 + 1 + w(n/4).
• …
• Như vậy, ta có w(n) = k + w(n/2k).
• Với n/2k = 1 → k = log2n.
• Độ phức tạp của thuật toán: O(log2n).
 Trong trường hợp trung bình đã chứng minh được,
độ phức tạp của thuật toán: O(log2n).

16 PhD. Ngo Huu Phuc, Le Quy Don Technical University


8.2. Tìm kiếm nhị phân (8/8)
Nhận xét về tìm kiếm nhị phân

 Ưu điểm:

 Hiệu quả hơn nhiều so với tìm kiếm tuần tự.

 Với dãy có n phần tử, chỉ cần tối đa log2n phép so

sánh.

 Nhược điểm:

 Dãy đã cho phải được sắp xếp theo một khóa nào đó.

Như vậy, cần thêm thời gian cho sắp xếp.

17 PhD. Ngo Huu Phuc, Le Quy Don Technical University


8.3. Tìm kiếm nội suy (1/7)
 Tìm kiếm nhị phân hiệu quả hơn nhiều so với tìm

kiếm tuần tự vì nó đã loại bỏ phần lớn phần tử trong


quá trình tìm kiếm.

 Trong thực tế, nếu biết thêm thông tin rằng các phần

tử trong danh sách, theo khóa, được phân bố khá


đều, khi đó, có thể sử dụng nội suy để thu ngắn hơn
nữa quá trình tìm kiếm.

18 PhD. Ngo Huu Phuc, Le Quy Don Technical University


8.3. Tìm kiếm nội suy (2/7)
 Nội suy được hiểu theo nghĩa là từ môt vài giá trị

đã biết, đoán giá trị chưa biết ở vị trí nào.

 Ở đây, chỉ số được sử dụng để xác định phần tử

đã biết, cũng như phần tử cần tìm.

 Giả sử, tìm kiếm phần tử x từ a[r] đến a[l]. Tìm

kiếm nội suy được thực hiện thông qua công


thức: m = l + (x – a[l])*(r-l)/(a[r]-a[l])

19 PhD. Ngo Huu Phuc, Le Quy Don Technical University


8.3. Tìm kiếm nội suy (3/7)
 So sánh x với a[m], có một số trường hợp sau:

 Nếu x = a[m]: Tìm thấy.

 Nếu x < a[m]: đặt r = m-1

 Nếu x > a[m]: đặt l = m + 1

 Nếu quá trình tìm kiếm chưa kết thúc, tiếp tục tìm

với chỉ số l và r mới.

 Quá trình tìm kiếm sẽ dừng khi: tìm thấy, hoặc

x<a[l], hoặc x>a[r].


20 PhD. Ngo Huu Phuc, Le Quy Don Technical University
8.3. Tìm kiếm nội suy (4/7)
Ví dụ: Tìm phần tử có khóa x = 32 trong danh sách
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
19 20
1 4 7 9 9 12 13 17 19 21 24 32 36 44 45 54 55 63
66 70
1: l=1, r=20 -> m=1+(32-1)*(20-1)/(70-1) = 10
a[10]=21<32=x -> l=11
2: l=11, r=20 -> m=11+(30-24)*(20-11)/(70-24) = 12
a[12]=32=x -> Found at m = 12

21 PhD. Ngo Huu Phuc, Le Quy Don Technical University


8.3. Tìm kiếm nội suy (5/7)
Ví dụ: Tìm phần tử có khóa x = 30 trong danh sách
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
19 20
1 4 7 9 9 12 13 17 19 21 24 32 36 44 45 54 55 63
66 70
1: l=1, r=20 -> m=1+(30-1)*(20-1)/(70-1) = 9
a[9]=19<30=x -> l=10
2: l=10, r=20 -> m=10+(30-21)*(20-10)/(70-21) =
12
a[12]=32>30=x -> r = 11
3: l=10, r=11 -> m=10+(30-24)*(11-10)/(24-21) = 12
22 PhD. Ngo Huu Phuc, Le Quy Don Technical University
m=12>11=r: Not Found
8.3. Tìm kiếm nội suy (6/7)
Private Sub Interpolation(a[]: Int, x: Int, n: Int, Found:
Boolean)
l = 1: r = n
Do While (r > l)
m = l + ((x – a[l]) / (a[r] – a[l])) * (r - l)
If (a[m] = x) Or (m < l) Or (m > r) Then
If (a[m] = x) Then Found = True Else Found = False
Exit Do
ElseIf (a[m] < x) Then
l=m+1
ElseIf (a[m] > x) Then
r=m-1
End If
Loop
End Sub
23 PhD. Ngo Huu Phuc, Le Quy Don Technical University
8.3. Tìm kiếm nội suy (7/7)
Nhận xét:
 Tìm kiếm nhị phân cho tốc độ tìm kiếm nhanh (độ phức
tạp O(logn)). Tuy nhiên, tìm kiếm nội suy cho tốc độ tìm
kiếm tốt hơn (độ phức tạp O(loglogn)).
 Ví dụ, với n = 2^32 (4 tỉ phần tử)
 Tìm kiếm nhị phân cần khoảng 32 bước.
 Tìm kiếm nội suy cần khoảng 5 bước.

 Tìm kiếm nội suy hiệu quả với danh sách có số phần tử
lớn.
 Tìm kiếm nội suy vẫn được dùng trong tìm kiếm dữ liệu
được lưu trữ trên ổ cứng hoặc các thiết bị truy cập chậm
24
khác.
PhD. Ngo Huu Phuc, Le Quy Don Technical University
Cấu trúc dữ liệu và giải thuật
Bài 9: Ngăn xếp - Stacks

Giảng viên: TS. Ngo Huu Phuc


Tel: 0438 326 077
Mob: 098 5696 580
Email: ngohuuphuc76@gmail.com

1 PhD Ngo Huu Phuc, Le Quy Don Technical University


Bài 9. Ngăn xếp
Nội dung:
9.1. Khái niệm về stacks (7)
9.2. Các thao tác chính của stacks (9)
9.3. Các thao tác khác của stacks (9)
Tham khảo:
1. Data structures and Algorithms Stacks.htm
2. Kyle Loudon Mastering Algorithms, Chapter 6 Stacks and Queues
3. Elliz Horowitz – Fundamentals of Data Structures, Chapter 3 Stacks and
Queues
4. Deshpande Kakle – C and Data Structures, Chapter 19. Stacks and
Queues
5. Bài giảng TS Nguyễn Nam Hồng

2 PhD Ngo Huu Phuc, Le Quy Don Technical University


9.1. Khái niệm về stacks (1/7)
 Có thể hình dung stack như một chồng đĩa. Với
chồng đĩa này, có thể nhìn thấy chiếc đĩa ở trên
cùng, các đĩa còn lại chưa nhìn thấy được.
 Khi thêm một đĩa vào chồng đĩa (pushed), chiếc
đĩa này ở đỉnh của stack, có thể nhìn thấy.
 Khi lấy di một đĩa từ stack (popped), có thể sử
dụng đĩa này, chiếc đĩa kế tiếp trở thành đỉnh của
stack.

3 PhD Ngo Huu Phuc, Le Quy Don Technical University


9.1. Khái niệm về stacks (2/7)

4 PhD Ngo Huu Phuc, Le Quy Don Technical University


9.1. Khái niệm về stacks (3/7)
 Nguyên lý cơ bản của stack là Last In First Out

(LIFO), có nghĩa vào sau ra trước.

 Với nguyên lý đó, chỉ có chiếc đĩa trên cùng stack

mới có thể nhìn thấy. Muốn nhìn thấy chiếc đĩa


thứ 3, cần lấy ra khỏi stack các đĩa thứ nhất và
thứ 2.

5 PhD Ngo Huu Phuc, Le Quy Don Technical University


9.1. Khái niệm về stacks (3/7)

6 PhD Ngo Huu Phuc, Le Quy Don Technical University


9.1. Khái niệm về stacks (4/7)
Có 3 thao tác chính của
stack: Push Pop

 Push
 Đưa một phần tử vào đỉnh
của stack.
 Pop
 Lấy từ đỉnh của stack một
phần tử.
 Peek
 Xem đỉnh của stack chứa nội
dung là gì?

7 PhD Ngo Huu Phuc, Le Quy Don Technical University


9.1. Khái niệm về stacks (5/7)
Một số ứng dụng của stack:
 Ứng dụng trực tiếp:
 Ứng dụng nổi bật của stack là stack cho chương trình,
chương trình sử dụng stack để gọi hàm.
 Trong trình duyệt WEB, các trang đã xem được lưu
trong stack.
 Trong trình soạn thảo văn bản, thao tác Undo được lưu
trong stack.
 Ứng dụng gián tiếp:
 Cấu trúc dữ liệu bổ trợ cho thuật toán khác.
 Một thành phần của cấu trúc dữ liệu khác.

8 PhD Ngo Huu Phuc, Le Quy Don Technical University


9.1. Khái niệm về stacks (6/7)
Thực thi và giới hạn của stack.
 Thực thi.
 Gọi n là số ô nhớ cho stack.
 Không gian có thể sử dụng đối với stack là O(n).
 Với mỗi thao tác, độ phức tạp là O(1)

 Giới hạn.
 Kích thước tối đa của stack được định nghĩa trước,
không thể thay đổi (nếu dùng mảng).
 Nếu stack đã đầy, không PUSH được phần tử mới vào
stack. Nếu stack rỗng, thao tác POP cho kết quả rỗng.

9 PhD Ngo Huu Phuc, Le Quy Don Technical University


9.1. Khái niệm về stacks (7/7)
Đối với stack cần:
 Định nghĩa stack:
 MAXSIZE: Số phần tử tối đa của stack (nếu dùng
mảng).
 ItemType: Kiểu dữ liệu cho stack.
 Thao tác chính:
 Push
 Pop
 Peek
 Thao tác khác:
 IsEmpty
 IsFull
 MakeEmpty
10 PhD Ngo Huu Phuc, Le Quy Don Technical University
9.2. Thao tác cơ bản của stack (1/9)
 Trước hết, định nghĩa kích thước cho stack:
Ví dụ: #define MAXSIZE 100
 Có thể sử dụng stack để lưu bất kỳ kiểu dữ liệu
nào, do đó cần định nghĩa kiểu dữ liệu cho stack:
Ví dụ: int stack[MAXSIZE];
 Có thể dùng một con trỏ để xác định đỉnh của stack
hoặc đơn giản hơn, dùng phần tử mảng đầu tiên
của stack để lưu số phần đã có trong stack:
Ví dụ: stack[0] = 0;

11 PhD Ngo Huu Phuc, Le Quy Don Technical University


9.2. Thao tác cơ bản của stack (2/9)
Thao tác Push (newItem: ItemType)

 Chức năng: Thêm một phần tử vào đỉnh của

Stack.

 Điều kiện thực hiện: Stack đã được khởi tạo và

chưa đầy.

 Kết quả: Nếu thêm thành công, newItem sẽ ở

đỉnh của Stack.

12 PhD Ngo Huu Phuc, Le Quy Don Technical University


9.2. Thao tác cơ bản của stack (3/9)
Ví dụ về hàm push:
void push(int stack[], int value)
{
if(stack[0] < MAXSIZE-1 )
{
stack[0] = stack[0] + 1;
stack[stack[0]] = value;
}
else
{
printf("Khong the them vao STACK\n");
getch();
}
}

13 PhD Ngo Huu Phuc, Le Quy Don Technical University


9.2. Thao tác cơ bản của stack (4/9)
Thao tác Pop (item: ItemType)
 Chức năng: Lấy phần tử ở đỉnh của Stack và trả lại

cho lời gọi hàm.

 Điều kiện: Stack đã được khởi tạo và không rỗng.

 Kết quả: Phần tử ở đỉnh của Stack được trả lại cho

lời gọi hàm và số phần tử trong Stack giảm đi 1.

14 PhD Ngo Huu Phuc, Le Quy Don Technical University


9.2. Thao tác cơ bản của stack (5/9)
Ví dụ về hàm pop:
int pop(int stack[]) {
int value;
if(stack[0] > 0 )
{
value = stack[stack[0]];
stack[0] = stack[0] - 1;
}
else
{
printf("STACK rong\n");
value = -32768;
}
return value;
}

15 PhD Ngo Huu Phuc, Le Quy Don Technical University


9.2. Thao tác cơ bản của stack (7/9)
Thao tác Peek:

 Chức năng: Lấy giá trị tại đỉnh của Stack nhưng

không loại phần tử ở đỉnh của Stack.

 Điều kiện: Stack đã được khởi tạo và không rỗng.

 Kết quả: Giá trị tại đỉnh của Stack được trả về cho lời

gọi hàm, Stack không thay đổi.

16 PhD Ngo Huu Phuc, Le Quy Don Technical University


9.2. Thao tác cơ bản của stack (8/9)
Ví dụ về hàm Peek
int peek(int stack[])
{
if(stack[0]>0)
return stack[stack[0]];
else
{
printf("STACK rong\n");
return -32768;
}
}
17 PhD Ngo Huu Phuc, Le Quy Don Technical University
9.3. Các thao tác khác của stacks (1/9)
 Thao tác isEmpty:
 Kiểm tra xem Stack có rỗng không?

 Thao tác isFull:


 Kiểm tra xem Stack đã đầy chưa?

 Thao tác makeEmpty:


 Làm rỗng Stack.

 Xây dựng hàm Push sử dụng thao tác isFull.

 Xây dựng hàm Pop sử dụng thao tác isEmpty.

 Xây dựng hàm Peek sử dụng thao tác isEmpty.


18 PhD Ngo Huu Phuc, Le Quy Don Technical University
9.3. Các thao tác khác của stacks (2/9)
Thao tác isEmpty:
 Kiểm tra xem Stack có rỗng không?
 Nếu rỗng hàm trả về 1, nếu không hàm trả về 0.
int isEmpty(int stack[])
{
if(stack[0]==0)
return 1;
else
return 0;
}

19 PhD Ngo Huu Phuc, Le Quy Don Technical University


9.3. Các thao tác khác của stacks (3/9)
Thao tác isFull:
 Kiểm tra xem Stack đã đầy chưa?
 Nếu đã đầy, hàm trả về 1; nếu chưa đầy, hàm trả về 0.
int isFull(int stack[])
{
if(stack[0]==MAXSIZE-1)
return 1;
else
return 0;
}

20 PhD Ngo Huu Phuc, Le Quy Don Technical University


9.3. Các thao tác khác của stacks (4/9)
Thao tác makeEmpty:
 Làm rỗng Stack.

 Đưa số phần tử trong Stack về 0.

void makeEmpty(int stack[])


{
stack[0]=0;
}

21 PhD Ngo Huu Phuc, Le Quy Don Technical University


9.3. Các thao tác khác của stacks (5/9)
Xây dựng hàm Push sử dụng thao tác isFull:
void push2(int stack[], int value)
{
if(!isFull(stack))
{
stack[0] = stack[0] + 1;
stack[stack[0]] = value;
}
else
{
printf("Khong the them vao STACK\n");
getch();
}
}

22 PhD Ngo Huu Phuc, Le Quy Don Technical University


9.3. Các thao tác khác của stacks (6/9)
Xây dựng hàm Pop sử dụng thao tác isEmpty.
int pop2(int stack[])
{
int value;
if(!isEmpty(stack))
{
value = stack[stack[0]];
stack[0] = stack[0] - 1;
}
else
{
printf("STACK rong\n");
value = -32768;
}
return value;
}

23 PhD Ngo Huu Phuc, Le Quy Don Technical University


9.3. Các thao tác khác của stacks (8/9)
Xây dựng hàm Peek sử dụng thao tác isEmpty.
int peek2(int stack[])
{
if(!isEmpty(stack))
return stack[stack[0]];
else
{
printf("STACK rong\n");
return -32768;
}
}
24 PhD Ngo Huu Phuc, Le Quy Don Technical University
Cấu trúc dữ liệu và giải thuật
Bài 10: Ứng dụng của ngăn xếp

Giảng viên: TS. Ngo Huu Phuc


Tel: 0438 326 077
Mob: 098 5696 580
Email: ngohuuphuc76@gmail.com

1 PhD. Ngo Huu Phuc, Le Quy Don Technical University


Bài 10. Một vài ứng dụng của Stack
Nội dung:
10.1. Đảo mảng (3)
10.2. Đảo chuỗi (4)
10.3. Chuyển đổi hệ số (9)
10.4. Bracket Matching (5)
10.5. Balancing Act (4)
Tham khảo:
1. Data structures and Algorithms Stacks.htm
2. Kyle Loudon Mastering Algorithms, Chapter 6 Stacks and Queues
3. Elliz Horowitz – Fundamentals of Data Structures, Chapter 3 Stacks and
Queues
4. Deshpande Kakle – C and Data Structures, Chapter 19. Stacks and
Queues
5. Bài giảng TS Nguyễn Nam Hồng

2 PhD. Ngo Huu Phuc, Le Quy Don Technical University


10.1. Đảo mảng (1/3)
 Cho một mảng gồm một dãy các giá trị.
 Để đảo thứ tự các phần tử trong mảng, sử dụng
nguyên lý Last-In-First-Out của Stack.
 Ví dụ về hoạt động của đảo mảng:

17 18 134 216
23 25 47 55
25 23 55 47
18 17 216 134

3 PhD. Ngo Huu Phuc, Le Quy Don Technical University


10.1. Đảo mảng (2/3)
Ý tưởng thực hiện giải thuật:
1. Khởi tạo một Stack rỗng, có kiểu số.
2. Với n phần tử của mảng, lần lượt đưa vào Stack
thông qua hàm Push:
Push a[i] in to Stack.
3. Lần lượt lấy ra từ Stack n phần tử và đưa vào trở
lại mảng ban đầu:
Pop a item from nStack in to a[i].
4. Kết thúc giải thuật.

4 PhD. Ngo Huu Phuc, Le Quy Don Technical University


10.1. Đảo mảng (3/3)
Ví dụ về đảo mảng:
void revArray(int a[],int n,int stack[])
{
makeEmpty(stack);
for(int i=0;i<n;i++)
push(stack,a[i]);
for(int i=0;i<n;i++)
a[i]=pop(stack);
}
5 PhD. Ngo Huu Phuc, Le Quy Don Technical University
10.2. Đảo chuỗi (1/4)
 Cho một chuỗi gồm nhiều từ.
 Đảo chuỗi thực hiện việc đảo các từ trong chuỗi, sử
dụng ý tưởng Last-In-First-Out của Stack.
 Ví dụ về đảo chuỗi:

Tôi Tập Chăm Giỏi


Làm Bài Học Thì
Bài Làm Thì Học
Tập Tôi Giỏi Chăm

6 PhD. Ngo Huu Phuc, Le Quy Don Technical University


10.2. Đảo chuỗi (2/4)
Ý tưởng xây dựng chương trình:
1. Tạo một wStack rỗng.
2. Với mỗi từ mWord đọc được từ string, đưa từ đó
vào Stack:
Push mWord into wStack.
3. Đọc từ wStack cho đến hết, thực hiện:
Pop a word from wStack into mWord.
Concate mWord to the end of outp string.
4. Dừng giải thuật.

Chú ý: cần xây dựng hàm tách word từ string.


7 PhD. Ngo Huu Phuc, Le Quy Don Technical University
10.2. Đảo chuỗi (3/4)
#include <stdio.h> for(int i=1; i<strlen(mString); i++)
#include <stdlib.h> {
#include <conio.h> mWord = strtok(NULL," ");
#include "string.h"
if(mWord) push(stack,mWord,&top);
#define MAXSIZE 100
}
#define MAX 100
char *p=pop(stack,&top);
void push(char* stack[], char* value, int* top);
while(top>=0)
char* pop(char* stack[], int* top);
void main() { {

char* stack[MAXSIZE]; p=strcat(p," ");


int top=-1; p=strcat(p,pop(stack,&top));
char mString[MAX]; }
char* mWord; printf("Chuoi ket qua \n");
printf("Nhap vao mot chuoi: "); puts(p);
gets(mString);
getch();
mWord = strtok(mString," ");
}
push(stack,mWord,&top);

8 PhD. Ngo Huu Phuc, Le Quy Don Technical University


10.2. Đảo chuỗi (4/4)
void push(char* stack[], char* value, int* top) char* pop(char* stack[], int* top) {
{ char* value;
if(*top < MAXSIZE) if(*top>=0) {
{
*top=*top+1; value=(char*)malloc(strlen(stack[*top]));
stack[*top]=(char*)malloc(strlen(value)); strcpy(value,stack[*top]);
strcpy(stack[*top],value); *top=*top-1;
}
}
else
else
{
{ printf("STACK rong\n");
printf("Khong the them vao STACK\n");
value = NULL; }
getch();
} return value;
} }
void makeEmpty(int* top) {
*top=-1;
}

9 PhD. Ngo Huu Phuc, Le Quy Don Technical University


10.3. Chuyển đổi hệ số (1/9)
 Các hệ số hay sử dụng: hệ thập phân (Decimal), hệ

nhị phân (Binary), hệ 16 (Hexadecimal).

 Con người thường sử dụng hệ thập phân.

 Đối với máy tính, thường sử dụng hệ nhị phân.

 Hệ 16 là hệ trung gian giữa hệ thập phân và hệ nhị

phân.

 Ví dụ: 111 = (01101111)B = (6F)H

10 PhD. Ngo Huu Phuc, Le Quy Don Technical University


10.3. Chuyển đổi hệ số (2/9)
Việc chuyển đổi giữa các hệ số thường xét:

1. Chuyển đổi từ hệ thập phân sang hệ nhị phân.

2. Chuyển đổi từ hệ nhị phân sang hệ thập phân.

3. Chuyển đổi từ hệ thập phân sang hệ 16.

4. Chuyển đổi từ hệ 16 sang hệ thập phân.

11 PhD. Ngo Huu Phuc, Le Quy Don Technical University


10.3. Chuyển đổi hệ số (3/9)
Ý tưởng chuyển đổi từ hệ thập phân sang hệ nhị
phân sử dụng Stack:
 Khởi tạo một Stack rỗng.

 Chuyển đổi số từ dạng thập phân sang nhị phân bằng

phép div và mod cho 2.


 Kết quả của phép mod được đưa vào Stack.

 Đọc từ Stack cho đến hết, kết quả được nối với nhau để

tạo thành chuỗi.


 Kết thúc giải thuật.

12 PhD. Ngo Huu Phuc, Le Quy Don Technical University


10.3. Chuyển đổi hệ số (4/9)
Các hàm chính Dec to Binary: void push(char stack[], char value)
char* DecToBin(int a, char stack[]) { { if(stack[0] < MAXSIZE) {
makeEmpty(stack); stack[0]++;
int n=0;
stack[stack[0]]= value; }
while(a>0) {
else {
push(stack,a%2+'0');
printf("Khong the them vao STACK\n");
a=a/2;
getch(); } }
n++;
} char pop(char stack[])

char* mString; { char value;


mString = (char*) malloc(n); if(stack[0]>0) {
int i=0; value=stack[stack[0]];
while(stack[0]>0) stack[0]--; }
{ mString[i]=pop(stack); else {
i++; }
printf("STACK rong\n");
mString[i]='\0';
value = -1; }
return mString; }
return value;
}
13 PhD. Ngo Huu Phuc, Le Quy Don Technical University
10.3. Chuyển đổi hệ số (3/9)
Ý tưởng chuyển đổi từ hệ thập phân sang hệ 16 sử
dụng Stack:
 Khởi tạo một Stack rỗng.

 Chuyển đổi số từ dạng thập phân sang nhị phân bằng

phép div và mod cho 16.


 Kết quả của phép mod được đưa vào Stack.

 Đọc từ Stack cho đến hết, kết quả được nối với nhau để

tạo thành chuỗi.


 Kết thúc giải thuật.

14 PhD. Ngo Huu Phuc, Le Quy Don Technical University


10.3. Chuyển đổi hệ số (4/9)
Các hàm chính Dec to Hex: char* mString;
char* DecToHex(int a, char stack[]) mString = (char*) malloc(n);
{ int i=0;
makeEmpty(stack);
while(stack[0]>0)
int n=0;
{
while(a>0)
mString[i]=pop(stack);
{
i++;
int t = a%16;
if(t>=0 && t<=9) }

push(stack,t+'0'); mString[i]='\0';
else return mString;
push(stack,t-10+'A'); }
a=a/16;
n++;
}

15 PhD. Ngo Huu Phuc, Le Quy Don Technical University


10.4. Bracket Matching (1/6)
 Một biểu thức sử dụng dấu ngoặc (Bracket) đúng
nếu:
• với mỗi dấu ngoặc trái sẽ có 1 dấu ngoặc phải gần nhất

khớp với nó.


• với mỗi dấu ngoặc phải sẽ có 1 dấu ngoặc trái gần nhất

(bên trái) khớp với nó.


• một đoạn biểu thức nằm giữa một cặp dấu ngoặc trái,

phải được gọi là sử dụng đúng dấu ngoặc.

16 PhD. Ngo Huu Phuc, Le Quy Don Technical University


10.4. Bracket Matching (2/6)
 Ví dụ về sử dụng dấu ngoặc trong biểu thức toán
học:
s * (s – a) * (s – b) * (s – c) → Well
(– b + (b2 – 4*a*c)^0.5) / 2*a → Well
s * (s – a) * (s – b * (s – c) → ???
s * (s – a) * s – b) * (s – c) → ???
(– b + (b^2 – 4*a*c)^(0.5/ 2*a)) → ???

17 PhD. Ngo Huu Phuc, Le Quy Don Technical University


10.4. Bracket Matching (3/6)
Giải thuật kiểm tra sử dụng dấu ngoặc:
1. Tạo một bStack rỗng (Stack chứa dấu ngoặc).
2. Với mỗi ký hiệu sym trong đoạn (từ trái sang phải) :
2.1. Nếu sym là dấu ngoặc trái:
2.1.1. Đưa sym vào bStack.
2.2. Nếu sym là dấu ngoặc phải:
2.2.1. Nếu bStack rỗng, return false.
2.2.2. Lấy dấu ngoặc ở bStack, đưa vào biến left.
2.2.3. Nếu left và sym không khớp được với nhau,
return false.
3. Dừng giải thuật, trả về True nếu bStack rỗng, hoặc
False với các trường hợp khác.

18 PhD. Ngo Huu Phuc, Le Quy Don Technical University


10.4. Bracket Matching (4/6)
#include <stdio.h> gets(a);
#include <stdlib.h> if(bracketMatching(a,bStack,&top))
#include <conio.h> printf("Bieu thuc su dung DUNG dau
#include "string.h" ngoac\n");
#define MAXSIZE 100 else
#define MAX 100 printf("Bieu thuc su dung KHONG
void push(char bStack[], char value, int *top); DUNG dau ngoac\n");
char pop(char bStack[], int *top); getch();
int isEmpty(char bStack[],int top); }
int isFull(char bStack[], int top);
void makeEmpty(int bStack[],int* top);
int bracketMatching(char* a,char bStack[], int *top);
void main()
{
char bStack[MAXSIZE];
char a[MAX];
int top=-1;
printf("Doc vao mot bieu thuc: ");

19 PhD. Ngo Huu Phuc, Le Quy Don Technical University


10.4. Bracket Matching (5/6)
int bracketMatching(char* a,char bStack[],int *top) void push(char bStack[], char value, int *top)
{ int kt=1; {
for(int i=0;i<strlen(a);i++) if(*top<MAXSIZE)
{
{
if(a[i]=='(')
*top = *top + 1;
push(bStack,a[i],top);
bStack[*top] = value;
if(a[i]==')')
}
{
if(isEmpty(bStack,*top)) else

kt=0; {
else printf("Khong the them vao STACK\n");
char ch=pop(bStack,top); getch();
} }
} }
if(!isEmpty(bStack,*top))
kt=0;
return kt;
}

20 PhD. Ngo Huu Phuc, Le Quy Don Technical University


10.4. Bracket Matching (6/6)
char pop(char bStack[], int *top) int isEmpty(char bStack[],int top)
{ {
char value; if(top==-1)
if(*top>=0)
return 1;
{
else
value = bStack[*top];
return 0;
*top = *top - 1;
}
}
else int isFull(char bStack[],int top)

{ {
printf("STACK rong\n"); if(top==MAXSIZE-1)
value = 0; return 1;
} else
return value; return 0;
}
}
void makeEmpty(char bStack[], int *top)
{ *top=-1; }

21 PhD. Ngo Huu Phuc, Le Quy Don Technical University


10.5. Balancing Act (1/4)
 Với phương pháp trước mới chỉ đảm bảo được khớp
dấu ngoặc ‘(‘ và ‘)’.
 Trong thực tế, còn có nhiều dấu ngoặc khác cần
khớp như: ‘(‘ và ‘)’; ‘[‘ và ‘]’; ‘{‘ và ‘}’.
 Như vậy, trong giải thuật trên, cần lưu ý thêm quá
trình push và pop vào bStack:
 Nếu quá trình push vào bStack có dạng: (, [, {.
 Quá trình pop từ bStack cần đúng thứ tự: }, ], ).
 Nếu đã duyệt xong biểu thức và bStack rỗng → return
True, ngược lại, return False.

22 PhD. Ngo Huu Phuc, Le Quy Don Technical University


10.5. Balancing Act (2/4)
void main() int balancingAct(char* a,char bStack[],int *top)
{ { int kt=1;
char bStack[MAXSIZE]; for(int i=0;i<strlen(a);i++) {
char a[MAX];
if(a[i]=='(' || a[i]=='[' || a[i] == '{')
int top=-1;
push(bStack,a[i],top);
printf("Doc vao mot bieu thuc: ");
if(a[i]==')' || a[i]=='}' || a[i]==']')
gets(a);
{
if(balancingAct(a,bStack,&top))
printf("Bieu thuc su dung DUNG dau if(isEmpty(bStack,*top))
ngoac\n"); kt=0;
else else {
printf("Bieu thuc su dung KHONG DUNG dau
char ch=pop(bStack,top);
ngoac\n");
if(!matching(ch,a[i])) kt=0; }
getch();
} }
}
if(!isEmpty(bStack,*top)) kt=0;
return kt;
}
23 PhD. Ngo Huu Phuc, Le Quy Don Technical University
10.5. Balancing Act (3/4)
int matching(char a, char b)
{
int kt=0;
if(a=='(' && b==')') kt=1;
if(a=='[' && b==']') kt=1;
if(a=='{' && b=='}') kt=1;
return kt;
}

24/24 PhD. Ngo Huu Phuc, Le Quy Don Technical University


Cấu trúc dữ liệu và giải thuật
Bài 11: Ký pháp nghịch đảo Balan
(Reverse Polish Notation)

Giảng viên: TS. Ngo Huu Phuc


Tel: 0438 326 077
Mob: 098 5696 580
Email: ngohuuphuc76@gmail.com

1 PhD Ngo Huu Phuc, Le Quy Don Technical University


Bài 11: Ký pháp nghịch đảo Balan
Nội dung:
11.1. Reverse Polish Notation (RPN) (6)
11.2. Chuyển đổi biểu dạng Infix sang RPN (7)
11.3. Ví dụ về chuyển đổi từ Infix sang RPN (9)
11.4. Prefix Notation (3)
Tham khảo:
1. Data structures and Algorithms Stacks.htm
2. Kyle Loudon Mastering Algorithms, Chapter 6 Stacks and Queues
3. Elliz Horowitz – Fundamentals of Data Structures, Chapter 3 Stacks and
Queues
4. Deshpande Kakle – C and Data Structures, Chapter 19. Stacks and
Queues
5. Bài giảng TS Nguyên Nam Hồng

2 PhD Ngo Huu Phuc, Le Quy Don Technical University


11.1. Ký pháp nghịch đảo Balan (RPN)

Nội dung phần 11.1:

11.1.1. Khái niệm về Ký pháp nghịch đảo Balan (RPN)

11.1.2. Tại sao sử dụng Ký pháp nghịch đảo Balan?

11.1.3. Một số ví dụ về Ký pháp nghịch đảo Balan.

3 PhD Ngo Huu Phuc, Le Quy Don Technical University


11.1.1. Khái niệm về Ký pháp nghịch đảo Balan
 Ký pháp nghịch đảo Balan, còn được gọi là Postfix, do

Charles Hamblin đề xuất vào những năm 1950s…

 Ký pháp này lấy ý tưởng của Polish notation, được đề

xuất vào năm 1920 của nhà toán học người Balan có tên
Jan Łukasiewicz. (Trong một số tài liệu còn gọi là ký pháp
Łukasiewicz).

Dạng Infix Dạng Postfix Dạng Prefix

A-B/(C+D) ABCD+/- –A/B+CD


4 PhD Ngo Huu Phuc, Le Quy Don Technical University
11.1.2. Tại sao sử dụng RPN? (1/3)
RPN cho phép giảm thời gian trong việc tính một biểu
thức. Người dùng không cần quan tâm đến dấu
ngoặc trong biểu thức.

Với ký pháp này cho phép thấy kết quả ngay sau
phép toán.

Với ký pháp này, việc thực hiện trên máy tính tỏ ra


hiệu quả hơn!!!

5 PhD Ngo Huu Phuc, Le Quy Don Technical University


11.1.2. Tại sao sử dụng RPN?(2/3)
Với việc cho thấy kết quả ngay, do đó, người sử dụng
có thể kiểm tra kết quả dễ hơn, nhanh hơn.

Với cách viết này, việc tính toán dựa trên thứ tự của
biểu thức, kết hợp với thứ tự ưu tiên của phép toán.

RPN có tính logic cao vì người dùng đưa biểu thức,


sau đó đưa phép tính cần thực hiện.

6 PhD Ngo Huu Phuc, Le Quy Don Technical University


11.1.2. Tại sao sử dụng RPN?(3/3)
Xem xét một biểu thức đại số dạng Infix sau:
1+2*3=?
Kết quả là 7 hay 9?
Trả lời: kết quả là 7 vì phép * có độ ưu tiên cao hơn phép +.

Xem xét ví dụ: (1+2) * 3?


Kết quả là 9.

Rõ ràng, với dạng ký pháp này, người dùng dễ nhầm lẫn


trong tính toán!!!

7 PhD Ngo Huu Phuc, Le Quy Don Technical University


11.1.3. Một số ví dụ về RPN (1/2)
Xem xét ký pháp RPN sau: 4 5 + 6 *
 Kết quả của biểu thức là bao nhiêu?
 45+ → 4+5=9
 96* → 9 * 6 = 54
Biểu thức 4 5 + 6 * tương tự như biểu thức dạng
Infix (4+5)*6.
Các bước thực hiện:
1. 45+6*
2. 96*
3. 54

8 PhD Ngo Huu Phuc, Le Quy Don Technical University


11.1.3. Một số ví dụ về RPN(2/2)
Xem xét biểu thức dạng Postfix: 6 4 5 + *
 Kết quả của biểu thức bằng?
 45+ → 4+5=9
 69* → 6 * 9 = 54
Biểu thức 6 4 5 + * tương đương với biểu thức
dạng Infix: 6 * (4 + 5).
Các bước thực hiện:
1. 645+*
2. 69*
3. 54

9 PhD Ngo Huu Phuc, Le Quy Don Technical University


11.2. Chuyển đổi biểu dạng Infix sang RPN
11.2.1. Ví dụ về chuyển đổi biểu thức dạng Infix sang
RPN.

11.2.2. Thuật toán chuyển đổi biểu thức dạng Infix


sang RPN.

11.2.3. Chương trình chuyển đổi biểu thức dạng Infix


sang dạng RPN.

10 PhD Ngo Huu Phuc, Le Quy Don Technical University


11.2.1. Ví dụ về chuyển đổi biểu thức
dạng Infix sang RPN
 Cho biểu thức dạng Infix: (4 + 5) / (6 + 7)
Làm thế nào để chuyển đổi từ dạng Infix sang RPN?
45+67+/
Cho biểu thức dạng Infix: ((4+5)*(2+3)+6)/(8+7)
Cần gõ phím bao nhiêu lần? 21
Kết quả của phép biến đổi sang RPN?
45+23+*6+87+/
Với dạng RPN, cần gõ phím 13 lần.

11 PhD Ngo Huu Phuc, Le Quy Don Technical University


11.2.2. Thuật toán chuyển đổi biểu thức
dạng Infix sang RPN (1/4)
Thuật toán thứ nhất: chuyển đổi biểu thức từ Infix sang Postfix:
 Bước 1 : Đọc một thành phần của biểu thức E (dạng Infix biểu diễn
bằng xâu, đọc từ trái qua phải). Giả sử thành phần đọc được là x
1.1. Nếu x là toán hạng thì viết nó vào bên phải biểu thức E1 (xâu
lưu kết quả của Postfix)
1.2. Nếu x là dấu ‘(’ thì đẩy nó vào Stack.
1.3. Nếu x là một trong các phép toán +, -, *, / thì
1.3.1. Xét phần tử y ở đỉnh Stack.
1.3.2. Nếu Pri(y)>=Pri(x) là một phép toán thì loại y ra khỏi Stack
và viết y vào bên phải của E1 và quay lại bước 1.3.1
1.3.3. Nếu Pri(y)<Pri(x) thì đẩy x vào Stack.

12 PhD Ngo Huu Phuc, Le Quy Don Technical University


11.2.2. Thuật toán chuyển đổi biểu thức
dạng Infix sang RPN (2/4)
1.4. Nếu x là dấu ‘)’ thì
1.4.1. Xét phần tử y ở đầu của Stack.
1.4.2. y là phép toán thì loại y ra khỏi Stack, viết y vào bên phải E1
và quay trở lại 1.4.1
1.4.3. Nếu y là dấu ‘(’ loại y ra khỏi Stack.
 Bước 2 : Lập lại bước 1 cho đến khi toàn bộ biểu thức E được đọc
qua
 Bước 3 :Loại phần tử ở đỉnh Stack và viết nó vào bên phải E1. Lặp
lại bước này cho đến khi Stack rỗng.
 Bước 4: Tính giá trị của biểu thức dưới dạng hậu tố
Chú ý:
Hàm Pri(‘$’)<Pri(‘(‘)<Pri(‘+‘)=Pri(‘-‘)<Pri(‘*‘)=<Pri(‘/‘)
$ ký hiệu đỉnh của Stack

13 PhD Ngo Huu Phuc, Le Quy Don Technical University


11.2.2. Thuật toán chuyển đổi biểu thức
dạng Infix sang RPN (3/4)
Thuật toán thứ hai: Tính giá trị biểu thức dạng Postfix:

 Bước 1 : Đọc lần lượt các phần tử của biểu thức E1 (từ trái qua phải)

 Nếu gặp toán hạng thì đẩy nó vào Stack.

 Nếu gặp phép toán thì lấy hai phần tử liên tiếp trong Stack thực

hiện phép toán, kết quả được đẩy vào trong Stack.

 Bước 2 : Lập lại bước 1 cho đến khi hết tất cả các phần tử trong biểu

thức E1. lúc đó đỉnh của Stack chứa giá trị của biểu thức cần tính

 Bước 3. Kết thúc.

14 PhD Ngo Huu Phuc, Le Quy Don Technical University


11.3. Ví dụ về chuyển đổi từ Infix sang RPN (1/2)
Cho biểu thức: E=a*(b+c)-d/e#.
E S E1 E S E1
$* abc+
$
- $ abc+*
a $ a
$- abc+*
* $* a
d $- abc+*d
( $*( a
/ $-/ abc+*d
b $*( ab
e $-/ abc+*de
+ $*(+ ab
# $- abc+*de/
c $*(+ abc $ abc+*de/-
) $*( abc+
15 PhD Ngo Huu Phuc, Le Quy Don Technical University
11.3. Ví dụ về tính biểu thức dạng RPN (2/2)
Xét biểu thức dạng RPN:
45+23+*6+87+/
4 4 4 9 9 9 9 9 9 45 45 45 51 51 51
5 5 2 2 2 5 5 6 6 8 8
+ 3 3 * + 7
+
51 51 51 3.4
8 15 15
7 /
+
16 PhD Ngo Huu Phuc, Le Quy Don Technical University
11.3. Chương trình minh họa (1/5)
#include "stdio.h" void main() {
#include "conio.h" struct infix p;
#include "string.h" char expr[MAX];
#include "ctype.h“
initinfix(&p);
#define MAX 50
printf ("\nNhap bieu thuc dang Infix: ");
struct infix
gets(expr) ;
{
setexpr(&p, expr);
char target[MAX] ;
char stack[MAX] ; convert(&p);

char *s, *t ; printf ( "\nBieu thuc dang Postfix: " ) ;


int top ; }; show(p);
void initinfix (struct infix *); getch();
void setexpr (struct infix *, char *); }
void push (struct infix *, char);
char pop (struct infix *);
void convert (struct infix *);
int priority (char);
void show (struct infix);

17 PhD. Ngo Huu Phuc, Le Quy Don Technical University


11.3. Chương trình minh họa (2/5)
/* Khoi tao cau truc dang Infix */ /* Push toan tu vao Stack*/
void initinfix (struct infix *p) void push(struct infix *p, char c)
{ {
p->top = -1 ;
if (p->top == MAX)
strcpy(p->target,"");
printf ("\nStack day\n");
strcpy(p->stack,"");
else
p->t = p->target;
{
p->s = "";
} p->top++;
p->stack[p->top] = c;
/* Lay thong tin cho bieu thuc */ }
void setexpr (struct infix *p, char *str) }
{
p->s = str;
}

18 PhD. Ngo Huu Phuc, Le Quy Don Technical University


11.3. Chương trình minh họa (3/5)
/* Lay toan tu khoi Stack */ /* Chuyen doi bieu thuc tu Infix sang Postfix */
char pop(struct infix *p) void convert(struct infix *p)
{ { char opr;
if (p->top == -1)
while (*( p ->s))
{
{
printf ( "\nStack rong\n" );
if (*(p->s) == ' ' || *(p->s) == '\t')
return -1;
{
}
else p->s++;

{ continue ;
char item = p->stack[p->top]; }
p->top--; if (isdigit(*(p->s)) || isalpha (*(p->s)))
return item; {
} while (isdigit(*(p->s)) || isalpha(*(p->s)))
}
{
*(p->t) = *(p->s);
p->s++;
p->t++ ; }
19 PhD. Ngo Huu Phuc, Le Quy Don Technical University
}
11.3. Chương trình minh họa (4/5)
if (*(p->s) == '(') else
{ push(p, *(p->s));
push(p,*(p->s)); p->s++;
p->s++;
}
}
if (*(p->s) == ')')
if (*(p->s) == '*' || *(p->s) == '+' || *(p->s) == '/' || *(p-
>s) == '%' || *(p->s) == '-' || *(p->s) == '$') {
{ opr = pop(p);
if (p->top != -1) while (opr != '(')
{ {
opr = pop(p); *(p->t) = opr;
while ( priority(opr)>= priority (*(p->s)))
p->t++;
{
opr = pop(p); }
*(p->t) = opr;
p->s++;
p->t++;
}
opr = pop(p);
} }

push(p, opr);
push(p, *(p->s)); }

20 PhD. Ngo Huu Phuc, Le Quy Don Technical University


11.3. Chương trình minh họa (5/5)
while (p->top != -1) /* So sanh giua cac toan tu */
{ int priority(char c) {
char opr = pop(p); if (c == '$')
*(p->t) = opr;
return 3;
p->t++;
if (c == '*' || c == '/' || c == '%')
}
return 2;
else {
*(p->t) = '\0';
} if (c == '+' || c == '-')
return 1;
else
return 0; }
}
/* In ket qua */
void show (struct infix p)
{
printf (" %s", p.target);
}
21 PhD. Ngo Huu Phuc, Le Quy Don Technical University
11.4. Một số dạng ký pháp khác
 Ký pháp Prefix:
 Ký pháp Prefix đặt toán tử trước các toán hạng của nó.
 Ví dụ:
/E F có nghĩa E/F
+A*BC có nghĩa A+B*C
 Một số vấn đề khác:
 Chuyển đổi từ Infix sang Prefix.
 Chuyển đổi từ Postfix sang Infix.
 Chuyển đổi từ Postfix sang Prefix.
 Chuyển đổi từ Prefix sang Postfix.
 Trường hợp toán hạng không phải là một số!!!

22 PhD Ngo Huu Phuc, Le Quy Don Technical University


Cấu trúc dữ liệu và giải thuật
Bài 12. Khử đệ quy

Giảng viên: TS. Ngo Huu Phuc


Tel: 0438 326 077
Mob: 098 5696 580
Email: ngohuuphuc76@gmail.com

1 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


Bài 12. Khử đệ quy
Nội dung bài học:
12.1. Khái niệm chung
12.2. Khử đệ quy cho bài toán tính giai thừa.
12.3. Khử đệ quy cho bài toán Fibonacci.
12.4. Khử đệ quy cho bài toán tháp Hanoi.
12.5. Khử đệ quy cho bài toán QuickSort.
Tham khảo:
1. Data structures and Algorithms Stacks.htm
2. Kyle Loudon Mastering Algorithms, Chapter 6 Stacks and Queues
3. Elliz Horowitz – Fundamentals of Data Structures, Chapter 3 Stacks and Queues
4. Deshpande Kakle – C and Data Structures, Chapter 19. Stacks and Queues.
5. Bài giảng TS Nguyễn Nam Hồng.

2 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


12.1. Khái niệm chung (1/7)
Đối với hệ điều hành, thông thường, khi một chương trình con
được gọi, nó sẽ thực hiện:
1. Trước hết, hệ điều hành lưu tất cả các thông tin cần thiết của
chương trình con (thông tin cần thiết).
2. Tiếp theo, chuẩn bị gọi chương trình và chuyển quyền điều
khiển cho chương trình con.
3. Sau đó, khi kết thúc chương trình con, hệ điều hành lấy lại
các thông tin đã lưu ở bước 1 và chuyển quyền điều khiển
đến nơi gọi chương trình con.
Lưu ý: các thông tin được lưu tại Stack của chương trình.

3 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


12.1. Khái niệm chung (2/7)
Stack chương trình:
 Đối với các ngôn ngữ bậc cao, sẽ sử dụng Stack chương trình
cho mỗi lời gọi chương trình con.
 Như vậy, khi chương trình con được gọi, mọi thông tin của
môi trường (tại nơi gọi chương trình con) sẽ được đưa vào
stack chương trình.
 Khi quay trở lại nơi gọi chương trình con, các thông tin được
lấy lại từ stack chương trình.
 Với cách này, bộ nhớ cần thiết sẽ rất lớn khi áp dụng cho bài
toán đệ quy.

4 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


12.1. Khái niệm chung (3/7)
Sử dụng bản ghi dạng stack riêng:
 Một phương pháp đơn giản để có thể kiểm soát được bộ nhớ
của những lời gọi chương trình con, sử dụng stack riêng để lưu
các biến, địa chỉ cần thiết.
 Về mặt bản chất, gần giống với hệ thống, tuy nhiên chỉ lưu trữ
thông tin cần thiết (không phải toàn bộ thông tin trước lời gọi
chương trình con).
 Quá trình lưu và lấy lại tương tự như thao tác trên stack.

5 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


12.1. Khái niệm chung (4/7)
Với cách sử dụng Stack riêng, lưu ý:
 Sử dụng biến để lưu địa chỉ nơi gọi chương trình con.

 Với các tham số dạng giá trị, tạo bản copy của nó khi

lưu.
 Với các biến dạng tham chiếu, lưu trữ địa chỉ của chúng.

 Với các biến cục bộ (khai báo trong đoạn chương trình),

tạo bản copy và lưu chúng.

6 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


12.1. Khái niệm chung (5/7)
Các bước gợi ý trong việc khử đệ quy tổng quát:
Có thể tạo một stack riêng để chứa các bản ghi. Lệnh gọi đệ quy
và lệnh trả về từ hàm đệ quy có thể được thay thế như sau:
 Đưa vào stack một bản ghi chứa các biến cục bộ, các tham số và
vị trí dòng lệnh ngay sau lệnh gọi đệ quy.
 Gán mọi tham số về các trị mới thích hợp.
 Trở về thực hiện dòng lệnh đầu tiên trong giải thuật đệ quy.
 Mỗi lệnh trả về của hàm đệ quy được thay đổi:
 Lấy lại từ stack để phục hồi mọi biến, tham số.
 Bắt đầu thực hiện dòng lệnh tại vị trí mà trước đó đã được cất trong stack.

7 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


12.1. Khái niệm chung (6/7)
Các bước gợi ý trong việc khử đệ quy đuôi:
1. Sử dụng một biến để thay thế cho việc gọi đệ quy.
2. Sử dụng một vòng lặp với điều kiện kết thúc giống như điều
kiện dừng của đệ quy.
3. Đặt tất cả các lệnh vốn cần thực hiện trong lần gọi đệ quy đuôi
vào trong vòng lặp.
4. Thay lệnh gọi đệ quy bằng phép gán.
5. Dùng các lệnh gán để gán các trị như các tham số mà hàm đệ
quy lẽ ra nhận được.
6. Trả về trị cho biến đã định nghĩa ở bước 1.

8 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


12.1. Khái niệm chung (7/7)
Một số lưu ý khi sử dụng:

 Nếu tất cả các tham số có cùng kiểu:

 Sử dụng nhiều thao tác push vào stack.

 Sau đó, sử dụng nhiều thao tác pop để phục hồi thông tin.

 Nếu các tham số có kiểu khác nhau:

 Sử dụng cấu trúc hoặc,

 Sử dụng nhiều stack cho mỗi loại tham số.

9 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


12.2. Khử đệ quy cho bài toán tính giai thừa(1/4)
Xét lại đoạn chương trình tính n!

long factorial(int n)
{
if((n==0)||(n==1)) return 1;
else return n*factorial(n-1);
}

10 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


12.2. Khử đệ quy cho bài toán tính giai thừa(2/4)
 Trong ví dụ trên, phần đệ quy chỉ gọi lại chính nó 1 lần, do

đó các bước để khử đệ quy như sau:


 Sử dụng một Stack để lưu giá trị động nonN.

 Làm rỗng Stack.

 Sử dụng vòng lặp để đưa nonN vào Stack.

 Sử dụng vòng lặp thứ 2 để lấy giá trị từ Stack và thực hiện phép

nhân.
 Lưu ý, địa chỉ lưu vị trí dòng lệnh gọi được đặt tại phép nhân.

11 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


12.2. Khử đệ quy cho bài toán tính giai thừa(3/4)
#include "conio.h" long nonFactorial(int n) {
#include "stdio.h" StackClass<int> stack;
#define MaxEntry 100 if(n<=1) return 1;
#include "StackClass.h“
else {
int nonN=n;
long nonFactorial(int n);
while(nonN>1) {
stack.push(nonN);
void main()
{ nonN--; }

int n; long factorial=1;


printf("Nhap vao mot so: "); while(!stack.isEmpty()) {
scanf("%d",&n); stack.pop(&nonN);
printf("Gia tri cua giai thua: %ld",nonFactorial(n)); factorial=factorial*nonN;
getch(); }
}
return factorial;
}
}

12 PhD. Ngo Huu Phuc, Le Quy Don Technical University


12.2. Khử đệ quy cho bài toán tính giai thừa(4/4)
Thực hiện hàm nonFactorial (4)
nonN = 4;
stack.push(4); nonN=4-1=3;
stack.push(3); nonN=3-1=2;
stack.push(2); nonN=2-1=1;
factorial = 1;
stack.pop(&nonN); nonN=2; factorial = 2*1 = 2;
stack.pop(&nonN); nonN=3; factorial = 2*3 = 6;
stack.pop(&nonN); nonN=4; factorial = 6*4 = 24;
13 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University
12.3. Khử đệ quy cho bài toán Fibonacci đuôi (1/4)
Phân tích lại bài toán tính Fibonaccy đuôi:
long fibonacci(int n, int a, int b)
{
if(n==1) return b;
else return fibonacci(n-1,b,a+b);
}
Lời gọi hàm tương ứng, ví dụ:
printf("Fibonacci cua %d: %ld",n,fibonacci(n,0,1));

14 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


12.3. Khử đệ quy cho bài toán Fibonacci đuôi (2/4)

 Trong ví dụ trên, phần đệ quy đuôi chỉ gọi lại chính nó 1

lần, do đó các bước để khử đệ quy, có thể dùng như sau:


 Sử dụng một Stack để lưu giá trị động nonN.

 Làm rỗng Stack.

 Sử dụng vòng lặp để đưa nonN vào Stack.

 Sử dụng vòng lặp thứ 2 để lấy giá trị từ Stack và thực hiện phép

cộng.
 Lưu ý, địa chỉ lưu vị trí dòng lệnh gọi được đặt tại phép cộng.

15 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


12.3. Khử đệ quy cho bài toán Fibonacci đuôi (3/4)
#include "conio.h" long nonFibonaccy(int n) {
#include "stdio.h" StackClass<int> stack;
#define MaxEntry 100 if(n==1) return 1;
#include "StackClass.h"
else {
int nonN=n;
long nonFibonaccy(int n);
while(nonN>1) {
stack.push(nonN);
void main()
{ nonN--; }

int n; long fibonaccy=0;


printf("Nhap vao mot so: "); int a=0,b=1;
scanf("%d",&n); while(!stack.isEmpty()) {
printf("Gia tri fibonaccy cua %d la: stack.pop(&nonN);
%ld",n,nonFibonaccy(n));
fibonaccy=a+b;
getch();
a=b;
}
b=fibonaccy; }
return fibonaccy; }
}
16 PhD. Ngo Huu Phuc, Le Quy Don Technical University
12.3. Khử đệ quy cho bài toán Fibonacci đuôi (4/4)
Thực hiện hàm nonFibonacci(4)
nonN = 4;
stack.push(4); nonN=4-1=3;
stack.push(3); nonN=3-1=2;
stack.push(2); nonN=2-1=1;
fibonacci = 0; a = 0; b = 1;
stack.pop(&nonN); fibonacci=0+1=1; a=1; b=1;
stack.pop(&nonN); fibonacci=1+1=2; a=1; b=2;
stack.pop(&nonN); fibonacci=1+2=3; a=2; b=3;
17 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University
12.4. Khử đệ quy cho bài toán QuickSort (1/6)
void qsort(int list[],int m,int n) while(i <= j)
{ {
int key,i,j,k; while((i <= n) && (list[i] <= key))
i++;
if( m < n)
while((j >= m) && (list[j] > key))
{
j--;
k = getkeyposition(m,n);
if( i < j)
swap(&list[m],&list[k]);
swap(&list[i],&list[j]);
key = list[m]; }
i = m+1; swap(&list[m],&list[j]);
j = n; qsort(list,m,j-1);
qsort(list,j+1,n);
}
}

18 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


12.4. Khử đệ quy cho bài toán QuickSort (2/6)
Quá trình khử đệ quy cho bài toán QuickSort:
 Để khử đệ quy cho bài toán này, sử dụng Stack, lưu vị trí bắt đầu và vị trí kết
thúc của mỗi đoạn.
 Trong quá trình chọn khóa, có thể sử dụng điểm trung vị, điểm giữa hay điểm
cuối để giảm xác suất của trường hợp tồi nhất.
 Như vậy, sẽ sử dụng nhiều chiến lược chọn khóa khác nhau.
 Bên cạnh đó, đối với những khoảng con có kích thước nhỏ (nhỏ hơn 10 phần
tử), sử dụng phương pháp insert sort thay cho QuickSort.
Lưu ý:
Sử dụng cấu trúc sau để lưu đoạn cần thực hiện việc sắp xếp:
struct index{
int left;
int right;
};
19 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University
12.4. Khử đệ quy cho bài toán QuickSort (3/6)
#include "conio.h" void main() {
#include "stdio.h" int n;
#define MaxEntry 100
printf("Nhap so p.tu cho mang, max = 100\n");
#include "StackClass.h"
scanf("%d",&n);
#define MaxElement 100
// voi truong hop it phan tu, su dung pp khac inputdata(list,n);
#define SmallSize 10 printf("Cac phan tu da nhap:\n");
int list[MaxElement]; printlist(list,n);
struct index{
quicksort(n);
int left;
printf("\nCac phan tu da sap xep:\n");
int right; };
void inputdata(int list[],int n); printlist(list,n);
void printlist(int list[],int n); getch();
void interchange(int *x,int *y); }
void split(int first,int last,int *splitpoint);
void insertion_sort(int first,int last);
void quicksort(int n);

20 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


12.4. Khử đệ quy cho bài toán QuickSort (4/6)
void inputdata(int list[],int n) { void split(int first,int last,int *splitpoint) {
int i; int x,i,j,s,g;
printf("Nhap cac phan tu cho mang\n");
// tim trung vi
for(i=0;i<n;i++)
if (list[first]<list[(first+last)/2]) {
scanf("%d",&list[i]);
fflush(stdin); } s=first; g=(first+last)/2; }
void printlist(int list[],int n) { else {
int i; g=first; s=(first+last)/2; }
printf("Cac phan tu trong mang: \n");
if (list[last]<=list[s]) x=s;
for(i=0;i<n;i++)
else if (list[last]<=list[g])
printf("%d\t",list[i]);
printf("\n"); } x=last;
void interchange(int *x,int *y) { else x=g;
int temp; //trao doi giua khoa va phan tu dau tien
temp=*x; interchange(&list[x],&list[first]);
*x=*y;
x=list[first];
*y=temp; }

21 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


12.4. Khử đệ quy cho bài toán QuickSort (5/6)
// thuc hien phan chia void insertion_sort(int first,int last)
i=first+1; {
j=last; int i,j,c;
while (i<=j)
for (i=first;i<=last;i++)
{
{
while ((list[j]>=x)&&(j>first))
j--; j=list[i];

while ((list[i]<x)&&(i<last)) c=i;


i++; while ((list[c-1]>j)&&(c>first))
if(i<j) {
interchange(&list[i],&list[j]); list[c]=list[c-1];
}
c--;
interchange(&list[first],&list[j]);
}
//Khoa duoc tra ve
*splitpoint=j; list[c]=j;

} }
}
22 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University
12.4. Khử đệ quy cho bài toán QuickSort (6/6)
void quicksort(int n) { if (last-splitpoint<splitpoint-first) {
StackClass<index> stack; point.left=first;
index point; point.right=splitpoint-1;
int first,last,splitpoint; stack.push(point);
point.left=0;
first=splitpoint+1; }
point.right=n-1;
else {
stack.push(point);
point.left=splitpoint+1;
while (!stack.isEmpty()) {
stack.pop(&point); point.right=last;

first = point.left; stack.push(point);


last = point.right; last=splitpoint-1; }
for (;;) { }
if (last-first>SmallSize) { else {
//Kich thuoc lon hon SmallSize //sap xep doan nho
split(first,last,&splitpoint);
insertion_sort(first,last);
//push doan nho vao stack
break; } } } }

23 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


12.5. Khử đệ quy cho bài toán tháp Hanoi (1/5)
Xem xét lại bài toán tháp Hanoi dạng đệ quy:

void HanoiTower(int m, char a, char b, char c)


{
if(m>0)
{
HanoiTower(m-1,a,c,b);
printf("Chuyen dia %d tu %c sang %c\n",m,a,b);
HanoiTower(m-1,c,b,a);
}
}
24 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University
12.5. Khử đệ quy cho bài toán tháp Hanoi (1/3)
Giải quyết bài toán:
 Tương tự như các ví dụ trước, sử dụng Stack để thực hiện khử
đệ quy.
Cấu trúc dữ liệu cho Stack:
Một số khai báo:
struct StackValue
nonN: Số đĩa cần chuyển.
{
nonT: Chỉ số tương ứng với
int nonN;
đĩa cần chuyển.
nonA: tên của cọc nguồn. int nonT;

nonB:tên cọc trung gian. char nonA;


nonC: tên của cọc đích. char nonB;
stack: Stack được dùng. char nonC;
};

25 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


12.5. Khử đệ quy cho bài toán tháp Hanoi (2/3)
#include "conio.h" printf("Ket qua cac buoc \n");
#include "stdio.h" nonHanoiTower(n);
#define MaxEntry 100 getch();
#include "StackClass.h“
}
struct StackValue {
StackValue CreateValue(int n, int t, char a, char b,
int nonN;
char c)
int nonT;
{
char nonA;
StackValue value;
char nonB;
char nonC; }; value.nonN = n;
void nonHanoiTower(int n); value.nonT = t;
StackValue CreateValue(int n, int t, char a, char b, value.nonA = a;
char c);
value.nonB = b;
void main() {
value.nonC = c;
int n;
printf("Nhap vao so dia m = "); return value;

scanf("%d",&n); }

26 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


12.5. Khử đệ quy cho bài toán tháp Hanoi (3/3)
void nonHanoiTower(int n) { {
StackClass<StackValue> stack; value = CreateValue(temp.nonN-
StackValue value; 1,temp.nonN==2?1:0,temp.nonB,temp.nonA,temp
.nonC);
if(n==1)
value = CreateValue(n,1,'A','B','C'); stack.push(value);
else value = CreateValue(1,temp.nonN,
value = CreateValue(n,0,'A','B','C'); temp.nonA,temp.nonB,temp.nonC);

stack.push(value); stack.push(value);
StackValue temp; value = CreateValue(temp.nonN-1 ,
while (!stack.isEmpty()) { temp.nonN==2?1:0,temp.nonA,temp.nonC,temp.n
onB);
stack.pop(&value);
stack.push(value);
temp=value;
if((temp.nonN==1)&&(temp.nonT>0)) }
printf("\nChuyen dia %d tu %c sang }
%c",temp.nonT,temp.nonA,temp.nonC);
}
else

27/27 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University
Cấu trúc dữ liệu và giải thuật
Bài 13. Hàng đợi - Queues

Giảng viên: TS. Ngo Huu Phuc


Tel: 0438 326 077
Mob: 098 5696 580
Email: ngohuuphuc76@gmail.com

1 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


Bài 13. Hàng đợi - Queues
Nội dung:
13.1. Khái niệm về hàng đợi.
13.2. Xây dựng và sử dụng Queue.
13.3. Ví dụ về hàng đợi.

Tham khảo:
1. Data structures and Algorithms Stacks.htm, Kyle Loudon Mastering
Algorithms,
2. Chapter 6 Stacks and Queues, Elliz Horowitz – Fundamentals of Data
Structures.
3. Chapter 3 Stacks and Queues, Deshpande Kakle – C and Data Structures.
4. Bài giảng TS Nguyễn Nam Hồng.
2 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University
13.1. Khái niệm về hàng đợi (1/8)

3 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


13.1. Khái niệm về hàng đợi (2/8)
 Trong ứng dụng máy tính, định nghĩa CTDL hàng đợi là danh
sách, trong đó việc thêm một phần tử được thực hiện ở đầu
một danh sách (cuối hàng đợi) và việc lấy ra một phần tử được
thực hiện ở cuối danh sách (đầu hàng).
 Hàng đợi còn được gọi là danh sách FIFO (First In First Out).

Ví dụ về hàng đợi
4 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University
13.1. Khái niệm về hàng đợi (3/8)
 Đối với hàng đợi, số lượng ứng dụng có nhiều hơn cả ngăn
xếp.
 Ví dụ như máy tính thực hiện nhiệm vụ, có nhiều hàng đợi
được sử dụng:
 hàng đợi máy in,
 việc truy xuất đĩa,
 sử dụng CPU.
 chuyển đổi từ Infix sang Prefix.

 Phần tử đầu hàng đợi được phục vụ trước, phần tử này thường
gọi là front hay head. Phần tử mới thêm vào được gọi là rear
hay tail.
5 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University
13.1. Khái niệm về hàng đợi (4/8)
Định nghĩa:
Một hàng đợi các phần tử kiểu T là một chuỗi nối tiếp các phần
tử của T và kèm theo một số tác vụ sau:
1. Tạo mới một đối tượng hàng rỗng.
2. Thêm một phần tử mới vào hàng, giả sử hàng đợi chưa đầy
(phần tử dữ liệu mới luôn được thêm vào cuối hàng).
3. Loại một phần tử ra khỏi hàng, giả sử hàng chưa rỗng (phần tử
bị loại là phần tử tại đầu hàng, thường là phần tử vừa được xử lý
xong).
4. Xem phần tử tại đầu hàng (phần tử sắp được xử lý).

6 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


13.1. Khái niệm về hàng đợi (5/8)

Hoạt động của hàng đợi

7 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


13.1. Khái niệm về hàng đợi (3/8)
Xây dựng Queue:
 Thành phần dữ liệu cho Queue:
 MaxEntry: Kích thước của Queue.
 QueueEntry: Kiểu dữ liệu dành cho mỗi phần tử trong Queue.
 Một số phương thức cơ bản của Queue:
 QueueClassL();
 int isEmpty();
 int isFull();
 int Dequeue(QueueEntry *value);
 int Enqueue(QueueEntry value);
 int Peek(QueueEntry *value);
 void makeEmpty();

8 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


13.1. Khái niệm về hàng đợi (4/8)
Khai báo lớp Queue (dạng tuyến tính):
template<class QueueEntry> // Lớp mẫu cho kiểu dữ liệu của Queue
class QueueClassL{ // Định nghĩa tên lớp
public:
QueueClassL(); // Hàm tạo của lớp, khởi tạo thông tin cho Queue
int isEmpty(); // Queue rỗng → 1, ngược lại → 0
int isFull(); // Queue đầy → 1, ngược lại → 0
int Dequeue(QueueEntry *value); // Lấy 1 phần tử Queue
int Enqueue(QueueEntry value); // Thêm 1 phần tử vào Queue
int Peek(QueueEntry *value); // Xem giá trị tại đầu của Queue
void makeEmpty(); // Làm rỗng Queue
private:
int front, rear; // front: đầu Queue, rear: cuối Queue
QueueEntry entry[MaxEntry]; // Mảng lưu thông tin của Queue
};
9 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University
13.1. Khái niệm về hàng đợi (5/8)
Một số phương thức của Queue:

template<class QueueEntry> template<class QueueEntry>


QueueClassL<QueueEntry> int QueueClassL<QueueEntry>
::QueueClassL(void) ::isEmpty()
{ {
front=rear=-1; if(front==rear)
} return 1;
else return 0;
}

10 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


13.1. Khái niệm về hàng đợi (6/8)
Một số phương thức của Queue:
template<class QueueEntry>
template<class QueueEntry> int QueueClassL<QueueEntry>
::Dequeue(QueueEntry *value)
int QueueClassL<QueueEntry>::isFull()
{
{
if(!isEmpty())
if(rear==MaxEntry-1)
{
return 1;
front++;
else return 0; *value = entry[front];
} return 1;
}
else
return 0;
}

11 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


13.1. Khái niệm về hàng đợi (7/8)
Một số phương thức của Queue: template<class QueueEntry>
template<class QueueEntry> int QueueClassL<QueueEntry>
int QueueClassL<QueueEntry> ::Peek(QueueEntry *value)
::Enqueue(QueueEntry value) { if(!isEmpty() && front!=-1)
{ { *value = entry[front];
if(!isFull()) return 1;
{ }
rear++; else return 0;
entry[rear]=value; }
return 1; template<class QueueEntry>
} void QueueClassL<QueueEntry>
else ::makeEmpty()
return 0; {
} front=rear=-1;
}

12 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


13.1. Khái niệm về hàng đợi (8/8)
Một số dạng của hàng đợi:
 Hàng đợi tuyến tính - Linear Queues
 Tổ chức hàng đợi theo nghĩa thông thường.

 Hàng đợi vòng - Circular Queues


 Giải quyết việc thiếu bộ nhớ khi sử dụng hàng đợi.

 Hàng đợi ưu tiên - Priority Queues


 Mỗi phần tử có kết hợp thêm thông tin về độ ưu tiên.
 Khi chương trình cần lấy một phần tử khỏi hàng đợi, nó sẽ xét
những phần tử có độ ưu tiên cao trước.
 Multi-Headed Queues.
13 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University
13.2. Xây dựng và sử dụng Queue (1/7)
Với Queue đã được khai báo, xây dựng chương trình sử dụng Queue
#include <stdio.h> void main() {
#include <stdlib.h> QueueClassL<QueueEntry> queue;
#include <conio.h> QueueEntry value;
typedef int QueueEntry;
int key;
#define MaxEntry 10
char ch;
#include "QueueClass.h“
do {
void menu()
menu();
{
printf("1. Nhap vao QUEUE\n"); printf("Moi lua chon: ");

printf("2. Lay ra khoi QUEUE\n"); scanf("%d",&key);


printf("3. Gia tri tren dinh QUEUE la gi?\n"); switch (key) {
printf("4. QUEUE rong?\n"); case 1:
printf("5. QUEUE day?\n"); printf("Nhap phan tu can dua vao QUEUE\n");
printf("6. Lam rong QUEUE!\n"); scanf("%d",&value);
printf("7. Thoat\n");
if(queue.Enqueue(value))
}
printf("Da them vao Queue\n");

14 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


13.2. Xây dựng và sử dụng Queue (2/7)
else printf("QUEUE van co gia tri\n");
printf("Queue da day!\n"); break; break;
case 2: case 5:
printf("Lay mot phan tu khoi QUEUE\n");
if(queue.isFull()==1)
if(queue.Dequeue(&value))
printf("QUEUE day\n");
printf("Gia tri lay ra: %d\n",value);
else
else
printf("QUEUE chua day\n");
printf("Queue rong\n");
break; break;

case 3: case 6:
if(queue.Peek(&value)) printf("Lam rong QUEUE? Co chac khong? (C/K)");
printf("Gia tri tai front QUEUE: %d\n",value); fflush(stdin);
else scanf("%c",&ch);
printf("Queue rong\n"); if(ch=='c' || ch=='C')
break;
queue.makeEmpty();
case 4:
break;
if(queue.isEmpty()==1)
}
printf("QUEUE rong\n");
} while (key!=7); }
else
15 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University
13.2. Xây dựng và sử dụng Queue (3/7)
Một số nhận xét cho dạng Queue nói trên:
 Đáp ứng được các tiêu chí về Queue.
 Tuy nhiên, với kích thước Queue là 10 (theo ví dụ) chỉ có thể
thêm tối 10 phần tử.
 Ngoài ra, vì front chỉ tăng, do đó, trong Queue vẫn còn ô nhớ
nhưng không sử dụng được.
Giải quyết vấn đề trên:
 Sử dụng Queue có dạng vòng.
 Khi đó, làm thế nào để biết Queue đã đầy hay rỗng?
 Giá trị khởi tạo cho front và rear như thế nào?
 Xây dựng Queue như thế nào?
16 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University
13.2. Xây dựng và sử dụng Queue (4/7)
Ví dụ về Queue
có dạng vòng

17 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


13.2. Xây dựng và sử dụng Queue (5/7)
Hoạt động của Queue After 5
front 1 dequeues
1 After 7 enqueues 0
0 front
12
12 11
11 rear
10
10 rear
rear
1
0
front
12
11
After 8 more enqueues
10

18 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


13.2. Xây dựng và sử dụng Queue (6/7)
Xây dựng hàng đợi vòng
template<class QueueEntry> template<class QueueEntry>
class QueueClassC{ QueueClassC<QueueEntry>::QueueClassC(void)
public:
{
QueueClassC();
front = 0;
int isEmpty();
int isFull(); rear = 0;

int Dequeue(QueueEntry *value); }


int Enqueue(QueueEntry value); template<class QueueEntry>
int Peek(QueueEntry *value); int QueueClassC<QueueEntry>::isEmpty()
void makeEmpty();
{
private:
if(front= =rear)
int front, rear;
QueueEntry entry[MaxEntry]; return 1;

}; else return 0;

19 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


13.2. Xây dựng và sử dụng Queue (7/7)
template<class QueueEntry> template<class QueueEntry>
int QueueClassC<QueueEntry>::isFull() int QueueClassC<QueueEntry>::Enqueue(QueueEntry
{ value)
if((rear+1)%MaxEntry== front) { if(!isFull()) {
return 1; rear = (rear+1) % MaxEntry;
else return 0; entry[rear]=value; return 1; }
} else return 0; }
template<class QueueEntry>
template<class QueueEntry>
int QueueClassC<QueueEntry>::Dequeue(QueueEntry
*value) int QueueClassC<QueueEntry>::Peek(QueueEntry *value)

{ { if(!isEmpty()) {
if(!isEmpty()) *value = entry[(front+1)%MaxEntry];
{ return 1; }
front = (front+1)%MaxEntry; else return 0; }
*value = entry[front]; template<class QueueEntry>
return 1;
void QueueClassC<QueueEntry>::makeEmpty()
}
{ front=rear=0;
else
}
return 0; }

20 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


13.3. Ví dụ về hàng đợi (1/5)
Palindromes (1/4)
 Khái niệm: Một chuỗi được gọi là Palindrome nếu như đọc
xuôi giống đọc ngược.
 Bài toán: Cho trước một chuỗi, kiểm tra xem chuỗi đó có phải
là chuỗi palindrome hay không?
 Ví dụ về chuỗi palindrome:
Able was I ere I saw Elba
 Giải pháp:
 Để tránh ảnh hưởng tới chuỗi ban đầu, đọc chuỗi nói trên vào
stack và queue.
 So sánh từng phần tử của stack và queue, nếu giống nhau từng
cặp thì đó là chuỗi Palindrome, ngược lại thì chuỗi trên không
phải là chuỗi Palindrome.

21 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


13.3. Ví dụ về hàng đợi (2/5)
Kiểm tra chuỗi có phải là chuỗi Palindrome hay không?
... int palindrome(char* sstring) {
typedef char StackEntry; StackClass<StackEntry> stack;
typedef char QueueEntry; QueueClassC<QueueEntry> queue;
#define MaxEntry 100
for(int i=0;i<strlen(sstring);i++) {
#include "StackClass.h"
stack.push(sstring[i]);
#include "QueueClass.h"
queue.Enqueue(sstring[i]); }
...
int kt=0;
int palindrome(char* sstring);
void main() { while(!queue.isEmpty() && !stack.isEmpty())
char sstring[100]; {
printf("Nhap mot xau ky tu: "); gets(sstring); StackEntry a;
int vt = palindrome(sstring); QueueEntry b;
if(!vt) stack.pop(&a);
printf("Chuoi da nhap la chuoi Palindrome!"); queue.Dequeue(&b);
else
if(a!=b) kt++;
printf(“Ko phai Palindrome!, co %d cap ky tu
khong khop!",vt/2); }
getch(); return kt;
} }

22 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


13.3. Ví dụ về hàng đợi (3/5)
Tổ chức dữ liệu hợp lý - Demerging (1/4)

Bài toán: Xem xét bài toán sau:


 Giả sử, với một hệ thống quản lý nhân sự. Các bản ghi được
lưu trên file.
 Mỗi bản ghi gồm các trường: Họ tên, giới tính, ngày tháng
năm sinh, ...
 Dữ liệu trên đã được sắp theo ngày tháng năm sinh.
 Cần tổ chức lại dữ liệu sao cho nữ được liệt kê trước nam
nhưng vẫn giữ được tính đã sắp theo ngày tháng năm sinh.
23 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University
13.3. Ví dụ về hàng đợi (4/5)
Tổ chức dữ liệu hợp lý - Demerging (1/4)

Cách giải quyết:

 Ý tưởng không hiệu quả:

 Sử dụng thuật toán sắp xếp.

 Độ phức tạp của thuật toán O(n log n) trong trường hợp tốt nhất.

 Ý tưởng hiệu quả hơn:

 Sử dụng giải thuật demerging.

 Độ phức tạp của giải thuật này là O(n).

24 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


13.3. Ví dụ về hàng đợi (5/5)
Giải thuật Demerging.
1. Tạo 2 queue rỗng, có tên lần lượt là NU và NAM.
2. Với mỗi bản ghi p, xem xét:
2.1. Nếu p có giới tính nữ, đưa vào queue NU.
2.2. Nếu p có giới tính nam, đưa vào queue NAM.
3. Xét queue NU, khi queue chưa rỗng:
3.1. Lấy từng phần tử trong queue này.
3.2. Ghi vào file output nào đó.
4. Xét queue NAM, khi queue chưa rỗng:
4.1. Lấy từng phần tử trong queue này.
4.2. Ghi tiếp vào file output trên.
5. Kết thúc giải thuật.
25/25 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University
13.3. Thuật toán chuyển đổi biểu thức dạng
Infix sang Prefix (1/4)
Thuật toán thứ nhất: chuyển đổi biểu thức từ Infix sang Prefix:
Sử dụng queue, stack và stackkq.
 Bước 1 : Đọc một thành phần của biểu thức E (dạng Infix biểu diễn bằng
xâu, đọc từ phải qua trái). Giả sử thành phần đọc được là x
1.1. Nếu x là toán hạng thì đưa nó vào queue.
1.2. Nếu x là dấu ‘)’ thì đẩy nó vào stack.
1.3. Nếu x là một trong các phép toán +, -, *, / thì
1.3.1. Kiểm tra xem stack có rỗng không? Nếu rỗng, đẩy vào stack, nếu
không rỗng, sang bước 1.3.2.
1.3.2. Lấy phần tử y ở đỉnh stack.
1.3.3. Nếu Pri(y)>=Pri(x), đưa tất cả các phần tử trong queue vào
stackkq, đưa y vào stackkq, đưa x vào stack.
1.3.4. Nếu Pri(y)<Pri(x) thì đẩy x vào stack.

26 PhD Ngo Huu Phuc, Le Quy Don Technical University


13.3. Thuật toán chuyển đổi biểu thức dạng
Infix sang Prefix (1/4)
1.4. Nếu x là dấu ‘(’ thì
1.4.1. Đưa tất cả các phần tử trong queue vào stackkq,
1.4.2. Xét phần tử y ở đầu của stack.
1.4.3. y là phép toán thì loại y ra khỏi stack, đưa y vào stackkq, quay về
bước 1.4.2.
1.4.3. Nếu y là dấu ‘)’ loại y ra khỏi stack.
 Bước 2 : Lập lại bước 1 cho đến khi toàn bộ biểu thức E được duyệt.
 Bước 3 : Đưa tất cả các phần tử trong queue vào stackkq, tất cả phần tử
trong stack và stackkq.
 Bước 4: Lấy từng phần tử trong stackkq ra, đó là kết quả dạng Prefix.
 Bước 5: Tính giá trị của biểu thức dưới dạng tiền tố.
Chú ý:
Pri(‘(‘)=Pri(‘)‘) <Pri(‘+‘)=Pri(‘-‘)<Pri(‘*‘)=<Pri(‘/‘)

27 PhD Ngo Huu Phuc, Le Quy Don Technical University


13.3. Thuật toán chuyển đổi biểu thức dạng
Infix sang Prefix (1/4)
Thuật toán thứ hai: Tính giá trị biểu thức dạng Prefix:

 Bước 1 : Đọc lần lượt các phần tử của biểu thức E1 (từ phải qua trái)

 Nếu gặp toán hạng thì đẩy nó vào stack.

 Nếu gặp phép toán thì lấy hai phần tử liên tiếp trong stack thực hiện phép

toán, kết quả được đẩy vào trong stack.

 Bước 2 : Lập lại bước 1 cho đến khi hết tất cả các phần tử trong biểu thức

E1. Lúc đó đỉnh của stack chứa giá trị của biểu thức cần tính.

 Bước 3. Kết thúc.

28 PhD Ngo Huu Phuc, Le Quy Don Technical University


13.3. Ví dụ về chuyển đổi từ Infix sang Prefix (1/2)
Cho biểu thức: E=a*b+c/d.
Bước E Phần tử xét queue stack stackkq
0 a*b+c/d null empty empty null
1 a*b+c/ d d empty null
2 a*b+c / d / null
3 a*b+ c dc / null
4 a*b + empty + dc/
5 a* b b + dc/
6 A * b *+ dc/
7 Null a ba *+ dc/
8 Null null empty empty dc/ba*+
Kết quả: +*ab/cd
29 PhD Ngo Huu Phuc, Le Quy Don Technical University
13.3. Ví dụ về tính biểu thức dạng Prefix (2/2)
Xét biểu thức dạng Prefix: (đọc từ phải qua trái)
/+7 8 + 6 * + 3 2 + 5 4

4 4 4 9 9 9 9 9 9 45 45 45 51 51 51
5 5 2 2 2 5 5 6 6 8 8
+ 3 3 * + 7
+
51 51 51 3.4
8 15 15
7 /
+
30 PhD Ngo Huu Phuc, Le Quy Don Technical University
13.3. Chương trình minh họa (1/4)
#include "stdio.h" strcpy(input,InfixToPrefix(input));
#include "conio.h" show(input);
#include "string.h" printf("\nGia tri bieu thuc: %d",calculate(input));
#include "ctype.h"
getch();
#define MaxEntry 100
}
#include "StackClass.h"
int priority(char c) {
#include "QueueClass.h"
if (c == '$')
typedef char StackEntry;
typedef char QueueEntry; return 3;

int priority(char c); if (c == '*' || c == '/' || c == '%')


char *InfixToPrefix(char *Infix); return 2;
void show(char *); else {
int calculate(char []); if (c == '+' || c == '-')
void main() { return 1;
char input[MaxEntry];
else
printf("Nhap Infix:");
return 0;
gets(input);
}
printf("Output:");
}
31 PhD. Ngo Huu Phuc, Le Quy Don Technical University
13.3. Chương trình minh họa (2/4)
char *InfixToPrefix(char *Infix) { if(Infix[i]=='+' || Infix[i]=='-' || Infix[i]=='*' ||
StackClass<char> stack, stackkq; Infix[i]=='/' || Infix[i]=='%' || Infix[i]=='$') {

QueueClassL<char> queue; if(!stack.isEmpty()) {


int n=strlen(Infix); stack.pop(&opr);
char opr, kt, vt; while(priority(opr)>=priority(Infix[i])) {
int i=n-1; while(!queue.isEmpty()) {
while(i>=0) { queue.Dequeue(&kt);
vt=Infix[i];
stackkq.push(kt); }
if(Infix[i]==' ' || Infix[i]=='\t') {
stackkq.push(opr);
i--;
vt=stack.pop(&opr);
continue;
if(!vt) break; }
}
if(isdigit(Infix[i]) || isalpha(Infix[i])) { if(vt) stack.push(opr);
queue.Enqueue(Infix[i]); } stack.push(Infix[i]); }
if(Infix[i]==')') { else {
stack.push(Infix[i]); } stack.push(Infix[i]);
}
}

32 PhD. Ngo Huu Phuc, Le Quy Don Technical University


13.3. Chương trình minh họa (3/4)
if(Infix[i]=='(') { char kq[MaxEntry];
while(!queue.isEmpty()) { i=0;
queue.Dequeue(&opr); while(!stackkq.isEmpty()) {
stackkq.push(opr); }
stackkq.pop(&opr);
stack.pop(&opr);
kq[i]=opr;
while(opr!=')') {
i++;
stackkq.push(opr);
}
stack.pop(&opr); }
} kq[i]='\0';

i--; return kq; }


} void show(char *value)
while(!queue.isEmpty()) { {
queue.Dequeue(&opr); puts(value);
stackkq.push(opr); } }
while(!stack.isEmpty()) {
stack.pop(&opr);
stackkq.push(opr);
}

33 PhD. Ngo Huu Phuc, Le Quy Don Technical University


13.3. Chương trình minh họa (4/4)
int calculate(char input[MaxEntry]) { if(input[i]=='-')
int n=strlen(input); a=a-b;
StackClass<char> stack; if(input[i]=='*')
int i=n-1;
a=a*b;
while(i>=0) {
if(input[i]=='/')
char vt=input[i];
a=a/b;
if(isdigit(input[i]))
if(input[i]=='%')
stack.push(input[i]);
if(input[i]=='+' || input[i]=='-' || input[i]=='*' a=a%b;
|| input[i]=='/' || input[i]=='%' || input[i]=='$') { a=a+'0';
char a; stack.push(a); }
char b;
i--;
stack.pop(&a);
}
stack.pop(&b);
char a;
a=a-'0';
stack.pop(&a);
b=b-'0';
if(input[i]=='+') return a-'0';

a=a+b; }

34 PhD. Ngo Huu Phuc, Le Quy Don Technical University


Cấu trúc dữ liệu và giải thuật
Bài 15. Danh sách liên kết

Giảng viên: TS. Ngo Huu Phuc


Tel: 0438 326 077
Mob: 098 5696 580
Email: ngohuuphuc76@gmail.com

1 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University Tháng 09 năm 2009
Bài 15: Danh sách liên kết
Nội dung:
15.1. Giới thiệu chung.
15.2. Danh sách liên kết đơn.
15.2.1. Khái niệm về danh sách liên kết đơn.
15.2.2. Các thao tác cơ bản của danh sách liên kết đơn.
15.3. Danh sách liên kết vòng.
15.3.1. Khái niệm về danh sách liên kết vòng.
15.3.2. Các thao tác cơ bản của danh sách liên kết vòng.
15.4. Danh sách liên kết kép.
15.4.1. Khái niệm về danh sách liên kết kép.
15.4.2. Các thao tác cơ bản của danh sách liên kết kép.
15.5. Một số ví dụ về danh sách liên kết.
Tham khảo:
1. Deshpande Kakde: C and Data structures.chm, Chapter 20: Linked Lists
2. Elliz Horowitz – Fundamentals of Data Structures.chm, Chapter 4: Linked Lists
3. Kyle Loudon: Mastering Algorithms with C.chm, Chapter 5 Linked Lists.
4. Bài giảng TS Nguyễn Nam Hồng

2 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University Tháng 09 năm 2009
15.1. Giới thiệu chung (1/2)
 Với CTDL dạng mảng, bộ nhớ được sử dụng là một dãy liền

kề và có kích thước cố định.

 Tuy nhiên, CTDL này có m ột số nhược điểm:

 Thời gian cho việc thêm hay bớt phần tử trong mảng khá lâu vì

phải thay đổi cả các phần tử còn lại trong mảng.


 Ngay cả khi khai báo một lượng lớn các phần tử cho mảng để có

thể áp dụng được cho nhiều bài toán, chúng ta cũng thấy khả năng
dư thừa bộ nhớ xuất hiện.

3 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University Tháng 09 năm 2009
15.1. Giới thiệu chung (2/2)
 Để khắc phục nhược điểm trên, có thể sử dụng danh sách liên

kết như là cấu trúc dữ liệu thay thế.

 Trong cấu trúc này, không cần xác định kích thước cho các

phần tử trước.

 Ta có thể định nghĩa phần tử bất cứ lúc nào, sau đó liên kết

phần tử đó với danh sách đã có trước đó.

 Như vậy, mỗi phần tử sẽ bao gồm thông tin cần lưu trữ và liên

kết với các phần tử khác.

4 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University Tháng 09 năm 2009
15.2. Danh sách liên kết đơn (1/23)
 Trong rất nhiều trường hợp cần sử dụng đến danh sách liên kết
động, danh sách liên kết động cần dùng đến khi kích thước
danh sách chưa biết tại thời điểm biên dịch chương trình.
 Khi đó, danh sách có thể mở rộng hoặc thu hẹp lại tại thời
điểm chạy chương trình.
 Cấu trúc dữ liệu linked list sử dụng mô hình liên kết động.
 Một số dạng của danh sách liên kết:
 Danh sách liên kết đơn.
 Danh sách liên kết vòng.
 Danh sách liên kết kép.

5 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University Tháng 09 năm 2009
15.2. Danh sách liên kết đơn (2/23)
15.2.1. Khái niệm về danh sách liên kết đơn

 Danh sách liên kết đơn là một


Link
cấu trúc dữ liệu bao gồm một tập Data Add
các nút, mà mỗi nút bao gồm:
 Dữ liệu cần lưu trữ Node

 Liên kết đến nút tiếp theo

60 800 45 90 55 0 NULL

1000 800 90

6 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University Tháng 09 năm 2009
15.2. Danh sách liên kết đơn (3/23)
15.2.1. Khái niệm về danh sách liên kết đơn

 Để quản lý danh sách liên kết, thông thường cần:


• Start là con trỏ chỉ đến phần tử đầu tiên của danh sách liên kết.
• Phần tử cuối của danh sách liên kết với vùng liên kết có nội dung
NULL.

start

60 800 45 90 55 0 NULL

1000 800 90

7 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University Tháng 09 năm 2009
15.2. Danh sách liên kết đơn (4/23)
15.2.1. Khái niệm về danh sách liên kết đơn
Khai báo cấu trúc một Node của danh sách:
template <class ListEntry>
struct node
{
ListEntry data;
struct node *link;
};

8 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University Tháng 09 năm 2009
15.2. Danh sách liên kết đơn (5/23)
15.2.2. Các thao tác cơ bản của danh sách liên kết đơn
1. Khởi tạo danh sách.
2. LAdd: Thêm một node vào đầu danh sách.
3. LInsert: Chèn một node vào danh sách.
4. LAppend: Thêm một node vào cuối danh sách.
5. LFind: Tìm một node trong danh sách.
6. LDelete: Xóa một node trong danh sách.
7. LLength: Số phần tử trong danh sách.
8. LMakeEmpty: Làm rỗng danh sách.
9. LGet: Lấy thông tin về một phần tử trong danh sách.

9 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University Tháng 09 năm 2009
15.2. Danh sách liên kết đơn (6/23)
15.2.2. Các thao tác cơ bản của danh sách liên kết đơn
class LList {
public:
LList();
int LAdd(ListEntry entry);
int LInsert(ListEntry value, ListEntry entry);
int LAppend(ListEntry entry);
int LFind(ListEntry value);
int LDelete(ListEntry value);
int LLength();
void LMakeEmpty();
int LGet(int pos, ListEntry *value);
template<typename ListEntry> friend void LPrint(LList<ListEntry> list);
private:
node<ListEntry> *start;
};

10 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University Tháng 09 năm 2009
15.2. Danh sách liên kết đơn (7/23)
15.2.2. Các thao tác cơ bản của danh sách liên kết đơn
Khởi tạo danh sách:
template <class ListEntry>
LList<ListEntry>::LList() start
{
start = NULL;
NULL
}

11 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University Tháng 09 năm 2009
15.2. Danh sách liên kết đơn (8/23)
15.2.2. Các thao tác cơ bản của danh sách liên kết đơn
Thêm một node đầu danh sách:
 Nếu danh sách rỗng, cấp phát ô nhớ và start temp 1
cho start trỏ vào ô nhớ đó.
 Nếu danh sách không rỗng:
 Cấp phát ô nhớ cho biến temp. data link NULL
 Phần liên kết của temp trỏ vào đầu danh
sách. 2
 Con trỏ start trỏ vào temp.

temp start

data link data link NULL

12 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University Tháng 09 năm 2009
15.2. Danh sách liên kết đơn (9/23)
15.2.2. Các thao tác cơ bản của danh sách liên kết đơn
Thêm một node đầu danh sách: else {
template <class ListEntry> temp=(node<ListEntry> *)
int LList<ListEntry>::LAdd(ListEntry entry) {
malloc(sizeof(node<ListEntry>));
int kt=0;
if(temp==NULL) {
node<ListEntry> *temp;
printf("Loi cap phat bo nho!\n");
if(start==NULL) {
start=(node<ListEntry> *) return kt; }
malloc(sizeof(node<ListEntry>)); kt=1;
if(start==NULL) { temp->data=entry;
printf("Loi cap phat bo nho!\n"); temp->link=start;
return kt; }
start=temp;
kt=1;
}
start->data=entry;
return kt;
start->link=NULL;
} }

13 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University Tháng 09 năm 2009
15.2. Danh sách liên kết đơn (10/23)
15.2.2. Các thao tác cơ bản của danh sách liên kết đơn
Chèn 1 node vào danh sách:
 Nhập thông tin về node đứng trước node thêm mới.
 Sử dụng con trỏ temp để đến được node đứng trước đó.
 Cấp phát ô nhớ cho biến temp1.
 Phần liên kết của temp1 trỏ vào phần liên kết của con trỏ temp.
 Phần liên kết của con trỏ temp trỏ vào temp1.

start temp

data link data link data link data link NULL


2 1
temp1 data link
14 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University Tháng 09 năm 2009
15.2. Danh sách liên kết đơn (11/23)
15.2.2. Các thao tác cơ bản của danh sách liên kết đơn
Chèn 1 node vào danh sách : if(temp!=NULL) {
template <class ListEntry> temp1=(node<ListEntry> *)
int LList<ListEntry>::LInsert(ListEntry value, malloc(sizeof(node<ListEntry>));
ListEntry entry) {
if(temp1==NULL) {
int kt=0;
node<ListEntry> *temp, *temp1; printf("Loi cap phat bo nho!\n");
if(start==NULL) printf("Danh sach rong!"); return kt; }
else { kt=1;
temp=start; temp1->data=entry;
while(temp!=NULL) {
temp1->link=temp->link;
if(temp->data==value)
temp->link=temp1;
break;
}
else
temp=temp->link; }
} return kt; }

15 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University Tháng 09 năm 2009
15.2. Danh sách liên kết đơn (12/23)
15.2.2. Các thao tác cơ bản của danh sách liên kết đơn
Thêm một node cuối danh sách:
 Nếu danh sách rỗng, cấp phát ô nhớ và start temp 1
cho start trỏ vào ô nhớ đó.
 Nếu danh sách không rỗng:
 Sử dụng con trỏ temp đi đến cuối danh data link NULL
sách.
 Cấp phát ô nhớ cho temp->link.
2
 Phần liên kết của temp->link trỏ vào
NULL.
start temp

data link data link data link NULL

1 data link 2

16 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University Tháng 09 năm 2009
15.2. Danh sách liên kết đơn (13/23)
15.2.2. Các thao tác cơ bản của danh sách liên kết đơn
Thêm một node cuối danh sách: else {
template <class ListEntry> temp=start;
int LList<ListEntry>::LAppend(ListEntry entry) { while(temp->link!=NULL)
int kt=0;
temp=temp->link;
node<ListEntry> *temp;
temp->link=(node<ListEntry> *)
if(start==NULL) {
start=(node<ListEntry> *) malloc(sizeof(node<ListEntry>));

malloc(sizeof(node<ListEntry>)); if(temp->link==NULL) {
if(start==NULL) { printf("Loi cap phat bo nho!\n");
printf("Loi cap phat bo nho!\n"); return kt; }
return kt; } kt=1; temp=temp->link;
kt=1;
temp->data=entry;
start->data=entry;
temp->link=NULL; }
start->link=NULL;
} return kt;
}
17 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University Tháng 09 năm 2009
15.2. Danh sách liên kết đơn (14/23)
15.2.2. Các thao tác cơ bản của danh sách liên kết đơn
Tìm một node trong danh sách:
 Sử dụng con trỏ temp để duyệt qua
danh sách.
 Sử dụng thêm biến pos để lưu vị trí
của node trong danh sách. Nếu danh
sách rỗng hoặc không tìm thấy trả về
0, ngược lại trả về vị trí của node.

start temp pos = 3

data link data link data link data link NULL

18 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University Tháng 09 năm 2009
15.2. Danh sách liên kết đơn (15/23)
15.2.2. Các thao tác cơ bản của danh sách liên kết đơn
Tìm một node trong danh sách:
template <class ListEntry>
int LList<ListEntry>::LFind(ListEntry value) {
int pos=0;
node<ListEntry> *temp=start;
while(temp!=NULL) {
pos++;
if(temp->data==value)
break;
temp=temp->link;
}
if(temp!=NULL)
return pos;
else return 0;
}

19 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University Tháng 09 năm 2009
15.2. Danh sách liên kết đơn (16/23)
15.2.2. Các thao tác cơ bản của danh sách liên kết đơn
Xóa một node trong danh sách: prev curr
 Sử dụng 2 con trỏ prev và curr để tìm 1
node cần xóa. Con trỏ prev trỏ vào node start
trước node cần xóa. Con trỏ curr trỏ vào
node cần xóa.
NULL data link data link NULL
 Nếu curr = start, cho start trỏ vào
start->link và giải phóng ô nhớ của curr.
 Nếu không, prev->link trỏ tới
curr->link và giải phóng ô nhớ của curr.

2
start prev curr

data link data link data link data link NULL

20 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University Tháng 09 năm 2009
15.2. Danh sách liên kết đơn (17/23)
15.2.2. Các thao tác cơ bản của danh sách liên kết đơn
Xóa một node trong danh sách : if(curr->data==value) kt=1;
int LList<ListEntry>::LDelete(ListEntry value) { else kt=2;
node<ListEntry> *prev, *curr; if(kt==1) {
int kt=0; if(prev==NULL) {
if(start==NULL) return kt;
start=start->link;
else {
free(curr); }
prev=NULL;
else {
curr=start;
prev->link=curr->link;
while(!kt && curr->link!=NULL) {
free(curr);
if(curr->data==value) {
kt=1; }
break; } }
else { }
prev=curr; return kt;
curr=curr->link; } } }
21 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University Tháng 09 năm 2009
15.2. Danh sách liên kết đơn (18/23)
15.2.2. Các thao tác cơ bản của danh sách liên kết đơn
Lấy thông tin một node ở vị trí nào đó:
 Sử dụng con trỏ temp để duyệt qua
danh sách.
 Duyệt cho đến khi đến node thứ pos,
lấy thông tin tại node đó và trả lại cho
nơi gọi hàm.

Cho trước pos = 3

start temp

data link data link data link data link NULL

22 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University Tháng 09 năm 2009
15.2. Danh sách liên kết đơn (19/23)
15.2.2. Các thao tác cơ bản của danh sách liên kết đơn
Lấy thông tin một node ở vị trí nào đó :
template <class ListEntry>
int LList<ListEntry>::LGet(int pos, ListEntry *value) {
int i=0;
node<ListEntry> *temp=start;
while(temp!=NULL && i<pos) {
temp=temp->link;
if(temp!=NULL)
i++;
}
if(i!=pos) return 0;
else {
*value=temp->data;
return 1;
}
}

23 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University Tháng 09 năm 2009
15.2. Danh sách liên kết đơn (20/23)
15.2.2. Các thao tác cơ bản của danh sách liên kết đơn
Xác định số phần tử trong danh sách:
 Sử dụng con trỏ temp để duyệt qua
danh sách, cho đến khi temp=NULL.
 Sử dụng một biến length để đếm số
phần tử đã duyệt.

start temp length = 3

data link data link data link data link NULL

24 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University Tháng 09 năm 2009
15.2. Danh sách liên kết đơn (21/23)
15.2.2. Các thao tác cơ bản của danh sách liên kết đơn
Xác định số phần tử trong danh sách:
template <class ListEntry>
int LList<ListEntry>::LLength() {
int length=0;
node<ListEntry> *temp=start;
while(temp!=NULL)
{
length++;
temp=temp->link;
}
return length;
}
25 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University Tháng 09 năm 2009
15.2. Danh sách liên kết đơn (22/23)
15.2.2. Các thao tác cơ bản của danh sách liên kết đơn
Làm rỗng danh sách:
 Xóa từng phần tử trong danh sách.
 Sử dụng con trỏ temp để xác định
phần tử cần xóa (từ đầu danh sách).
 Dịch chuyển con trỏ start sang phần
tử kế tiếp.
 Thu hồi ô nhớ ứng với con trỏ temp.
 Thực hiện cho đến khi start=NULL.

temp start

data link data link data link data link NULL

26 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University Tháng 09 năm 2009
15.2. Danh sách liên kết đơn (23/23)
15.2.2. Các thao tác cơ bản của danh sách liên kết đơn
Làm rỗng danh sách :
template <class ListEntry>
void LList<ListEntry>::LMakeEmpty()
{
node<ListEntry> *temp;
while(start!=NULL)
{
temp=start;
start=start->link;
free(temp);
}
}

27 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University Tháng 09 năm 2009
15.3. Danh sách liên kết vòng (1/20)
15.3.1. Khái niệm về danh sách liên kết vòng
 Được định nghĩa giống danh sách
liên kết đơn, tuy nhiên, phần tử
Data Add
cuối thay vì trỏ vào NULL thì trỏ Link
vào phần tử đầu tiên. Có 1 node
 Danh sách liên kết vòng được
dùng khi có nhiều thao tác với dữ
liệu.

60 800 45 90 55 1000

1000 800 90

28 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University Tháng 09 năm 2009
15.3. Danh sách liên kết vòng (2/20)
15.3.1. Khái niệm về danh sách liên kết vòng
 Để quản lý danh sách liên kết, thông thường cần:
• Start là con trỏ chỉ đến một phần tử của danh sách liên kết.
• Phần tử “cuối” của danh sách, liên kết với phần tử do start quản lý.
• Như vậy, từ bất kỳ vị trí nào trong danh sách cũng có thể duyệt hết danh sách.
 Lưu ý: khi thêm hay bớt phần tử nào trong danh sách cần đảm bảo rằng
sau thao tác đó vẫn giữ được tính liên kết vòng.

start

60 800 45 90 55 1000

1000 800 90

29 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University Tháng 09 năm 2009
15.3. Danh sách liên kết vòng (3/20)
15.3.2. Các thao tác cơ bản của danh sách liên kết vòng
template <class ListEntry>
class CLList {
public:
CLList();
int CLAdd(ListEntry entry);
int CLInsert(ListEntry value, ListEntry entry);
int CLAppend(ListEntry entry);
int CLFind(ListEntry value);
int CLDelete(ListEntry value);
int CLLength();
void CLMakeEmpty();
int CGet(int pos, ListEntry *value);
template<typename ListEntry> friend void LPrint(CLList<ListEntry> list);
private:
node<ListEntry> *start;
};
30 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University Tháng 09 năm 2009
15.3. Danh sách liên kết vòng (4/20)
15.3.2. Các thao tác cơ bản của danh sách liên kết vòng
Khởi tạo danh sách LKV:
template <class ListEntry>
CLList<ListEntry>::CLList() start
{
start = NULL;
NULL
}

31 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University Tháng 09 năm 2009
15.3. Danh sách liên kết vòng (5/20)
15.3.2. Các thao tác cơ bản của danh sách liên kết vòng
Thêm một node đầu danh sách LKV:
 Nếu danh sách rỗng, cấp phát ô nhớ và start 1
cho start trỏ vào ô nhớ đó, phần liên
kết trỏ vào start.
 Nếu danh sách không rỗng: data link
 Sử dụng biến temp để duyệt qua danh
sách, khi temp→link <> start.
 Cấp phát ô nhớ cho temp→link. 2
 Phần liên kết của ô nhớ mới trỏ vào
start.
temp->link
 Start trỏ vào vùng ô nhớ mới trên.
start
temp 10 link
30 link 20 link

32 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University Tháng 09 năm 2009
15.3. Danh sách liên kết vòng (6/20)
15.3.2. Các thao tác cơ bản của danh sách liên kết vòng
Thêm một node đầu danh sách LKV: else {
template < class ListEntry> temp=start;
int CLList<ListEntry>::CLAdd(ListEntry entry) while (temp->link!=start)
{ int kt=0; temp=temp->link;
node<ListEntry> *temp; temp->link=(node<ListEntry> *)
if (start==NULL) { malloc(sizeof(node<ListEntry>));
start=(node<ListEntry> *) if (temp->link==NULL) {
malloc(sizeof(node<ListEntry>)); printf("Loi cap phat bo nho!\n");
if (start==NULL) { return kt; }
printf("Loi cap phat bo nho!\n"); kt=1; temp=temp->link;
return kt; } temp->data=entry;
kt=1; temp->link=start;
start->data=entry; start=temp; }
start->link=start; return kt;
} }

33 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University Tháng 09 năm 2009
15.3. Danh sách liên kết vòng (7/20)
15.3.2. Các thao tác cơ bản của danh sách liên kết vòng
Chèn 1 node vào danh sách LKV:
 Nhập thông tin về node đứng trước node thêm mới.
 Sử dụng con trỏ temp để đến được node đứng trước đó.
 Cấp phát ô nhớ cho biến temp1.
 Phần liên kết của temp1 trỏ vào phần liên kết của con trỏ temp.
 Phần liên kết của con trỏ temp trỏ vào temp1.
temp1

start temp 25 link


2 1
10 link 20 link 30 link 40 link

34 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University Tháng 09 năm 2009
15.3. Danh sách liên kết vòng (8/20)
15.3.2. Các thao tác cơ bản của danh sách liên kết vòng
Chèn 1 node vào danh sách LKV : temp1=(node<ListEntry> *)
template <class ListEntry> malloc(sizeof(node<ListEntry>));
int CLList<ListEntry>::CLInsert(ListEntry value, if(temp1->link==NULL) {
ListEntry entry) {
printf("Loi cap phat bo nho!\n");
int kt=0;
return kt; }
node<ListEntry> *temp, *temp1;
kt=1;
if (start==NULL) printf("Danh sach rong!");
else { temp1->data=entry;
temp=start; temp1->link=temp->link;
while (temp->link!=start) { temp->link=temp1;
if (temp->data==value) break; }
else temp=temp->link;
}
}
return kt;
if (temp->data==value) {
}

35 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University Tháng 09 năm 2009
15.3. Danh sách liên kết vòng (9/20)
15.3.2. Các thao tác cơ bản của danh sách liên kết vòng
Thêm một node cuối danh sách LKV:
 Nếu danh sách rỗng, cấp phát ô nhớ và start 1
cho start trỏ vào ô nhớ đó, phần liên
kết trỏ vào start.
 Nếu danh sách không rỗng: data link
 Sử dụng biến temp để duyệt qua danh
sách, khi temp→link <> start.
2
 Cấp phát ô nhớ cho temp→link.
 Phần liên kết của ô nhớ mới trỏ vào
start. temp->link
start
temp 40 link
30 link 20 link
36 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University Tháng 09 năm 2009
15.3. Danh sách liên kết vòng (10/20)
15.3.2. Các thao tác cơ bản của danh sách liên kết vòng
Thêm một node cuối danh sách LKV: else {
template <class ListEntry> temp=start;
int CLList<ListEntry>::CLAppend(ListEntry entry) { while (temp->link!=start)
int kt=0; temp=temp->link;
node<ListEntry> *temp; temp->link=(node<ListEntry> *)
if (start==NULL) { malloc(sizeof(node<ListEntry>));
start=(node<ListEntry> *) if (temp->link==NULL) {
malloc(sizeof(node<ListEntry>)); printf("Loi cap phat bo nho!\n");
if (start==NULL) { return kt; }
printf("Loi cap phat bo nho!\n"); kt=1;
temp=temp->link;
return kt; }
temp->data=entry;
kt=1;
temp->link=start;
start->data=entry;
}
start->link=start;
return kt;
}
}
37 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University Tháng 09 năm 2009
15.3. Danh sách liên kết vòng (11/20)
15.3.2. Các thao tác cơ bản của danh sách liên kết vòng
Tìm một node trong danh sách LKV:
 Sử dụng con trỏ temp để duyệt qua
danh sách, khi temp→link <> start.
 Sử dụng thêm biến pos để lưu vị trí
của node trong danh sách. Nếu danh
sách rỗng hoặc không tìm thấy trả về
0, ngược lại trả về vị trí của node.
Tìm vị trí của phần tử
có giá trị 30!
start temp pos = 3

10 link 20 link 30 link 40 link

38 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University Tháng 09 năm 2009
15.3. Danh sách liên kết vòng (12/20)
15.3.2. Các thao tác cơ bản của danh sách liên kết vòng
Tìm một node trong danh sách LKV:
template <class ListEntry>
int CLList<ListEntry>::CLFind(ListEntry value) {
int pos=0;
node<ListEntry> *temp=start;
if (temp!=NULL) {
do {
pos++;
if (temp->data==value) break;
temp=temp->link;
} while (temp!=start);
if (temp->data!=value) pos=0;
}
return pos;
}

39 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University Tháng 09 năm 2009
15.3. Danh sách liên kết vòng (13/20)
15.3.2. Các thao tác cơ bản của danh sách liên kết vòng
Xóa một node trong danh sách LKV: prev
 Sử dụng 2 con trỏ prev và curr để tìm node 1
cần xóa. Con trỏ prev trỏ vào node trước start
node cần xóa. Con trỏ curr trỏ vào node cần
xóa.
 Nếu curr=curr->link, danh sách có 1 phần NULL data link NULL
tử, cho start trỏ NULL, giải phóng ô nhớ.
 Nếu curr <> start, cho prev->link trỏ vào
curr->link và giải phóng ô nhớ của curr.
 Nếu curr=start, thay đổi start, prev->link
trỏ curr->link và giải phóng ô nhớ của curr.
2
start prev

data link data link data link data link

40 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University Tháng 09 năm 2009
15.3. Danh sách liên kết vòng (14/20)
15.3.2. Các thao tác cơ bản của danh sách liên kết vòng
Xóa một node trong danh sách LKV: if(kt==1) {
template <class ListEntry> if (curr==curr->link) {
int CLList<ListEntry>::CLDelete(ListEntry value) { start=NULL; free(curr); }
node<ListEntry> *prev, *curr; else {
int kt=0; if (curr!=start) {
if (start==NULL) return kt; prev->link=curr->link; free(curr); }
else { else {
prev=NULL; prev=start; curr=start->link;
curr=start; while (curr!=start) {
while (!kt && curr->link!=start) { prev=curr;
if (curr->data==value) { curr=curr->link; }
kt=1; break; } prev->link=curr->link;
else { start=prev->link;
prev=curr; free(curr);
curr=curr->link; } }
} }
if (curr->data==value) }
kt=1; }
else return kt;
kt=2; }

41 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University Tháng 09 năm 2009
15.3. Danh sách liên kết vòng (15/20)
15.3.2. Các thao tác cơ bản của danh sách liên kết vòng
Lấy thông tin một node ở vị trí nào đó LKV:
 Sử dụng con trỏ temp để duyệt qua danh
sách.
 Duyệt cho đến khi đến node thứ pos, lấy
thông tin tại node đó và trả lại cho nơi gọi
hàm.

Cho trước pos = 3


start temp

10 link 20 link 30 link 40 link

42 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University Tháng 09 năm 2009
15.3. Danh sách liên kết vòng (16/20)
15.3.2. Các thao tác cơ bản của danh sách liên kết vòng
Lấy thông tin một node ở vị trí nào đó : if (i!=pos)
template <class ListEntry> return 0;
int CLList<ListEntry>::CGet(int pos, ListEntry *value) else
{ {
int i=0; *value=temp->data;
node<ListEntry> *temp=start; return 1;
if (temp!=NULL) { }
if (i<pos) { }
i++; else
temp=temp->link; } return 0;
while (temp!=start && i<pos) { }
temp=temp->link;
if (temp!=start)
i++;
}

43 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University Tháng 09 năm 2009
15.3. Danh sách liên kết vòng (17/20)
15.3.2. Các thao tác cơ bản của danh sách liên kết vòng
Xác định số phần tử trong danh sách:
 Sử dụng con trỏ temp để duyệt qua
danh sách, khi temp<> start.
 Sử dụng một biến length để đếm số
phần tử đã duyệt.

start temp length = 3

data link data link data link data link

44 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University Tháng 09 năm 2009
15.3. Danh sách liên kết vòng (18/20)
15.3.2. Các thao tác cơ bản của danh sách liên kết vòng
Xác định số phần tử trong danh sách LKV:
template <class ListEntry>
int CLList<ListEntry>::CLLength()
{ int length=0;
node<ListEntry> *temp=start;
if(temp!=NULL)
do
{
length++;
temp=temp->link;
}
while(temp!=start);
return length;
}
45 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University Tháng 09 năm 2009
15.3. Danh sách liên kết vòng (19/20)
15.3.2. Các thao tác cơ bản của danh sách liên kết vòng
Làm rỗng danh sách:
 Để làm rỗng danh sách, cách đơn giản
có thể sử dụng là xóa từng phần tử
bằng hàm Delete.
 Ngoài ra, có thể viết lại hàm xóa từng
phần tử.
 Lưu ý: tính vòng của danh sách.

temp start

data link data link data link data link

46 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University Tháng 09 năm 2009
15.3. Danh sách liên kết vòng (20/20)
15.3.2. Các thao tác cơ bản của danh sách liên kết vòng
Làm rỗng danh sách LKV:
template <class ListEntry>
void CLList<ListEntry>::CLMakeEmpty()
{
node<ListEntry> *temp;
while(start!=NULL)
{
temp=start;
start=start->link;
CLDelete(temp->data);
}
}

47 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University Tháng 09 năm 2009
15.4. Danh sách liên kết kép (1/22)
15.4.1. Khái niệm về danh sách liên kết kép
 Với các danh sách liên kết đơn, một số vấn đề xuất hiện:
 Với danh sách liên kết đơn, chỉ cho phép duyệt danh sách theo một
chiều.
 Để xóa một node cần lưu node đứng trước đó.
 Nếu một liên kết bị hỏng, các phần tử sau đó không dùng được.
 Để giải quyết vấn đề trên, có thể thêm cho mỗi phần tử một liên kết
nữa, liên kết này có chiều ngược lại. Khi thêm mỗi node một liên
kết như vậy, danh sách liên kết được gọi là có liên kết kép.

Add Data Add


left right
Có 1 node
48 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University Tháng 09 năm 2009
15.4. Danh sách liên kết kép (2/22)
15.4.1. Khái niệm về danh sách liên kết kép
 Để quản lý danh sách liên kết, thông thường cần:
• Start là con trỏ chỉ đến phần tử đầu tiên của danh sách liên kết kép.
• End là con trỏ chỉ đến phần tử cuối cùng của danh sách liên kết kép.
• Thông thường, phần tử cuối có liên kết right trỏ tới NULL.
• Bằng cách sử dụng hợp lý liên kết left hay right, có thể đi về trước hay sau.
 Lưu ý: khi thêm hay bớt phần tử nào trong danh sách cần đảm bảo rằng
sau thao tác đó vẫn giữ được tính liên kết vòng.
start end

NULL
0 60 800 1000 45 90 800 55 0
NULL
1000 800 90

49 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University Tháng 09 năm 2009
15.4. Danh sách liên kết kép (3/22)
15.4.1. Khái niệm về danh sách liên kết kép
 Trong thực tế, có thể tổ chức thành danh sách liên kết kép vòng (có header hoặc không có
header).
 Với trường hợp không có header, liên kết right của phần tử cuối trỏ vào phần tử đầu tiên
(do start quản lý), liên kết left của phần tử đầu tiên trỏ tới phần tử cuối cùng.
 Nếu có header, với việc thêm phần tử này, các thao tác cơ bản sẽ dễ xác định điều kiện
dừng.

start

90 60 800 1000 45 90 800 55 1000


1000 800 90

Ví dụ về danh sách liên kết kép vòng

50 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University Tháng 09 năm 2009
15.4. Danh sách liên kết kép (4/22)
15.4.1. Khái niệm về danh sách liên kết kép
Khai báo cấu trúc một Node của danh sách liên kết kép:
template <class ListEntry>
struct dnode
{
ListEntry data;
struct dnode *left, *right;
};

51 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University Tháng 09 năm 2009
15.4. Danh sách liên kết kép (5/22)
15.4.2. Các thao tác cơ bản của danh sách liên kết kép
template <class ListEntry>
class DLList {
public:
DLList();
int DLAdd(ListEntry entry);
int DLInsert(ListEntry value, ListEntry entry);
int DLAppend(ListEntry entry);
int DLFind(ListEntry value);
int DLDelete(ListEntry value);
int DLLength();
void DLMakeEmpty();
int DGet(int pos, ListEntry *value);
template<typename ListEntry> friend void LPrint(DLList<ListEntry> list);
private:
dnode<ListEntry> *start, *end;
};
52 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University Tháng 09 năm 2009
15.4. Danh sách liên kết kép (6/22)
15.4.2. Các thao tác cơ bản của danh sách liên kết kép
Khởi tạo danh sách LK kép:
template <class ListEntry>
DLList<ListEntry>::DLList() start end
{
start = NULL;
NULL
end = NULL;
}

53 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University Tháng 09 năm 2009
15.4. Danh sách liên kết kép (7/22)
15.4.2. Các thao tác cơ bản của danh sách liên kết kép
Thêm một node đầu danh sách LK kép:
 Nếu danh sách rỗng, cấp phát ô nhớ và start end 1
cho start và end trỏ vào ô nhớ đó.
 Nếu danh sách không rỗng:
 Cấp phát ô nhớ cho con trỏ temp. NULL left data right NULL
 Tạo các liên kết left và right tương ứng
(liên kết temp->left trỏ vào NULL, liên
kết temp->right trỏ vào start). 2
 Start trỏ vào vùng ô nhớ mới trên.

temp start end


4
NULL 2
0 60 800 1000 45 90 800 55 0
1 3 NULL
1000 800 90
54 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University Tháng 09 năm 2009
15.4. Danh sách liên kết kép (8/22)
15.4.2. Các thao tác cơ bản của danh sách liên kết kép
Thêm một node đầu danh sách LK kép: else {
template <class ListEntry> temp=(dnode<ListEntry> *)
int DLList<ListEntry>::DLAdd(ListEntry entry) { malloc(sizeof(dnode<ListEntry>));
int kt=0; if (temp==NULL) {
dnode<ListEntry> *temp; printf("Loi cap phat bo nho!\n");
if (start==NULL) { return kt; }
start=(dnode<ListEntry> *) kt=1;
malloc(sizeof(dnode<ListEntry>)); temp->data=entry;
if (start==NULL) { temp->left=NULL;
printf("Loi cap phat bo nho!\n"); temp->right=start;
return kt; } start->left=temp;
kt=1; start->data=entry; start=temp;
start->left=start->right=NULL; }
end=start; return kt;
} }

55 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University Tháng 09 năm 2009
15.4. Danh sách liên kết kép (9/22)
15.4.2. Các thao tác cơ bản của danh sách liên kết kép
Chèn 1 node vào danh sách LK kép:
 Nhập thông tin về node đứng trước node thêm mới.
 Sử dụng con trỏ curr để xác định node đứng trước node cần thêm.
 Cấp phát ô nhớ cho biến temp.
 Tạo các liên kết left và right của temp thông qua curr và curr->right.
 Lưu ý: có thể không xác định được node đứng trước, khi đó không thực hiện gì cả.
temp

curr curr->right
4 1000 45 90 1
2 3
? 60 800 800 55 ?
1000 800 90
56 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University Tháng 09 năm 2009
15.4. Danh sách liên kết kép (10/22)
15.4.2. Các thao tác cơ bản của danh sách liên kết kép
Chèn 1 node vào danh sách LK kép : temp->data=entry;
template <class ListEntry> temp->right=NULL;
int DLList<ListEntry>::DLInsert(ListEntry value, temp->left=end;
ListEntry entry) { end->right=temp;
int kt=0; end=temp;
dnode<ListEntry> *temp, *curr; kt=1; }
if (start==NULL) printf("Danh sach rong!"); }
else { else {
curr=start; temp=(dnode<ListEntry> *)
while(curr!=NULL) { malloc(sizeof(dnode<ListEntry>));
if (curr->data==value) break; if (temp==NULL) {
else curr=curr->right; } printf("Loi cap phat bo nho!\n");
if (curr==NULL) { return kt; }
if (end->data==value) { temp->data=entry;
temp=(dnode<ListEntry> *) temp->right=curr->right;
malloc(sizeof(dnode<ListEntry>)); temp->left=curr;
if (temp==NULL) { temp->right->left=temp;
printf("Loi cap phat bo nho!\n"); temp->left->right=temp;
return kt; } kt=1; }
}
return kt;
57
}
@copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University Tháng 09 năm 2009
15.4. Danh sách liên kết kép (11/22)
15.4.2. Các thao tác cơ bản của danh sách liên kết kép
Thêm một node cuối danh sách LK kép:
 Nếu danh sách rỗng, cấp phát ô nhớ và cho
start end 1
start và end trỏ vào ô nhớ đó.
 Nếu danh sách không rỗng:
 Cấp phát ô nhớ cho con trỏ temp.
 Tạo các liên kết left và right tương ứng (liên kết
NULL left data right NUL
temp->right trỏ vào NULL, liên kết temp->left L
trỏ vào end).
 End->right trỏ vào temp. 2
 End trỏ vào vùng ô nhớ mới trên.

end temp
4
3 1
0 60 800 1000 45 90 800 55 0
2 NULL
1000 800 90
58 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University Tháng 09 năm 2009
15.4. Danh sách liên kết kép (12/22)
15.4.2. Các thao tác cơ bản của danh sách liên kết kép
Thêm một node cuối danh sách LK kép: else
template <class ListEntry> { temp=(dnode<ListEntry> *)
int DLList<ListEntry>::DLAppend(ListEntry entry){ malloc(sizeof(dnode<ListEntry>));
int kt=0; if (temp==NULL)
dnode<ListEntry> *temp; {
if (start==NULL) { printf("Loi cap phat bo nho!\n");
start=(dnode<ListEntry> *) return kt;
malloc(sizeof(dnode<ListEntry>)); }
if (start==NULL) { kt=1;
printf("Loi cap phat bo nho!\n"); temp->data=entry;
return kt; } temp->right=NULL;
kt=1; temp->left=end;
start->data=entry; end->right=temp;
start->left=start->right=NULL; end=temp;
end=start; }
} return kt;
}
59 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University Tháng 09 năm 2009
15.4. Danh sách liên kết kép (13/22)
15.4.2. Các thao tác cơ bản của danh sách liên kết kép
Tìm một node trong danh sách LK kép:
 Sử dụng con trỏ temp để duyệt qua
danh sách, khi temp<> NULL.
 Sử dụng thêm biến pos để lưu vị trí
của node trong danh sách. Nếu danh
sách rỗng hoặc không tìm thấy trả về
0, ngược lại trả về vị trí của node. Tìm vị trí của phần tử
có giá trị 45!
start temp pos=2 end

NULL 0 60 800 1000 45 90 800 55 0


NULL
1000 800 90
60 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University Tháng 09 năm 2009
15.4. Danh sách liên kết kép (14/22)
15.4.2. Các thao tác cơ bản của danh sách liên kết kép
Tìm một node trong danh sách LK kép: temp=temp->right;
template <class ListEntry> }
int DLList<ListEntry>::DLFind(ListEntry value) if (found)
{ return pos;
int pos=0; else
int found=0; return 0;
dnode<ListEntry> *temp=start; }
while (temp!=NULL && !found)
{
pos++;
if (temp->data==value)
found=1;
else

61 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University Tháng 09 năm 2009
15.4. Danh sách liên kết kép (15/22)
15.4.2. Các thao tác cơ bản của danh sách liên kết kép
Xóa một node trong danh sách LK kép:
 Sử dụng con trỏ curr để xác định vị trí curr start 1
cần xóa.
 Nếu vị trí cần xóa là start:
 Chuyển con trỏ start sang phải.
NULL
0 60 800 0 45 ?
 Thay đổi liên kết left của start về NULL.
 Giải phóng ô nhớ do curr quản lý.
1000 800
 Nếu vị trí cần xóa không phải là start:
 Thay đổi các liên kết dựa vào left và right.
2
 Giải phóng ô nhớ do curr quản lý.
curr
1

0 60 800 1000 45 90 800 55 0

1000 2 800 90
62 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University Tháng 09 năm 2009
15.4. Danh sách liên kết kép (16/22)
15.4.2. Các thao tác cơ bản của danh sách liên kết kép
Xóa một node trong danh sách LK kép: if (kt!=1 && curr==NULL)
template <class ListEntry> kt=2;
int DLList<ListEntry>::DLDelete(ListEntry value) if (kt==1)
{ {
dnode<ListEntry> *curr; if (curr==start)
int kt=0; {
if (start==NULL) start=start->right;
return kt; start->left=NULL;
else { free(curr);
curr=start; }
while (!kt && curr!=NULL) else
{ {
if (curr->data==value) curr->left->right=curr->right;
{ curr->right->left=curr->left;
kt=1; free(curr);
break; }
} }
else return kt;
curr=curr->right; }
} }

63 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University Tháng 09 năm 2009
15.4. Danh sách liên kết kép (17/22)
15.4.2. Các thao tác cơ bản của danh sách liên kết kép
Lấy thông tin một node ở vị trí nào đó LK kép:
 Sử dụng con trỏ temp để duyệt qua danh
sách.
 Duyệt cho đến khi đến node thứ pos, lấy
thông tin tại node đó và trả lại cho nơi gọi
hàm.

Cho trước
start temp pos=2 end

NULL 0 60 800 1000 45 90 800 55 0


NULL
1000 800 90
64 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University Tháng 09 năm 2009
15.4. Danh sách liên kết kép (18/22)
15.4.2. Các thao tác cơ bản của danh sách liên kết kép
Lấy thông tin một node ở vị trí nào đó: if(i!=pos)
template <class ListEntry> return 0;
int DLList<ListEntry>::DGet(int pos, ListEntry
else
*value)
{ {
int i=0; *value=temp->data;
dnode<ListEntry> *temp=start; return 1;
while(temp!=NULL && i<pos)
}
{
}
temp=temp->right;
if(temp!=NULL)
i++;
}

65 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University Tháng 09 năm 2009
15.4. Danh sách liên kết kép (19/22)
15.4.2. Các thao tác cơ bản của danh sách liên kết kép
Xác định số phần tử trong danh sách LK kép:
 Sử dụng con trỏ temp để duyệt qua danh sách,
khi temp<> start.
 Sử dụng một biến length để đếm số phần tử đã
duyệt.

length = 2
start temp end

NULL 0 60 800 1000 45 90 800 55 0


NULL
1000 800 90
66 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University Tháng 09 năm 2009
15.4. Danh sách liên kết kép (20/22)
15.4.2. Các thao tác cơ bản của danh sách liên kết kép
Xác định số phần tử trong danh sách LK kép:
template <class ListEntry>
int DLList<ListEntry>::DLLength()
{
int length=0;
dnode<ListEntry> *temp=start;
if (temp!=NULL)
{
while (temp!=NULL)
{
length++;
temp=temp->right;
}
}
return length; }

67 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University Tháng 09 năm 2009
15.4. Danh sách liên kết kép (21/22)
15.4.2. Các thao tác cơ bản của danh sách liên kết kép
Làm rỗng danh sách liên kết kép:
 Để làm rỗng danh sách, cách đơn giản
có thể sử dụng là xóa từng phần tử
bằng hàm Delete.
 Ngoài ra, có thể viết lại hàm xóa từng
phần tử.

start end

NULL 0 60 800 1000 45 90 800 55 0


NULL
1000 800 90
68 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University Tháng 09 năm 2009
15.4. Danh sách liên kết kép (22/22)
15.4.2. Các thao tác cơ bản của danh sách liên kết kép
Làm rỗng danh sách LK kép:
template <class ListEntry>
void DLList<ListEntry>::DLMakeEmpty()
{
dnode<ListEntry> *temp;
while (start!=NULL)
{
temp=start;
start=start->right;
DLDelete(temp->data);
}
}

69 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University Tháng 09 năm 2009
15.4. Một số dạng và ví dụ sử dụng DSLK (1/)
Trong thực tế, có thể sử dụng DSLK cho nhiều ứng dụng như:
 Xây dựng Stack, Queue sử dụng danh sách liên kết.

 Biểu diễn cây sử dụng danh sách liên kết.

 Biểu diễn mảng, ma trận thưa bằng danh sách liên kết.

 Biểu diễn đồ thị bằng danh sách liên kết.

 Bài toán đa thức sử dụng danh sách liên kết.

 Bài toán số lớn sử dụng danh sách liên kết.

 ...

70 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University Tháng 09 năm 2009
Cấu trúc dữ liệu và giải thuật
Bài 17. Cấu trúc dữ liệu dạng cây

Giảng viên: TS. Ngo Huu Phuc


Tel: 0438 326 077
Mob: 098 5696 580
Email: ngohuuphuc76@gmail.com

1 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


Lecture 17. Trees (1/2)
Nội dung bài học:
17.1. Khái niệm về cây.
17.2. Các phương pháp duyệt cây.

Tham khảo:
1. Deshpande Kakde: C and Data structures.chm, Chapter 21: Trees
2. Elliz Horowitz – Fundamentals of Data Structures.chm, Chapter 5: Trees.
3. Kyle Loudon: Mastering Algorithms with C.chm, Chapter 9. Trees.
4. Bài giảng TS Nguyễn Nam Hồng

2 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


17.1. Khái niệm về cây (1/)
17.1.1. Giới thiệu.
 Trees được dùng cho cấu trúc dữ liệu dạng phân cấp.
 Ví dụ:
 Việc phân cấp cấu trúc dữ liệu được dùng cho minh họa lược đồ công việc.
 Tổ chức của một đơn vị.
 Cây biểu thức.

Khoa Công nghệ thông tin

BM KHMT BM HTTT BM ANM BM CNPM BM Toán TTMT

Phòng TN Giáo viên 1 Giáo viên 2


Ví dụ về cây: Tổ chức Khoa CNTT
3 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University
17.1. Khái niệm về cây (2/)
17.1.2. Định nghĩa về tree.

Cây được định nghĩa đệ quy như sau:


Một cây được định nghĩa bởi một tập các node T có dạng:
 Có một node đặc biệt gọi là root.

 Các node còn lại được phân chia rời nhau thành n tập dạng T1,

T2,…,Tn, trong đó Ti cũng là một cây.

4 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


17.1. Khái niệm về cây (3/)
A

B C D

G H I E F

 Hình trên minh họa 1 cây.


 Tập hợp các node {A, B, C, D, G, H, I, E, F}.
 A là root.
 Các node còn lại được chia thành các tập {B, G, H, I}, {C, E, F} và
{D}. Mỗi tập trên lại tạo thành 1 cây.

5 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


17.1. Khái niệm về cây (4/)
A

B C D

G H I E F

 Minh họa trên không phải là một cây.


 Mặc dù:
 Tập hợp các node vẫn là {A, B, C, D, G, H, I, E, F}.
 A là root.
 Node E thuộc 2 tập hợp.

6 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


17.1. Khái niệm về cây (5/)
• Bậc của một node: là số node con của node đó.
• Bậc của một cây: là bậc lớn nhất của các node trên cây.
• Node gốc: là node không có node cha.
• Node lá: là node có bậc bằng 0.
• Node nhánh: là node có bậc khác 0 và không phải là node gốc.
• Mức của một node:
 Gọi mức của node root là 1 (cây T0).
 Gọi T1, T2, T3, ... , Tn là các cây con của T0
 Mức của T1 = Mức của T2 = ... = Mức của Tn = Mức của T0 + 1=2
• Chiều cao của cây hay độ sâu của cây: là mức cao lớn nhất
của node trên cây.

7 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


17.1. Khái niệm về cây (6/)
 Gốc.

 Cạnh (cung).
Gốc (root)
 Node. node
Cạnh (edge, arc)
 Lá. A

B C D

G H I E F

Lá (leaf)

8 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


17.1. Khái niệm về cây (7/)
Một số ví dụ sử dụng cây:
 Cây phả hệ.

 Cây quyết định.

 Sử dụng cây để tạo queue có độ ưu tiên.

 Tổ chức truy cập dữ liệu nhanh, ví dụ như B-tree.

9 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


17.1. Khái niệm về cây (8/)
Xây dựng cây:
 Có thể xây dựng cây như danh sách liên kết, tuy nhiên
mỗi thành phần có nhiều con trỏ (nhiều con).
• Mỗi node chứa thông tin về node.
• Sử dụng mảng để lưu các con.
Ví dụ về khai báo cây:
struct node
{
TreeEntry data;
struct node *children[max];
};
10 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University
17.2. Các phương pháp duyệt cây (1/2)
 Việc thăm tất cả các node trên cây 1 lần được gọi là duyệt cây.

 Với một cây có n node, như vậy có n! cách duyệt cây khác

nhau. Tuy nhiên, đa số các phép duyệt cây đó không hữu ích.

 Đối với cây tổng quát, có 2 cách duyệt cây thông thường:

 Phương pháp duyệt cây theo chiều rộng (Breadth-first traversal)

 Phương pháp duyệt cây theo chiều sâu (Depth-first traversal).

 Với một cây có n node, độ phức tạp sẽ là O(n).

11 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


17.2. Các phương pháp duyệt cây (2/2)
Một số thao tác khi duyệt cây:
 Xem tất cả các node trên cây.

 Tìm phần tử lớn nhất hay nhỏ nhất trên cây.

 Xác định số node có trên cây.

 Sao chép cây.

 ...

12 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


17.2.1. Duyệt cây theo chiều sâu (1/7)
 Các thao tác chính khi duyệt cây:

 N: Duyệt node đang xét.

 L: Duyệt cây con bên trái của node đang xét.

 R: Duyệt các cây con còn lại của node đang xét.

 Với các thao trên, có 3 cách cơ bản:

 Duyệt tiền thứ tự (Preorder): NLR

 Duyệt trung thứ tự (Inorder): LNR

 Duyệt hậu thứ tự (Postorder): LRN


13 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University
17.2.1. Duyệt cây theo chiều sâu (2/7)
Duyệt tiền thứ tự (Preorder): NLR

1. Thăm node đang xét trước các node con của nó.

2. Các node con được thăm theo thứ tự từ trái qua phải.

3. Với mỗi node con, việc thăm được thực hiện theo dạng tiền
thứ tự.

14 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


17.2.1. Duyệt cây theo chiều sâu (3/7)
Duyệt tiền thứ tự (Preorder): NLR
Preorder(node)
1. Thăm node.
2. Với mỗi con k của
node: Preorder(k)
1
A
2
B 6 C 9 D

3 4 5 7 8
G H I E F

Thứ tự đã duyệt: A B G H I C E F D
15 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University
17.2.1. Duyệt cây theo chiều sâu (4/7)
Duyệt trung thứ tự (Inorder): LNR
1. Thăm con thứ nhất của node đang xét dạng trung thứ tự.

2. Thăm node đang xét.

3. Thăm các con còn lại của node đang xét dạng trung thứ tự.

16 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


17.2.1. Duyệt cây theo chiều sâu (5/7)
Duyệt trung thứ tự (Inorder): LNR
Inorder(node)
1. Inorder(FirstChildren).
2. Thăm node.
3. Với mỗi con còn lại k của
node: Inorder(k)
5
A
2
B 7 C 9 D

1 3 4 6 8
G H I E F

Thứ tự đã duyệt: G B H I A E C F D
17 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University
17.2.1. Duyệt cây theo chiều sâu (6/7)
Duyệt hậu thứ tự (Postorder): LRN
1. Thăm con thứ nhất của node đang xét dạng hậu thứ tự.

2. Thăm các con còn lại của node đang xét dạng hậu thứ tự.

3. Thăm node đang xét.

18 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


17.2.1. Duyệt cây theo chiều sâu (7/7)
Duyệt hậu thứ tự (Postorder): LRN
Postorder(node)
1. Postorder(FirstChildren).
2. Với mỗi con còn lại k của
node: Postorder(k)
3. Thăm node.
9
A
4
B 7 C 8 D

1 2 3 5 6
G H I E F

Thứ tự đã duyệt: G H I B E F C D A
19 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University
17.2.2. Duyệt cây theo chiều rộng (1/2)
 Thăm các node bắt đầu từ mức thấp nhất cho đến các mức cao.

 Tại mỗi mức, thăm từ trái sang phải.

 Sử dụng queue hỗ trợ trong quá trình duyệt cây.

 Phương pháp này còn được gọi là Level-Order Traversal.

20 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


17.2.2. Duyệt cây theo chiều rộng (2/2)

Thứ tự đã duyệt: A B C D G H I E F

1
A
2
B 3 C 4 D

5 6 7 8 9
G H I E F

21 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


Cấu trúc dữ liệu và giải thuật
Bài 19. Cây nhị phân

Giảng viên: TS. Ngo Huu Phuc


Tel: 0438 326 077
Mob: 098 5696 580
Email: ngohuuphuc76@gmail.com

1 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


Bài 19: Cây nhị phân
Nội dung:
19.1. Khái niệm về cây nhị phân.
19.2. Biểu diễn cây nhị phân.
19.4. Duyệt cây nhị phân.

Tham khảo:
1. Deshpande Kakde: C and Data structures.chm, Chapter 21: Trees
2. Elliz Horowitz – Fundamentals of Data Structures.chm, Chapter 5: Trees
3. Kyle Loudon: Mastering Algorithms with C.chm, Chapter 9 Trees.
4. Bài giảng TS Nguyễn Nam Hồng.

2 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


19.1. Khái niệm về cây nhị phân (1/7)
19.1.1. Giới thiệu và định nghĩa:
 Cây nhị phân là trường hợp đặc biệt của cây, trong đó không
có node nào trên cây có bậc lớn hơn 2.
 Do đó cây nhị phân T có thể định nghĩa:
 Có một node đặc biệt trên cây gọi là root của cây.

 Các node khác trên cây được chia thành 2 tập T1 và T2, trong đó
chúng cũng là cây nhị phân.
 Cây con T1 được gọi là cây con bên trái.

 Cây con T2 được gọi là cây con bên phải.

3 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


19.1. Khái niệm về cây nhị phân (2/7)
Từ định nghĩa cây nhị phân ta
nhận thấy rằng: A

 Số node tối đa trên cây nhị B C


phân tại mức i là 2i−1
 Nếu k là độ sâu của cây thì số D E F G
phần tử tối đa trên cây là:
2k − 1 = 2k−1 + 2k−2 + … + 20 H I

Ví dụ về cây nhị phân

4 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


19.1. Khái niệm về cây nhị phân (3/7)
Cây lệch:
Trong thực tế, có dạng đặc biệt: cây lệch. Cây lệch là cây chỉ có
cây con trái hoặc phải.

A A

B B

C C

Cây lệch trái Cây lệch phải

5 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


19.1. Khái niệm về cây nhị phân (4/7)
Cây nhị phân đầy đủ (Full binary tree):
 Cây nhị phân đầy đủ có độ cao là k thì các node ở mức thấp hơn có đủ con trái
và phải.
 Như vậy, với cây nhị phân đầy đủ có độ cao là k thì số node trên cây là 2k-1.

Ví dụ: Với k=3, số node trên cây A


nhị phân đầy đủ là 23-1=7
B E

C D F G

Ví dụ về cây nhị
phân đầy đủ có độ
cao là 3
6 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University
19.1. Khái niệm về cây nhị phân (5/7)
Cây nhị phân hoàn chỉnh (Complete binary tree):
 Cây nhị phân hoàn chỉnh có độ cao là k, với độ cao k-1 là đầy đủ.
 Tại độ cao là k, các node được đưa vào cây từ trái sang phải.
 Nhận xét:
 Các node tại mức k-2 về trước có đủ 2 con.
 Các node ở mức k-1 có thể có 2 con hoặc 1 con. Nếu có 1 con trì có con trái
(các node cùng mức trước đó đã có 2 con)

A A

B E B E

C D C D F
Ví dụ về cây nhị phân hoàn chỉnh

7 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


19.1. Khái niệm về cây nhị phân (6/7)
So sánh giữa cây nhị phân đầy đủ và hoàn chỉnh:
 Cây nhị phân đầy đủ là trường hợp riêng của cây nhị phân hoàn chỉnh.
 Cây nhị phân hoàn chỉnh chưa chắc đã là cây nhị phân đầy đủ.

A A

B E B E

C D F G C D F

Cây nhị phân đầy đủ Cây nhị phân hoàn chỉnh

8 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


19.1. Khái niệm về cây nhị phân (7/7)
Cây nhị phân cân bằng (Balanced binary tree):
 Cây nhị phân cân bằng là cây mà tại mỗi node độ cao cây con trái và phải
không lệch nhau quá 1.

A A

B E B E

C D C D F

Ví dụ về cây nhị phân cân bằng

9 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


19.2. Biểu diễn cây nhị phân (1/6)
 Với cây nhị phân hoàn chỉnh có thể biểu diễn cây bằng mảng có n phần tử.

 Nếu cây nhị phân hoàn chỉnh được biểu diễn dưới dạng mảng, giá trị của
phần tử thứ i sẽ được chứa trong mảng tại vị trí thứ i (1<=i<=n).
 Khi đó, phần tử “cha” của i sẽ là i/2 và 2 con của node i sẽ là 2i và 2i+1.

1 A A
2 B
3 C B C
4 D D E
5 E

Biểu diễn cây nhị phân hoàn chỉnh bằng mảng

10 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


19.2. Biểu diễn cây nhị phân (2/6)
 Với cây nhị phân bất kỳ cũng có thể biểu diễn dạng mảng.
 Tuy nhiên, cách biểu diễn trên gây lãng phí bộ nhớ.

1 A A
2 B
3 C B C
4 D
5 E
6 F D E F G
7 G
8 H I
9
10 H
11 I Ô nhớ lãng phí

11 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


19.2. Biểu diễn cây nhị phân (3/6)
 Với cách biểu diễn trên có ưu điểm là có khả năng truy cập dữ
liệu nhanh. Tuy nhiên, nhược điểm chính là lãng phí ô nhớ.
 Có thể biểu diễn cây dạng mảng, trong đó, mỗi node sẽ có cấu
trúc:
 Dữ liệu của node.
 Chỉ số của node con trái.
 Chỉ số của node con phải.

Left index Data Right index

12 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


19.2. Biểu diễn cây nhị phân (4/6)
 Ví dụ về biểu diễn cây nhị phân.

Left index Data Right index


2 1. A 3 A
4 2. B 5
6 3. C 7 B C
0 4. D 0
8 5. E 9 D E F G
0 6. F 0
0 7. G 0 H I
0 8. H 0
0 9. I 0

13 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


19.2. Biểu diễn cây nhị phân (5/6)
 Việc biểu diễn cây dạng mảng không phù hợp với việc thường
xuyên thêm bớt trên cây. Chi phí cho việc thêm bớt quá lớn. Do đó,
ta có thể biểu diễn cây dạng liên kết. Trong trường hợp này, mỗi
phần tử sẽ có 3 thành phần:
 Thành phần dữ liệu,
 Con trỏ liên kết trái,
 Con trỏ liên kết phải

Left ptr Data Right ptr


Biểu diễn trên ngôn ngữ C:
struct tnode
{
TreeEntry data;
struct tnode *lchild,*rchild;
};
14 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University
19.2. Biểu diễn cây nhị phân (6/6)
A

B C

D NULL NULL

NULL NULL

Ví dụ về biểu diễn cây dạng liên kết

15 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


19.3. Duyệt cây nhị phân (1/9)
 Đối với cây nói chung, cây nhị phân nói riêng, dữ liệu
được lấy ra phụ thuộc vào cách duyệt cây.

 Thông thường, có 4 cách duyệt cây sau:


 Duyệt tiền thứ tự (PreOrder): NLR.
 Duyệt hậu thứ tự (PostOrder): LRN.
 Duyệt trung thứ tự (InOrder): LNR
 Duyệt theo chiều rộng (LevelOrder).

16 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


19.3. Duyệt cây nhị phân (2/9)
Duyệt tiền thứ tự (Preorder): NLR

1. Thăm node đang xét trước các node con của nó.

2. Thăm cây con bên trái.

3. Thăm cây con bên phải.

17 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


19.3. Duyệt cây nhị phân (3/9)
Duyệt tiền thứ tự (Preorder): NLR
void NLR(Tree root)
1
{ A
if (root != NULL)
{ 2 7
B C
xử lý root;
NLR (root->left);
3 4 8 9
NLR (root->right); D E F G
}
}
5 6
H I

Thứ tự đã duyệt: A B D E H I C F G
18 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University
19.3. Duyệt cây nhị phân (4/9)
Duyệt trung thứ tự (Inorder): LNR
1. Thăm cây con bên trái của node đang xét trước.

2. Thăm node đang xét.

3. Thăm cây con bên phải của node đang xét.

19 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


19.3. Duyệt cây nhị phân (5/9)
Duyệt trung thứ tự (Inorder): LNR
void LNR(Tree root)
6
{ A
if (root != NULL)
2 8
{ B C
LNR(root->left);
1 4 7 9
xử lý root; D E F G
LNR(root->right);
} 3 5
H I
}

Thứ tự đã duyệt: D B H E I A F C G
20 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University
19.3. Duyệt cây nhị phân (6/9)
Duyệt hậu thứ tự (Postorder): LRN
1. Thăm cây con bên trái của node đang xét trước.

2. Thăm cây con bên phải của node đang xét trước.

3. Thăm node đang xét.

21 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


19.3. Duyệt cây nhị phân (7/9)
Duyệt hậu thứ tự (Postorder): LRN
void LRN(Tree root) 9
A
{
if (root != NULL) 5 8
B C
{
LRN (root->left); 1 4 6 7
LRN (root->right); D E F G
xử lý root;
2 3
} H I
}

Thứ tự đã duyệt: D H I E B F G C A
22 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University
19.3. Duyệt cây nhị phân (8/9)
 Thăm các node bắt đầu từ mức thấp nhất cho đến các mức cao.

 Tại mỗi mức, thăm từ con trái trước, sau đó thăm con phải.

 Sử dụng queue hỗ trợ trong quá trình duyệt cây.

 Phương pháp này còn được gọi là Level-Order Traversal.

23 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


19.3. Duyệt cây nhị phân (9/9)
1
A

2 3
B C

4 5 6 7
D E F G

8 9
H I

Thứ tự đã duyệt: A B C D G H I E F

24 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


Cấu trúc dữ liệu và giải thuật
Bài 21. Cây nhị phân tìm kiếm

Giảng viên: TS. Ngo Huu Phuc


Tel: 0438 326 077
Mob: 098 5696 580
Email: ngohuuphuc76@gmail.com

1 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


Bài 21: Cây nhị phân tìm kiếm
Nội dung:
21.1. Khái niệm cây nhị phân tìm kiếm.
21.2. Các thao tác trên cây nhị phân tìm kiếm.
21.3. Một vài ví dụ sử dụng cây nhị phân tìm kiếm.

Tham khảo:
1. Deshpande Kakde: C and Data structures.chm, Chapter 20: Linked Lists
2. Elliz Horowitz – Fundamentals of Data Structures.chm, Chapter 4: Linked Lists
3. Kyle Loudon: Mastering Algorithms with C.chm, Chapter 5 Linked Lists.
4. Bài giảng TS Nguyễn Nam Hồng

2 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


21.1. Khái niệm về cây nhị phân tìm kiếm (1/4)
Khái niệm:

 Cây nhị phân tìm kiếm là cây rỗng hoặc cây mà node có
chứa các khóa.

 Các khóa của node trên cây con bên trái nhỏ hơn khóa của
root, khóa của các node trên cây con bên phải lớn hơn khóa
của root.

 Cây con bên trái và phải cũng là cây nhị phân tìm kiếm.

 Việc quản lý cây nhị phân thông qua node root.

3 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


21.1. Khái niệm về cây NPTK (2/4)

50

30 60

25 40 70

35 65

Ví dụ về cây nhị phân tìm kiếm


4 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University
21.1. Khái niệm về cây NPTK (3/4)
Một số tính chất của cây NPTK:
 Cây nhị phân tìm kiếm là tập con của cây nhị phân, nên nó
cũng có các cách duyệt LNR, LRN, NLR.
 Với cây nhị phân tìm kiếm, nếu duyệt cây theo kiểu inorder ta
sẽ được một dãy đã sắp theo chiều tăng dần.
 Cây nhị phân tìm kiếm là cấu trúc tìm kiếm hiệu quả. Việc tìm
kiếm trên cây nhị phân tìm kiếm nhanh hơn tìm kiếm tuần tự.
Tuy nhiên, việc biểu diễn cây dạng mảng sẽ gây khó khăn cho
việc thêm, bớt...
 Với việc biểu diễn dạng liên kết, ta có độ phức tạp của việc tìm
kiếm là O( logn).
5 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University
21.1. Khái niệm về cây NPTK (4/4)
Cấu trúc cây nhị phân tìm kiếm:
template <class TreeEntry>
struct tbnode
{
TreeEntry data;
struct tbnode *lchild, *rchild;
};

6 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


21.2. Các thao tác trên cây NPTK (1/)
Một số thao tác trên cây nhị phân tìm kiếm:
1. Khởi tạo cây NPTK.
2. TBInsert: Thêm một node vào cây NPTK.
3. TBCount: đếm số node trên cây NPTK.
4. TBRInorder: duyệt cây dạng inorder.
5. TBRPostorder: duyệt cây dạng postorder.
6. TBRPreorder: duyệt cây dạng preorder.
7. TBLevelorder: duyệt cây dạng levelorder.
8. TBDelete: xóa node trên cây NPTK.
9. TBGetptr: định vị một node và node cha của nó.

7 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


21.2. Các thao tác trên cây NPTK (2/)
Khai báo lớp cây nhị phân tìm kiếm:
template <class TreeEntry> class TBTree {
public:
TBTree();
int TBInsert(TreeEntry value);
int TBCount();
void TBRInorder(tbnode<TreeEntry>* treenode, QueueClassC<TreeEntry> *q);
void TBRPostorder(tbnode<TreeEntry>* treenode, QueueClassC<TreeEntry> *q);
void TBRPreorder(tbnode<TreeEntry>* treenode, QueueClassC<TreeEntry> *q);
void TBInorder(QueueClassC<TreeEntry>* q);
void TBPostorder(QueueClassC<TreeEntry>* q);
void TBPreorder(QueueClassC<TreeEntry>* q);
void TBLevelorder(QueueClassC<TreeEntry>* q);
void TBGetptr(tbnode<TreeEntry> **father, tbnode<TreeEntry> **curr, TreeEntry value);
int TBDelete(TreeEntry value);
template<typename TreeEntry> friend void TreePrint(TBTree<TreeEntry> tree, int type);
private: tbnode<TreeEntry> * root; };
8 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University
21.2. Các thao tác trên cây NPTK (3/)
Khởi tạo cây NPTK:
template <class TreeEntry>
TBTree<TreeEntry>::TBTree()
{ root
root=NULL;
} NULL

9 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


21.2. Các thao tác trên cây NPTK (4/)
Thêm một node vào cây NPTK:
root 1
 Nếu cây rỗng, cấp phát ô nhớ cho con
trỏ root.
50
 Nếu cây không rỗng:
 Sử dụng 2 con trỏ temp và temp1 để
xác định vị trí cần thêm (con trỏ temp1 NULL NULL
sẽ trỏ vào NULL, con trỏ temp sẽ trỏ
vào node là cha của node mới).
 Tùy thuộc vào giá trị mới được thêm
2
vào sẽ cấp phát ô nhớ cho temp->left 50
hay temp->right.
30 temp 60
25 40
Thêm node 70
có giá trị 35 NULL 45 65
45 vào cây
NULL NULL
10 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University
21.2. Các thao tác trên cây NPTK (5/)
temp1=temp1->lchild;
Thêm một node vào cây NPTK else
temp1=temp1->rchild; }
if(temp->data>value) {
template <class TreeEntry> temp->lchild = (tbnode<TreeEntry> *)
int TBTree<TreeEntry>::TBInsert(TreeEntry value) malloc(sizeof(tbnode<TreeEntry>));
{ int kt=0; if(temp->lchild==NULL) {
tbnode<TreeEntry> * temp, *temp1; printf("Khong du bo nho");
if(root==NULL) { return kt; }
root = (tbnode<TreeEntry> *) kt=1;
temp=temp->lchild;
malloc(sizeof(tbnode<TreeEntry>)); temp->data=value;
if (root==NULL) { temp->lchild=temp->rchild=NULL;
printf("Khong du bo nho"); }
return kt; } else {
kt=1; temp->rchild = (tbnode<TreeEntry> *)
root->data=value; malloc(sizeof(tbnode<TreeEntry>));
root->lchild=root->rchild=NULL; if(temp->rchild==NULL) {
printf("Khong du bo nho");
} return kt; }
else kt=1;
{ temp1=root; temp=temp->rchild;
while(temp1!=NULL) { temp->data=value;
temp=temp1; temp->lchild=temp->rchild=NULL; }
if(temp1->data>value) }
return kt;
}
11 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University Tháng 09 năm 2009
21.2. Các thao tác trên cây NPTK (6/)
Xóa một node trên cây NPTK:

Xóa node không có con


12 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University
21.2. Các thao tác trên cây NPTK (7/)
Xóa một node trên cây NPTK:

Xóa node có 1 con


13 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University
21.2. Các thao tác trên cây NPTK (8/)
Xóa một node trên cây NPTK:

Xóa node có 2 con


14 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University
Cấu trúc dữ liệu và giải thuật
Bài 22: Hàm băm

Giảng viên: TS. Ngo Huu Phuc


Tel: 0438 326 077
Mob: 098 5696 580
Email: ngohuuphuc76@gmail.com

1 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


Bài 22: Hàm băm
Nội dung:
22.1. Bài toán.
22.2. Hàm băm.
22.3. Giải quyết xung đột.
22.4. Một số ví dụ sử dụng hàm băm.

2 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


22.1. Bài toán (1/9)
 Giả sử cần lưu trữ một số bản ghi và thực hiện các thao tác:

 Thêm: thêm một bản ghi

 Xóa: xóa một bản ghi

 Tìm kiếm: tìm kiếm một bản ghi

 Hãy đưa ra cách tổ chức để thực hiện hiệu quả các công việc

trên.

3 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


22.1. Bài toán (2/9) – Sử dụng mảng
 Sử dụng mảng không được sắp xếp

 Thêm: thêm vào cuối mảng->O(1)

 Xóa: mất nhiều thời gian tìm vị trí cần xóa và dồn mảng->O(n)

 Tìm kiếm: tìm kiếm tuần tự->O(n)

 Sử dụng mảng được sắp xếp

 Thêm: phải tìm vị trí thêm vào->O(n)

 Xóa: mất nhiều thời gian tìm vị trí cần xóa và dồn mảng->O(n)

 Tìm kiếm: tìm kiếm nhị phân->O(log(n))

4 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


22.1. Bài toán (3/9) – Sử dụng DSLK
 Thêm: thêm vào vị trí bất kỳ nhanh->O(1)

 Xóa: nhanh trong tổ chức các nút, nhưng chậm trong tìm kiếm

nút cần khóa->O(n)

 Tìm kiếm: tìm kiếm tuần tự->O(n)

5 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


22.1. Bài toán (4/9) – dùng như bảng
 Giả sử cần lưu trữ 1000 bản ghi về sinh viên và tìm kiếm
chúng theo ID

ID Họ và tên Điểm
0012345 Nguyễn Văn A 10
0033333 Nguyễn Văn B 9
0056789 Nguyễn Văn C 8
… … …
9801010 Nguyễn Thị A 7
9802020 Nguyễn Thị B 8
… … …
9903030 Trần Văn A 9
9908080 Trần Văn B 10

6 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


22.1. Bài toán (5/9) – dùng như bảng
 Dùng một mảng rất lớn để lưu trữ (index 0..9999999). Chỉ số của mảng bằng với
chỉ số id của sinh viên, i.e. ví dụ sinh viên với studid 0012345 thì được lưu trữ tại
A[12345]
Tên Điểm
… … …
12345 Nguyễn Văn A 10
… … …
33333 Nguyễn Văn B 9
… … …
56789 Nguyễn Văn C 8
… … …
9801010 Nguyễn Thị A 7
… … …
9802020 Nguyễn Thị B 8
… … …
9999999 … …

7 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


22.1. Bài toán (6/9) – dùng như bảng
Một số nhận xét:

 Đánh giá các thao tác

 Thêm: rất nhanh O(1)

 Xóa: rất nhanh O(1)

 Tìm kiếm: rất nhanh O(1)

 Nhưng quá tốn kém bộ nhớ->không hiệu quả

8 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


22.1. Bài toán (7/9) – dùng hàm băm

function Hash(key: KeyType): integer;

Giả sử có 1 hàm băm lý tưởng. Nó


H(‘0012345’) = 134
ánh xạ khóa (ID) của 1000 bản ghi
H(‘0033333’) = 67
vào các giá trị nguyên 0..999, hai H(‘0056789’) = 764

khóa khác nhau cho hai số nguyên
H(‘9908080’) = 3
khác nhau.

9 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


22.1. Bài toán (8/9) – dùng hàm băm

0 … …
• Để lưu trữ một bản ghi, … … …
tính Hash(ID) cho bản 3 Trần Văn B 10
… … …
ghi và lưu trữ nó tại vị trí
67 Nguyễn Văn B 9
Hash(ID) của mảng. … … …

•Để tìm kiếm một sinh 134 Nguyễn Văn A 10


… … …
viên, chỉ cần truy cập đến 764 Nguyễn Văn C 8
vị trí Hash(target ID). … … …
999 … …

10 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


22.1. Bài toán (9/9) – dùng hàm băm
 Với hàm băm lý tưởng

 Thêm: O(1)

 Xóa: O(1)

 Tìm kiếm: O(1)

 Nói chung là khó thiết kế được hàm băm lý tưởng

11 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


22.2. Hàm băm (1/6)
Khái niệm:

 Hàm băm là giải thuật nhằm sinh ra các giá trị băm tương ứng với

mỗi khối dữ liệu.

 Giá trị băm đóng vai gần như một khóa để phân biệt các khối dữ

liệu.

 Hàm băm thường được dùng trong bảng băm nhằm giảm chi phí

tính toán khi tìm một khối dữ liệu trong một tập hợp.

12 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


22.2. Hàm băm (2/6)
Yêu cầu đối với hàm băm:
 Tính toán nhanh.

 Các khóa được phân bố đều trong bảng.

 Ít xảy ra đụng độ.

 Xử lý được các loại khóa có kiểu khác nhau.

13 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


22.2. Hàm băm (3/6)
Một số lĩnh vực sử dụng hàm băm:
 Mật mã học

 Bảng băm

 Phát hiện và sửa lỗi dữ liệu

 Nhận dạng âm thanh

14 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


22.2. Hàm băm (4/6) – Một số hàm băm
 Hàm cắt bỏ:

 Cho khóa là số nguyên, bỏ bớt một phần nào đó của khóa.

 Ví dụ: khóa là một số nguyên có 6 chữ số x=842615. Ta có thể

quy ước là bỏ bớt chẳng hạn các chữ số hàng lẻ (1,3,5…), số còn
lại sẽ là 821. Vậy H(x) = H(842615) = 821.
 Nhận xét: Hàm cắt bỏ thỏa mãn tính chất thứ nhất của hàm băm

nhưng tính chất thứ hai là khó thực hiện (khó có phân bố đều).

15 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


22.2. Hàm băm (5/6) – Một số hàm băm
 Hàm phần dư:

 Khóa có giá trị nguyên và bảng băm B có m phần tử, ta lấy phần

dư của phép chia x/m làm giá trị hàm băm. Để đảm bảo tính chất
thứ hai của hàm băm nên chọn m là số nguyên tố.
 Nhận xét: Các cách lấy phần dư cho khả năng tránh hiện tượng

xung đột là tốt hơn cả.

16 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


22.2. Hàm băm (6/6) – Một số hàm băm
 Hàm gấp:

 Cho khóa là số nguyên, chia số nguyên đó thành một số đoạn tùy

chọn, sau đó kết hợp các phần đó lại theo một quy ước nào đó.
 Ví dụ: Số các hàng lẻ: 465 và số các hàng chẵn: 821, vậy

H(x)=465+821=1286.
 Nhận xét: Tính chất thứ nhất của hàm băm được thỏa mãn. Do

các chữ số của khóa đều có sử dụng, nên tính chất thứ hai có thể
thỏa mãn tốt hơn với trường hợp dùng hàm băm cắt bỏ.

17 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


22.3. Xung đột và giải quyết xung đột (1/4)
 Trong hầu hết các trường hợp đều không tránh được xung
đột

H(‘0012345’) = 134
H(‘0033333’) = 67
H(‘0056789’) = 764

H(‘9903030’) = 3
H(‘9908080’) = 3

• Xử lý thế nào khi hai khóa khác nhau lại ánh xạ đến một
địa chỉ?

18 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


22.3. Xung đột và giải quyết xung đột (2/4)
 Phương pháp dò tuyến tính: ý tưởng là dò tìm vị trí trống tiếp

theo rồi chèn phần tử bị đụng độ vào đó. Khi mảng đầy thì
resize lại mảng.

 Phương pháp dây chuyền: Thay vì cố gắng tìm trong danh

sách một vị trí còn trống kế tiếp, phương pháp dây chuyền liên
kết các danh sách có các khóa khác nhau nhưng có cùng giá trị
hàm băm thành một danh sách.

19 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


22.3. Xung đột và giải quyết xung đột (3/4)
 Phương pháp dò tuyến tính:
Insert: 89, 18, 49, 58, 9 to table size=10, hash function is: %tablesize

49 49 49
58 58
9

18 18 18 18
89 89 89 89 89

20 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


22.3. Xung đột và giải quyết xung đột (4/4)
 Phương pháp dây chuyền:
0
1 null
2 null
3
4 null
5
:

HASHMAX null ID: 9903030


Tên: Trần Văn A
Điểm: 9

21 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University


22.4. Một số ví dụ sử dụng hàm băm
 Xây dựng từ điển sử dụng hàm băm.

22 @copyright by PhD Ngo Huu Phuc, Le Quy Don Technical University

You might also like