Bai Giang CTDL Va GT 2008

You might also like

Download as ppt, pdf, or txt
Download as ppt, pdf, or txt
You are on page 1of 202

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

SỐ TIẾT: 60 (45 LÝ THUYẾT + 15 THỰC HÀNH)

1. Sách, giáo trình chính:


- Cấu trúc dữ liệu và giải thuật - Đỗ Xuân Lôi - NXB Khoa học kỹ thuật
- Bài giảng Cấu trúc dữ liệu và giải thuật - Đỗ Thị Mơ
2. Sách tham khảo:
- Cấu trúc dữ liệu + giải thuật = chương trình - Niklaus Wirth
- Thuật toán - Đỗ Xuân Huy
- Cấu trúc dữ liệu và giải thuật - Trung tâm Tin học Ngoại ngữ Trí Đức
- Nhà xuất bản Thống kê 2003
3. Giáo viên: Đỗ Thị Mơ - Bộ môn Công nghệ phần mềm
Email: dtmo@hau1.edu.vn
CẤU TRÚC DỮ LIỆU VÀ GIẢI THUẬT
Chương 1: Giải thuật
1. Giải thuật - Cấu trúc dữ liệu
1.1. Giải thuật
* Khái niệm giải thuật: Giải thuật là một hệ thống các thao tác, các phép toán
một cách rõ ràng chính xác, sao cho sau một số hữu hạn các bước thực
hiện ta đạt được kết quả mong muốn.
* Giải thuật phản ánh các phép xử lý, còn đối tượng xử lý là dữ liệu.
1.2. Cấu trúc dữ liệu
* Khái niêm dữ liệu: Dữ liệu là các phần tử biểu diễn các thông tin càn thiết cho
bài toán.
* Một bài toán có thể có các loại dữ liệu: Dữ liệu vào, dữ liệu trung gian, dữ liệu
ra.
- Dữ liệu vào là dữ liệu cần đưa vào ban đầu để xử lý, đây chính là đầu vào
của bài toán.
- Dữ liệu trung gian là dữ liệu chứa các kết quả trung gian trong quá trình xử
lý.
- Dữ liệu ra là dữ liệu chứa kết quả mong muốn của bài toán.
Như vậy giải thuật thực hiện biến đổi từ các dữ liệu vào thành các dữ liệu ra.
Ví dụ 1: Ta xét bài toán tính học bổng cho sinh viên theo chế độ hiện
hành. Các dữ liệu của bài toán bao gồm:
Dữ liệu vào: Họ và tên, Điểm các môn, Số trình các môn học
Dữ liệu trung gian: Điểm trung bình
Dữ liệu ra: Học bổng
Ví dụ 2: Xét bài toán giải phương trình bậc hai ax2 + bx + c = 0 . Các dữ
liệu của bài toán này như sau:
Dữ liệu vào: a, b, c
Dữ liệu trung gian: delta
Dữ liệu ra: x1, x2
* Dữ liệu nguyên tử là phần tử dữ liệu cơ sở không thể tách nhỏ ra được,
có thể là một chữ số, một kí tự, v.v... Trong một bài toán dữ liệu bao
gồm một tập các dữ liệu nguyên tử.
* Trên cơ sở các dữ liệu nguyên tử, các cách thức liên kết chúng lại với
nhau thành các cấu trúc dữ liệu. Chẳng hạn liên kết các kí tự lại với
nhau tạo thành cấu trúc dữ liệu kiểu xâu kí tự ; liên kết các số lại với
nhau theo kiểu một dãy số ta được cấu trúc dữ liệu kiểu mảng một
chiều, liên kết các số lại với nhau theo kiểu bảng có dòng và cột ta
được cấu trúc dữ liệu kiểu mảng hai chiều
* Trước đây có các cấu trúc dữ liệu đơn giản như xâu kí tự, mảng, bản ghi, ...
. Nay do khối lượng dữ liệu lớn, đa dạng nên có các cấu trúc dữ liệu phức
tạp hơn như cây, đồ thị, ...
* Lựa chọn một cấu trúc dữ liệu thích hợp, trên đó xây dựng một giải thuật xử
lý hữu hiệu là một việc làm quan trọng trong lập trình. Chọn một cấu trúc dữ
liệu thì phải xét tới các phép toán tác động trên cấu trúc dữ liệu ấy, ngược
lại xét đến phép toán phải chú ý phép toán đó tác động trên cấu trúc dữ liệu
nào.
* Cấu trúc lưu trữ : Cách biểu diễn một cấu trúc dữ liệu trong bộ nhớ được gọi
là cấu trúc lưu trữ, đó chính là cách cài đặt cấu trúc dữ liệu trên máy vi tính.
- Có thể có nhiều cấu trúc lưu trữ khác nhau cho một cấu trúc dữ liệu. Chẳng
hạn một cấu trúc dữ liệu kiểu mảng ta có thể lưu trữ bằng các ô nhớ kế tiếp
nhau trong bộ nhớ hoặc có thể lưu trữ bằng các ô nhớ không kế tiếp nhau
trong bộ nhớ.
- Có thể có nhiều cấu trúc dữ liệu khác nhau được cài đặt trong bộ nhớ bằng
một cấu trúc lưu trữ. Chẳng hạn cấu trúc xâu kí tự, cấu trúc mảng đều có
thể cài đặt trong bộ bằng các ô kế tiếp nhau.
* Mỗi một ngôn ngữ lập trình đều có các cấu trúc dữ liệu tiền định ( định sẵn),
nên chọn ngôn ngữ lập trình nào là ta phải chấp nhận cấu trúc dữ liệu tiền
định của nó, phải vận dụng linh hoạt vào bài toán cần giải.
1.3. Mối quan hệ giữa giải thuật và cấu trúc dữ liệu
* Xét tới giải thuật thì phải xét giải thuật đó tác động trên cấu trúc dữ liệu
nào.
* Xét tới cấu trúc dữ liệu thì phải hiểu cấu trúc dữ liệu đó cần được tác
động bằng giải thuật gì để được kết quả mong muốn.
* Lựa chọn một cấu trúc dữ liệu thích hợp, trên đó xây dựng một giải
thuật xử lý hiệu quả là hai khâu quan trọng nhất trong lập trình. Do đó
tác giả Niklaus Wirth có công thức sau :
Cấu trúc dữ liệu + Giải thuật = Chương trình
* Với một cấu trúc dữ liệu ta sẽ chọn một giải thuật tương ứng. Cấu trúc
dữ liệu thay đổi giải thuật cũng thay đổi theo.
Ví dụ : Giả sử có một danh sách gồm những cặp ‘ Tên, Số điện thoại ‘ :
(a1,b1), (a2, b2), . . . , (an, bn ) . Viết chương trình trên máy vi tính để
khi biết tên in ra số điện thọại. Đây là bài toán tìm kiếm.
- Giải thuật một cách đơn giản là duyệt lần lượt các tên trong danh sách
a1, a2, ..., an . Tìm thấy tên ai, đối chiếu ra số điện thoại bi tơng ứng.
- Nếu các cặp đó được sắp theo thứ tự tên thì sẽ áp dụng thuật toán
ngắn hơn như ta vãn tra từ điển, còn gọi là tím kiếm nhị phân.
2. Ngôn ngữ diễn tả thuật giải
Có rất nhiều ngôn ngữ lập trình nhưng ở đây không dùng một ngôn ngữ
nào, ta dùng ngôn ngữ ‘ thô hơn’ đủ khả năng diễn tả giải thuật, được
gọi là ‘ngôn ngữ giả Pascal’ (hay ngôn ngữ tựa Pascal).
2.1. Quy cách về cấu trúc chương trình
* Tên chương trình: Chương trình có tên, tên viết bằng chữ hoa có gạch
nối ( _ ) ,bắt đầu bằng chữ cái, độ dài không giới hạn.
* Câu giải thích được viết trong dấu { ... }
* Chương trình gồm nhiều đoạn, mỗi đoạn được phân cách nhau bới số
thứ tự.
2.2. Kí tự, phép toán, biểu thức
* Kí tự: Giống như ngôn ngữ chuẩn Pascal bo gồm:
- 26 chữ cái hoa và thường
- 10 chữ số 0..9
- Các dấu
* Các phép toán:
- Số học: +, -, *, /, ↑
- Quan hệ: < , = , > , ≤ , ≥, ≠
Các giá trị Logic là True, False
* Tên biến, biểu thức
- Tên biến là một dãy chữ cái, chữ số, dấu gạch nối ( _ ), bắt đầu bằng
chữ cái, độ dài không giới hạn.
- Biến chỉ số : Tên[chỉ số] ví dụ : a[i], b[i,j]
- Biểu thức tương tự như Pascal.
2.3. Các câu lệnh
Các câu lệnh cách nhau bởi dấu ;
2.3.1. Lệnh gán
Tên biến := biểu thức
Ví dụ: a:=10 ; b:=true ; t:=’hong ha’ ; c:=2^5+a
2.3.2. Câu lệnh ghép
Begin s1; s2; ... ; sn; end
Trong đó si là câu lệnh i
2.3.3. Câu lệnh điều kiện
- Dạng khuyết : If B then S trong đó B là biểu thức logic, S là
câu lệnh.
- Dạng đầy đủ: if B then S1 else S2
2.3.4. Câu lệnh lựa chọn
CASE
B1: S1 ;
B2: S2 ;
...
Bn: Sn;
ELSE Sn+1;
END CASCE
2.3.5. Câu lệnh lặp
* Lặp với số lần lặp biết trước
FOR i:=m TO n DO S
FOR i:= n DOWNTO m DO S
* Lặp với số lần lặp không biết trước:
- Lặp theo điều kiện trước: WHILE B DO S
- Lặp theo điều kiện sau: REPEAT S UNTIL B
2.3.6. Câu lệnh chuyển
GOTO n
2.3.7. Câu lệnh vào/ ra ( nhập/xuất )
input : danh sách biến
output : danh sách biểu thức
2.3.8. Câu lệnh kết thúc
END
2.4. Chương trình con
2.4.1. Chương trình con dạng hàm
FUNCTION Tên_hàm(danh sách tham số)
S1; S2; . . .; Sn;
Tên_hàm:= biểu thức;
RETURN
2.4.2. Chương trình con dạng thủ tục
PROCEDURE Tên_thủ_tục(danh sách tham số)
S1; S2; . . .; Sn;
RETURN
2.4.3. Lời gọi chương trình con
* Lời gọi chương trình con dạng hàm
Tên_hàm( danh sách tham số thực sự)
* Lời gọi chương trình con dạng thủ tục
CALL Tên_thủ_tục( danh sách tham số thực sự)
2.4.4.Khung của giả thuật
1. Tên giải thuật ( Program …..)
2. Các lệnh nhập dữ liệu
3. Các câu lệnh xử lý
4. Các lệnh xuất dữ liệu
5. Kết thúc
Bài tập thảo luận

1- Nêu ví dụ minh họa mối quan hệ giữa cấu trúc dữ liệu và


giải thuật.

2- Cấu trúc dữ liệu và cấu trúc lưu trữ khác nhau ở điểm
nào?

3- Có 1 dãy 15 tên, mỗi tên chứa 25 kí tự, hãy minh hoạ cấu
trúc lưu trữ dãy tên đó, nếu trong Pascal thì cấu trúc lưu
trữ nó có kích thước bao nhiêu.

4- Hãy nêu các cấu trúc dữ liệu tiền định của ngôn ngữ lập
trình Pascal
5- Ngoài cấu trúc dữ liệu tiền định Pascal còn có cấu trúc dữ
liệu nào nữa không? cho ví dụ

6- Hãy nêu các tính chất của giải thuật và cho ví dụ minh hoạ.
7- Viết giải thuật tính P theo công thức sau:

2 n
x
p= 1 + + x x
+... +
1! 2! n!

8- Nhập vào danh sách cho n người bao gồm: Tên, điểm. Viết giải thuật
tìm điểm của một người khi biết tên người đó.

9- Nhập vào danh sách cho n mặt hàng bao gồm: Tên, số lượng, đơn giá.
Viết giải thuật sắp xếp danh sách trên theo tên.

10- Cho một dãy số. Viết giải thuật tìm giá trị lớn nhất và giá trị nhỏ nhất
của dãy số đó. Trong đó có sử dụng chương trình con .
11- Viết giải thuật nhân 2 ma trân.
3. Thiết kế và phân tích giải thuật
3.1. Thiết kế giải thuật
3.1.1. Mô hình hoá việc giải quyết bài toán
- Khi thiết kế giải thuật ta sử dụng phương pháp mô đun hoá, nội
dung của phương pháp mô đun hoá là coi bài toán lớn như một mô
đun chính và phân chia nó thành các mô đun con, mỗi mô đun con
lại được phân chi tiếp, cho tới những mô đun ứng với các phần việc
cơ bản mà ta đã biết cách giải quyết.
Với phương pháp mô đun hoá bài toán thì lời giải của bài toán được
tổ chức theo cấu trúc cây (phân cấp) có dạng như sau:

C
B D

E F G H I
- Chiến thuật giải quyết bài toán là chiến thuật “ chia để trị”, để thể hiện
chiến thuật đó người ta dùng cách thiết kế “ đỉnh xuống “ ( Top - Down )
- Cách thiết kế Top - Down hay thiết kế từ khái quát đến đến chi tiết thể hiện
như sau: Phân tích tổng quát toàn bộ vấn đề xuất phát từ dữ liệu và mục
tiêu đề ra, đề cập đến vấn đề chủ yếu, rồi sau đó mới đi dần vào giải quyết
các vấn đề cụ thể một cách chi tiết hơn.
Ví dụ: Bài toán đặt ra là dùng máy vi tính để quản lý lương của cán bộ trong
xí nghiệp.
- Phân tích tổng quát bài toán:
+ Dữ liệu vào là một tệp hồ sơ về lương, bao gồm các bản ghi chứa các
thông tin về lương của cán bộ. Bản ghi gồm các trường: mã, họ và tên, đơn
vị, hệ số lương, phụ cấp, nợ.
+ Chương trình lập ra phải cho người sử dụng thực hiện được các công
việc chính sau:
1. Tìm kiếm thông tin
2. Cập nhất thông tin
3. In các bảng tổng hợp lương
- Xuất phát từ phân tích tổng quát trên thuật giải xử lý phải giải quyết
được 3 vấn đề sau:
1. Đọc tệp: Đọc thông tin từ đĩa từ vào bộ nhớ
2. Xử lý tệp: Xử lý các thông tin để đưa ra kết quả mong muốn
3. Ghi tệp: Lưu trữ thông tin mới nhất vào tệp.
Thiết kế theo sơ đồ sau:
QUẢN LÍ LƯƠNG

ĐỌC TỆP XỬ LÝ TỆP GHI TỆP


Các nhiệm vụ trên còn phức tạp, cần phải phân chia ra thành các nhiệm
vụ con. Chẳng hạn nhiệm vụ “ XỬ LÝ TÊP” được phân chia ra thành 3
nhiệm vụ con:
1. Tìm kiếm bản ghi
2. Cập nhật bản ghi
3. In bản lương
Những nhiệm vụ con lại được chia ra thành các nhiệm vụ nhỏ hơn theo
sơ đồ sau:

XỬ LÝ TỆP

TÌM BẢN GHI CẬP NHẬT BẢN GHI IN BẢNG LƯƠNG

TÌM THEO TÌM THEO SỬA XOÁ THÊM LƯƠNG LƯƠNG


MÃ TÊN THÁNG NĂM
* Ưu điểm của cách thiết kế Top - Down:
- Giải quyết bài toán có định hướng, tránh sa đà vào chi tiết
phụ.
- Làm nền tảng cho lập trình có cấu trúc.
- Bài toán do nhiều người cùng làm, phương pháp mô đun
hoá tách bài toán thành nhiều bài toán con tạo cho các nhóm
làm việc độc lập không ảnh hưởng đến nhóm khác.
- Chương trình xây dựng trên giải thuật thiết kế theo kiểu Top
- Down dễ dàng trong chỉnh lý và sửa chữa.
3.1.2 Phương pháp tinh chỉnh từng bước
- Phương pháp tinh chỉnh từng bước là phương pháp thiết kế
giải thuật gắn liền với lập trình, nó phản ánh tinh thần của
quá trình mô đun hóa bài toán và thiết kế kiểu Top - Down.
- Phương pháp tinh chỉnh tưng bước thể hiện như sau: Thoạt
đầu chương trình thể hiện giải thuật được trình bày bằng
ngôn ngữ tự nhiên, phản ánh ý chính của công việc cần làm.
Các bước sau sẽ chi tiết hoá dần dần, tương ứng với các
công việc nhỏ hơn, ta gọi là các bước tinh chỉnh. Càng ở các
bước sau mô tả công việc hướng tới các lệnh của chương
trình.
- Quá trinhg thiết kế giải thuật diễn ra như sau:
Ngôn ngữ tự nhiên → Giả ngôn ngữ → Ngôn ngữ lập trình
- Trong quá trình này dữ liệu cũng được tinh chế dần dần từ
dạng cấu trúc đến dạng cài đặt cụ thể.
Ví dụ 1: Lập trình sắp xếp một dãy n số nguyên khác nhau theo thứ tự tăng dần.
* Có thể phác thảo giải thuật theo ngôn ngữ tự nhiên như sau:
- Từ dãy các số nguyên chưa được sắp xếp chọn ra số nhỏ nhất
- Cứ lặp lại quá trình đó cho đến khi dãy chưa được sắp trở thành rỗng.
* Dùng ngôn ngữ Pascal :
1. Bước tinh chỉnh đầu tiên là:
For i:=1 To n-1 Do Begin
- Xét từ ai đến an để tìm số nhỏ nhất aj
- Đổi chỗ giữa ai và aj
End
2. Bước tinh chỉnh 2.1.: Tìm số nhỏ nhất
j:=i
For k:= j+1 To n Do
If ak < aj Then j:=k
3. Bước tinh chỉnh 2.2: Đổi chỗ
x:=ai ; ai:=aj ; aj=x;
* Sau khi chỉnh lại ta có thủ tục sắp xếp như sau :
Procedure Sap(a,n)
1. For i :=1 To n-1 Do
Begin
2. j :=i
For k :=j+1 To n Do
If a[k] < a[j] Then j :=k
3. x:=a[i]; a[i]:=a[j]; a[j]:=x;
End
Return
Ví dụ 2: Cho ma trạn cấp mxn ( m dòng, n cột). In ra các phần tử lớn nhất của các
dòng và đổi chỗ nó cho phần tử đầu dòng.
* Phác hoạ thuật giải:
1. Nhập m,n
2. Nhập các phần tử của ma trận
3. Tìm phần tử lớn nhất của các dòng và đổi chỗ cho phần tử đầu dòng.
4. In ra ma trận
* Dùng ngôn ngữ giả Pascal :
1. Readln(m,n)
2. For i:= 1 To m Do
Đọc vào các phần tử dòng i
3. For i:= 1 To m Do
Begin
Tìm a[i,k] là phàn tử lớn nhất cho dòng i
Đổi chỗ giữa a[i,k] và a[i,1]
End
4. For i:=1 To m Do
In ra các phần tử dòng i
* Tinh chỉnh các bước như sau:
1. Readln(m,n)
2. For i:=1 To m Do
For J:= 1 To n Do
Readln(a[i,j])
3. For i:=1 To m Do
Begin
k:=1
For j:=1 To n Do
If a[i,j]> a[i,k] Then k:=j
x:=a[i,k]; a[i,k]:= a[i,1]; a[i,1]:=x
End
4. For i:= 1 To m Do
Begin
Writeln;
For j:=1 To n Do
Write(a[i,j], ‘ ‘)
End
* Ta sẽ viết thành chương trình pascal hoàn chỉnh.
3.2. Phân tích giải thuật
3.2.1. Đạt vấn đề
* Khi xây dựng giải thuật và chương trình tương ứng có các
nhu cầu sau:
- Phân tích tính đúng đắn: Chạy thử chương trình trên bộ dữ
liệu, so sánh kết quả với kết quả đã biết.
- Các công cụ toán học chứng minh tính đúng đắn của giải
thuật.
- Tính đơn giản : Dễ hiểu, dễ lập trình, dễ chỉnh lý.
- Phân tích thời gian: Thời gian thực hiện giải thuật là tiêu
chuẩn đánh giá hiệu lực của giải thuật.
3.2.2. Phân tích thời gian thực hiện giải thuật
* Với một bài toán có nhiều giải thuật chọn giải thuật dẫn đến
kết quả nhanh nhất là vấn đề đòi hỏi của thực tế.
* Thời gian thực hiện phụ thuộc vào nhiều yếu tố như:
- Kích thước của dữ liệu vào. Nếu gọi n là kích thước của dữ liệu vào, thì
thời gian thực hiện T của một giải thuật được biểu diễn như một hàm của
n: T(n)
- Các kiểu lệnh, tốc độ xử lý của máy tính, ngôn ngữ viết chương trình,
chương trình dịch cũng ảnh hưởng đến tốc độ thực hiện. Nhưng những
yếu tố này không đồng đều vỡi mỗi loại máy vi tính, vì vậy không thể đưa
chúng vào xác lập T(n).
Do vậy dùng “ Độ phức tạp tính toán của giải thuật “ để đánh giá thời gian
thực hiện giải thuật.
3.2.3. Độ phức tạp tính toán của giải thuật
* Nếu thời gian thực hiện một giải thuật là T(n) = C n2 trong đó C là hằng
số, thì ta nói độ phức tạp tính toán của giải thuật này có cấp n2, và được
kí hiệu là: T(n)= O(n2)
* Tổng quát: Hàm f(n) có độ phức tạp tính toán cấp g(n) nếu hàm f(n) bị
chặn bởi Cg(n), với C là hằng số. Kí hiệu là f(n) = O(g(n))
Ví dụ 1: f(n) = O(n3) có nghĩa độ phức tạp tính toán cấp n3
Ví dụ 2: f(n) = O(2n) có nghĩa độ phức tạp tính toán cấp 2n
* Các hàm thể hiện độ phức tập tính toán của giải thuật có các dạng sau:
nn, n!, 2n, n3, n2, nlog2n, n, log2n. Các hàm đó đã được sắp theo thứ tự ưu tiên giá trị
giảm dần, có nghĩa là với giá trị của n hàm nn là lớn nhất, log2n là nhỏ nhất.
Các hàm này có dạng đồ thị như sau:

2n n3
n2
nlog2n

log2n
- Các hàm nn , n! , 2n gọi là các hàm mũ. Một gíải thuật có độ phức tạp
tính toán cấp hàm mũ thì rất chậm, do đó khó được chấp nhận.
- Các hàm n3, n2, nlog2n, n, log2n là các hàm loại đa thức. Độ phức tạp tính
toán của giải thuật có cấp đa thức thì chấp nhận được.
3.2.4. Xác định độ phức tạp tính toán
a) Quy tắc cộng
Giả sử T1(n) và T2(n) là thời gian thực hiện 2 đoạn chương trình P1 và
P2 mà T1(n)= O(f(n)) T2(n)=O(g(n)), thì thời gian thực hiện P1 rồi đến P2
tiếp theo sẽ là: T1(n) + T2(n) = O(max(f(n),g(n)))
Ví dụ 1: Chương trình có 3 bước, mỗi bước có độ phức tạp tính toán lần
lượt là O(n3), O(n), O(nlog2n). Vậy thời gian thực hiện 3 bước là:
T1(n) + T2(n) + T3(n) = O ( max(n3, n, nlog2n) = O(n3)
b) Qui tắc nhân
Nếu tương ứng với 2 bước P1 và P2 là T1(n) = O(f(n)),T2(n) = O(g(n)) thì
thời gian thực hiện P1 và P2 lồng nhau là : T1(n).T2(n) = O(f(n).g(n))
Ví dụ 2 : Câu lệnh x+1 có thời gian thực hiện là 1 hằng số, T(n) =O(1)
Ví dụ 3 : Câu lệnh sau :
For i :=1 To n Do x :=x+1 ;
có thời gian thực hiện là: T(n)=O(n.1)=O(n)
Ví dụ 4: Câu lệnh
For i :=1 To n Do
For j :=1 To n Do x:=x+1;
Thời gian thực hiện được đánh giá là: T(n)= O(n.n) = O(n2)
c) Qui tắc 3: Bỏ hằng số
O(c. f(n)) = O(f(n) trong đó c là một hằng số.
Ví dụ 5 : O(n2/ 3) = O(n2)
* Chú ý 1 : Khi đánh giá thời gian thực hiện giải thuật ta chỉ cần chú ý tới các
bước tương ứng với một phép toán được gọi là phép toán tích cực. Đó là
phép toán mà thời gian thực hiện nó không ít hơn thời gian thực hiện các
phép toán khác.
Ví dụ 6 : ex = 1+ x/1! + x2/2! + . . .+ xn/n! với x và n cho trước.
+ Giải thuật 1:
Read(x,n); s:=1;
For i :=1 To n Do Begin
P:=1;
For j :=1 To i Do p:=p*x/j ;
s:=s+p;
end;
end.
Trong giải thuật 1 phép toán tích cực ở đây là p:=p*x/j. Ta thấy nó được
thực hiện với số lần là: 1+2+3+ . . . + n = n(n+1)/2
Vậy thời gian thực hiện giải thuật là: T(n) = O(n2)
+ Giải thuật 2:
Read(x,n); s:=1; p:=1;
For i :=1 To n Do Begin
p:=p*x/i;
s:=s+p;
end;
end.
Thời gian thực hiện giải thuật 2 là: T(n) = O(n)
* Chú ý 2: có những trường hợp thời gian thực hiện giải thuật không chỉ phụ thuộc vào kích
thước của dữ liệu vào mà còn phụ thuộc vào chính tình trạng của dữ liệu đó nữa.
Ta có T(n) trong trường hợp thuận lợi nhất.
T(n) trong trường hợp trung bình
T(n) trong trường hợp xáu nhất.
Nếu T(n) trong trường hợp trung bình khó tính toán thì người ta thường đánh giá giải thuật
bằng T(n) trong trường hợp xấu nhất.
Ví dụ 7 : Thuật toán tìm kiếm
Cho véc tơ a có n phần tử a1, a2, ..., an . Tìm trong a phần tử đầu tiên có giá trị = x cho
trước.
Giải thuật như sau:
Found := False;
i:=1;
While (i<=n) and Not Found Do
If a[i] =x then Begin
Found:=True;
k:=i;
Write(k);
end
else i:=i+1;
End.
T(n) trong trường hợp tốt T(n)=O(1)
T(n) trong trường hợp xấu T(n)=O(n) . Vậy suy ra T(n)=O(n)
BÀI TẬP THẢO LUẬN 2
1. Mô đun hoá việc giải quyết bài toán có nghĩa là gì ? Cho ví dụ minh hoạ
2. Cách thiết kế “ Top - Down “ có nghĩa là gì ? Cho ví dụ minh hoạ.
3. Thế nào là phương pháp tinh chỉnh từng bước.
4. Thời gian thực hiện giải thuật được đánh giá bằng gì? tại sao?
5. Độ phức tạp tính toán của giải thuật có cấp là loại hàm nào thì được chấp
nhân.
6. Nêu các quy tắc xác định độ phức tạp tính toán giải thuật. Cho các ví dụ.
7. Thế nào là phép toán tích cực? Nó được dùng để làm gì? cho ví dụ.
8. Trong giải thuật nếu độ phức tạp tính toán phụ thuộc vào tình trạng của dữ
liệu đầu vào thì đánh giá dựa vào đâu?
9. Đánh giá thời gian thực hiện giải thuật của các bài toán đã cho ở bài tập
thao luận 1
Chương 2: Đề quy và giải thuật đệ quy
1. Khái niệm đệ quy
Khái niệm: Ta nói một đối tượng là đệ quy nếu nó được định nghĩa dưới
dạng chính nó.
Ví dụ 1: Trên màn hình của vô tuyến truyền hình lại xuất hiện hình ảnh
của chính cái nàm hình vô tuyến đó.
Ví dụ 2: Trong toán học hay gặp định nghĩa đệ quy:
1. Định nghĩa số tự nhiên
a. 1 là số tự nhiên
b. x là số tự nhiên nếu x-1 là số tự nhiên
2. Hàm n !
a. 0 !=1
b. Nếu n>0 thì n ! = n(n-1) !
2. Giải thuật đệ quy và thủ tục đệ quy
* Nếu lời giải của một bài toán T được thực hiện bằng lời giải của bài toán
T’ có dạng giống như T thì đó là một lời giải đệ quy.
Trong đó T’ tuy giống T nhưng nó phải nhỏ hơn T.
* Giải thuật tương ứng với lời giải đệ quy gọi là giải thuật đệ quy.
* Thủ tục viết cho bài toán có lời giải đệ quy gọi là thủ tục đệ quy.
Trong thủ tục đệ quy có lời gọi tới chính nó, mỗi lần gọi thì kích thước bài
toán thu nhỏ hơn và dần dần tiến tới trường hợp đặc biệt là trường hợp
suy biến.
Ví dụ : Bài toán tìm 1 từ trong cuốn từ điển.
- Giải thuật đệ quy của bài toán này thể hiện như sau :
IF từ điển là một trang THEN tìm từ trong trang ấy
ELSE BEGIN
Mở từ điển vào trang giữa; Xác định xem nửa nào chứa từ
IF từ nằm trong nửa trước THEN tìm trong nửa trước
ELSE tìm trong nửa sau
END
Trong giải thuật này có 2 điểm cần chú ý:
Điểm 1: Sau mỗi lần từ điển được tách đôi, một nửa thích hợp sẽ được
tìm kiếm theo chiến thuật đã dùng.
Điểm 2: Có trường hợp đặc biệt là sau khi tách đôi từ điển chỉ còn 1
trang, giải quyết trực tiếp bằng cách tìm từ trong trang đó. Trường hợp
đặc biệt này gọi là trường hợp suy biến.
- Giải thuật này gọi là giải thuật chia đôi: Bài toán được tách đôi ra bài
toán nhỏ hơn, bài toán nhỏ hơn lại dùng chiến thuật chia đôi, cho tới khi
gặp trường hợp suy biến.
- Thủ tục đệ quy của bài toán được viết như sau:
Procedure timkiem(Tudien, tu)
IF Tudien chỉ còn một trang THEN tìm từ trong trang ấy
ELSE BEGIN
Mở từ điểm vào trang giữa
Xác định xem nửa nào chứa từ
IF Từ nằm ở nửa trước
THEN CALL timkiem(Tudien1, tu)
ELSE CALL timkiem(Tudien2, tu)
END
RETURN
3. Thiết kế giải thuật đệ quy
* Khi bài toán đang xét hoặc dữ liệu đang xử lý được định nghĩa dưới
dạng đệ quy thì việc thiết kế các giải thuật đệ quy tỏ ra rất thuận lợi.
* Khi thiết kế giải thuật đệ quy cần trả lời các câu hỏi sau:
1- Có thể định nghĩa bài toán dưới dạng một bài toán cùng loại nhưng
nhỏ hơn như thế nào ?
2- Như thế nào là kích thước của bài toán được giảm đi ở mỗi lần gọi
đệ quy ?
3- Trường hợp đặc biệt nào của bài toán sẽ được gọi là trường hợp
suy biến ?
* Bài toán 1: Tính hàm n!
Định nghĩ đệ quy của hàm n! như sau:
FAC(n) = 1 nếu n=0
FAC(n)=n*FAC(n-1) nếu n>0
Thuật giải đệ quy được viết dưới dạng hàm như sau :
Function FAC(n)
If n=0 then FAC :=1
Else FAC(n) := n * FAC(n-1)
Đối chiếu với 3 đặc điểm của thủ tục đệ quy ta thấy:
- Lời gọi tới chính nó đứng sau Else
- Mỗi lần gọi đệ quy giá trị giảm đi: FAC(4)→FAC(3)→FAC(2)→ FAC(1)
- Trường hợp suy biến là FAC(0): FAC(0) = 1
* Bài toán 2: Lập dãy số FIBONACCI 1 1 2 3 5 8 13 ...
F(n)=F(n-2)+F(n-1)
Định nghĩa F(n) như sau :
F(n) = 1 nếu n ≤ 2
F(n)=F(n-2)+F(n-1) nếu n>2
Thủ tục đệ quy thể hiện giải thuật tính F(n) như sau :
Function F(n :integer) :integer;
If n<=2 then F:=1
Else F:=F(n-2)+F(n-1)
Return
* Bài toán 3: Bài toán “Tháp Hà nội “, đây là bài toán mang tính chất trò
chơi, nội dung như sau:
Có n đĩa kích thước nhỏ dần, đĩa có lỗ ở giữa. Có thể xếp chồng chúng lên
nhau xuyên qua một cái cọc, to dưới nhỏ trên để cuối cùng có một chồng
đĩa dạng như hình tháp.

A B C
Yêu cầu đặt ra:
Chuyển chồng đĩa từ cọc A sang cọc C theo những điều kiện sau:
1- Mỗi lần chỉ được chuyển một đĩa
2- Không khi nào có tình huống đĩa to ở trên đĩa nhỏ ở dưới
3- Được phép sử dụng 1 cọc trung gian ( cọc B) để đặt tạm thời.
Ta xét một vài trường hợp đơn giản:
* Trường hợp 1 đĩa:
- Chuyển từ cọc A sang cọc C
* Trường hợp 2 đĩa:
- Chuyển đĩa thứ nhất từ cọc A sang cọc B
- Chuyển đĩa thứ hai từ cọc A sang cọc C
- Chuyển đĩa thứ nhất từ cọc B sang cọc C
* Trường hợp n đĩa (n>2): Ta coi n-1 đĩa ở trên như đĩa thứ nhất và xử lý
giống như trường hợp 2 đĩa:
- Chuyển n-1 đĩa từ cọc A sang cọc B
- Chuyển đĩa thứ n từ cọc A sang cọc C
- Chuyển n-1 đĩa từ cọc B sang cọc C
* Chuyển n-1 đĩa từ cọc A sang cọc B thuật giải sẽ là:
- Chuyển n-2 đĩa từ cọc A sang cọc C
- Chuyển 1 đĩa từ cọc A sang cọc B
- Chuyển n-2 đĩa từ cọc C sang cọc B
Cứ như thế cho đến khi trường hợp suy biến xảy ra, đó là trường hợp ứng
với bài toán chuyển 1 đĩa.
Thủ tục của bài toán “ Tháp Hà nội “ như sau:
Procedure Hanoi(n,A,B,C)
If n=1 then chuyển đĩa từ A sang C
Else Begin
Call Hanoi(n-1,A,C,B)
Call Hanoi(1,A,B,C)
Call Hanoi(n-1,B,A,C)
End;
Return
BÀI TẬP THẢO LUẬN 3
1. Thế nào là giải thuật đệ quy ?
2. Ưu nhược điểm của giải thuật đệ quy?
3. Trong bộ nhớ của máy tính dùng vùng nhớ nào để dùng cho giải thuật đệ
quy.
4. Trường hợp suy biến là trường hợp như thế nào trong giải thuật đệ quy.
5. Thường hay dùng cấu trúc lập trình nào để thể hiện giải thuật đệ quy
6. Viết giải thuật đệ quy cho bài toán sau:
Acker(m,n)= n+1 nếu m=0
Acker(m,n)= Acker(m-1,1) nếu n=0
Acker(m,n)= Acker(m-1,Acker(m,n-1)) với các trường hợp khác.
7. Giải thuật tính ước số chung lớn nhất của hai số nguyên dương p và q
(p>q) như sau: Gọi r là số dư của phép chia p cho q.
- Nếu r=0 thì q là ước số chung lớn nhất
- r khác 0 thì gán cho p giá trị của q, gán cho q giá trị của r rồi lặp lại quá
trình.
Hãy xây dựng giải thuật đệ quy tính ước số chung lớn nhất USCLN(p,q)
8. Hàm C(n,k) với n, k là các giá trị nguyên không âm và k<=n,
được định nghĩa như sau:
C(n,n)=1
C(n,0)=1
C(n,k)=C(n-1,k-1)+C(n-1,k) nếu 0< k<n
Hãy xây dựng giải thuạt đệ quy cho bài toán trên
9. Viết thủ tục đệ quy in ngược một dòng kí tự cho trước.
Chương 3: Các kiểu dữ liệu
1. Dữ liệu trìu tượng cơ bản
1.1. Khái niệm kiểu dữ liệu
* Kiểu dữ liệu xác định tập hợp các giá trị mà hằng, biến, biểu thức, hàm có thể có.
* Kiểu của một đại lượng được chỉ ra trong lúc khai báo hay có thể suy ra từ dạng của
nó.
* Mỗi toán tử, hàm thuộc kiểu định sẵn và tạo kết quả từ kiểu định sẵn.
Nếu một toán tử có đối thuộc một vài kiểu khác nhau thì kiểu của kết quả theo quy tắc
riêng của ngôn ngữ lập trình đó.
1.2. Các loại kiểu dữ liệu
Kiểu dữ liệu được chia làm 2 loại:
- Kiểu dữ liệu cơ bản
- Kiểu dữ liệu có cấu trúc.
2. Kiểu dữ liệu cơ bản
2.1. Khai báo kiểu dữ liệu
* Địng nghĩa kiểu dữ liệu như sau:
Type Tên_kiểu = kiểu dữ liệu
* Khai báo biến có kiểu dữ liệu đã định nghĩa
Var tên biến : Tên_kiểu
Ví dụ 1: Type mau=(do, xanh, vang, tim)
Var mau_ao: mau
Ví dụ 2 : Type gioi_tinh = (nam, nu)
Var n1: gioi_tinh
Ví dụ 3: Type PTGT= (tau, oto, xe may, may bay, tau thuy)
Var van_chuyen : PTGT
2.2 Kiểu nguyên (Integer )
* Kiểu nguyên bao gồm các giá trị là số nguyên. Được khai báo với từ khoá
Integer.
Var i : Integer
* Các phép tính với kiểu nguyên: + - * / div mod
2.3. Kiểu thực (Real)
* Kiểu thực bao gồm các số thực. Được khai báo với từ khoá Real.
Var x : Real
* Các phép tính kiểu thực: + - * /
2.4. Kiểu Boolean
* Kiểu Boolean là kiểu chỉ nhận giá trị quy ước là true, false. Được khai báo với từ khoá
Boolean.
Var t: Boolean
* Các phép toán Boolean: NOT, AND, OR
2.5. Kiểu kí tự
* Kiểu kí tự là kiểu chỉ nhận giá trị là 1 kí tự trong bảng mã ASCII. Được khai báo với từ
khoá Char.
Var T: Char
2.6. Kiểu liệt kê
* Kiểu liệt kê nhận giá trị trong tập danh sách liệt kê. Được khai báo bằng cách liệt kê
một tập các giá trị có thứ tự
Type Tên_kiểu = Tập danh sach liệt kê
Ví dụ: Type mau1=(xanh, do, tim, vang, den)
Var mau_khan: mau1
2.7. Kiểu miền con
* Kiểu miền con nhận giá trị trong khoảng vô hướng đếm được. Được khai báo bằng
cách chỉ ra khoảng : min .. max
Var M : 10 .. 50
3. Dữ liệu có cấu trúc : Mảng và danh sách
3.1. Khái niệm
3.1.1. Mảng (Array)
* Khái niệm : Mảng là một tập hợp có thứ tự gồm một số cố định các phần tử
cùng kiểu.
* Một phần tử mảng được chỉ ra bởi chỉ số, thể hiện thứ tự của phần tử trong
mảng. Véc tơ là mảng 1 chiều có 1 chỉ số (i). Ma trận là mảng 2 chiều có 2 chỉ
số (i, j). Không gian 3 chiều là mảng 3 chiều có 3 chỉ số. Không gian n chiều là
mảng n chiều có n chỉ số.
* Có các phép tạo lập mảng, tìm kiếm 1 phần tử từ mảng, lưu trữ một phần tử
mảng.
* Không cho phép bổ sung hoặc loại bỏ một phần tử mảng.
3.1.2. Danh sách (List)
* Khái niệm: Danh sách là một tập hợp có thứ tự gồm một số biến động các phần
tử cùng kiểu. Phép loại bỏ, bổ sung 1 phần tử là phép thường xuyên tác động
lên danh sách.
Ví dụ: Tập hợp người đến khám bệnh cho ta một danh sách. Người đến xếp hàng
khám bổ sung ở phía sau, người được khám sẽ ra khỏi hàng ( loại bỏ ) ở phía
trước.
* Danh sách tuyến tính: Một danh sách mà quan hệ lận cận giữa
các phần tử được hiển thị ra thì được gọi là danh sách tuyến
tính. Véc tơ là hình ảnh của một danh sách tuyến tính tại một
thời điểm nào đấy. Như vậy danh sách tuyến tính hoặc rỗng
( không có phần tử nào) hoặc có dạng (a1, a2, ..., an) với ai ,
1 ≤ i ≤ n là các dữ liệu nguyên tử.
* Trong danh sách tuyến tính tồn tại phần tử đầu là a1, phần tử
cuối là an, phần tử thứ i là ai . Với ai bất kỳ 1 ≤ i ≤ n-1 thì ai+1
gọi là phần tử sau ai ; 2 ≤ i ≤ n thì phần tử ai-1 là phần tử trước
của ai .
* n là độ dài ( kích thước) của danh sách, nó có độ dài thay đổi.
* Một phần tử trong danh sách thường là một bản ghi (gồm một
hoặc nhiều trường).
Ví dụ: Danh mục điện thoại là một danh sách tuyến tính, mỗi
phần tử của nó là một thuê bao gồm 3 trường: Họ tên chủ hộ,
địa chỉ, số điện thoại.
* Trong danh sách có các phép sau đây:
- Phép bổ sung
- Phép loại bỏ
- Phép ghép
- Phép tách
- Phép sao chép
- Phép cập nhật
- Phép sắp xếp
- Phép tìm kiếm.
* Tệp(File) là một danh sách có kích thước lớn được lưu trữ ở bộ
nhớ ngoài.
3.2. Các phép toán trên danh sách
* Phép bổ sung: Có thể bổ sung phàn tử vào cuối danh sách.
* Phép loại bỏ: có thể loại bỏ một phàn tử ra khỏi danh sách.
* Phép ghép: có thể ghép hai hay nhiều danh sách thành một
danh sách.
* Phép tách: có thể tách một danh sách thành nhiều danh sách.
* Phép cập nhật: cập nhật giá trị cho các phần tử của danh sách.
* Phép sao chép: có thể sao chép một danh sách.
* Phép sắp xếp: Có thể sắp xếp các phần tử của danh sách theo
một thứ tự nhất định.
* Phép tìm kiếm: Tìm kiếm trong danh sách một phần tử mà một
trường nào đó có giá trị ấn định.
Ví dụ 1: Minh hoạ cho các phép toán trên danh sách được cài
đặt trên mảng. Cho danh sách các số nguyên, thêm vào 1 số
nguyên và loại bỏ một số nguyên.
Program Bo_sung_loai_bo_danh_sach;
Uses crt;
Var i,j,k,m,n: integer;
a: array[1..100] of integer;
Begin
clrscr;
{Tao danh sach }
Write(‘Nhap so phan tu n cua mang: ‘); Read(n);
For i:=1 to n do
Begin
Write(‘a(‘, i,’)=’);
Readln(a[i]);
End;
{ Bo sung mot phan tu vao cuoi danh sach}
n:=n+1;
Write(‘ Nhap phan tu them vao: ‘);
Readln(a[n]);
{ Loai bo phan tu thu k trong danh sach }
Write(‘ Loai bo phan tu thu :’);
Readln(k);
For i:=k to n-1 do a[i]:= a[i+1];
n:=n-1;
{ In danh sach ket qua }
For i:=1 to n do Write(a[i], ‘ ‘);
Writeln;
Readln;
End.
Ví dụ 2: Xét danh sách thuê bao điện thoại. Mỗi thuê bao coi
như một bản ghi có 3 trường: HT (họ tên), DC (địa chỉ ), SDT
(số điện thoại). Có 2 danh sách thuê bao ghép thành một danh
sách.
Program ghep_danh_sach;
Uses crt;
Type db=record
HT: string[25];
DC: string[30];
SDT: longint;
End;
DT= Array[1..200] of db;
Var DS1, DS2: DT;
i,j,k,n1,n2 : integer;
{ Thu tuc tao danh sach }
Procedure Tao_ds(var DS: DT; var n:integer);
Begin
clrscr;
{ Tao danh sach }
Write(‘ Nhap so nguoi trong danh sach ‘); Readln(n);
For i:=1 to n do
With DS[i] do begin
Write(‘ Ho va ten : ‘); Readln(HT);
Write(‘ Dia chi :’); Readln(DC);
Write(‘ So dien thoai : ‘); Readln(SDT);
end;
End;
{ Thu tuc ghep danh sach a2 vao cuoi danh sach a1}
Procedure Ghep_danh_sach(var a1,a2:DT; m1,m2:integer; var m:integer);
Begin
m:=m1+m2;
For i:=m1+1 to m do a1[i]:=a2[i-m1]
End;
{ Than chuong trinh chinh }
Begin
Clrscr;
{ Tao danh sach 1}
Tao_ds(DS1,n1);
{ Tao danh sach 2 }
Tao_ds(DS2,n2);
{ Ghep danh sach }
Ghep_danh_sach(DS1,DS2,n1,n2,k);
{ In danh sach ket qua }
For i:= 1 to k do
With DS1[i] do
Writeln( HT: 25,DC: 30, SDT:10);
Readln;
End.
Ví dụ 3: Tìm kiếm trong danh sách. Xét danh sách tên các sách
trong thư viện. Mỗi cuốn sách coi như một bản ghi gồm các
trường: TEN ( tên sách), TG (tác giả), NXB (nhà xuất bản),
NAM (năm xuất bản). Hãy tìm cuốn sách mà tên đã biết.
Program Tim_danh_sach;
Uses crt;
Type db=record
TEN: string[25];
TG: string[25];
NXB: string[25];
NAM: integer;
End;
DS= Array[1..200] of db;
Var SACH: DS;
m : integer;
{ Thu tuc tao danh sach }
Procedure Tao_ds(var DS1: DS; var n:integer);
Begin
Write(‘ Nhap so sach trong danh sach ‘); Readln(n);
For i:=1 to n do
With DS1[i] do begin
Write(‘ Ten sach : ‘); Readln(TEN);
Write(‘ Tac gia :’); Readln(TG);
Write(‘ Nha xuat ban : ‘); Readln(NXB);
Write(‘ Nam xuat ban : ‘); Readln(NAM);
end;
End;
Procedure Tim( DS2: DS; n1:integer);
Var t:boolean; x: string[25]; j:integer;
Begin
Write(‘ Nhap ten sach can tim ‘); Readln(x);
t:=false;
j:=1;
While (not t) and (j<=n1) do
Begin
If DS2[j].TEN=x then begin
t:=true;
With DS2[j] do
Writeln(TEN:25, TG:25, NXB:25, NAM);
End;
j:=j+1;
End;
If not t then Writeln(‘ Khong tim thay sach ‘);
End;
{ Than chuong trinh chinh }
Begin
Clrscr;
{ Tao danh sach}
Tao_ds(SACH,m);
{ Tim kiem}
Tim(SACH,m);
Readln;
End.
4. Cấu trúc lưu trữ của mảng ( cài đặt cấu trúc dữ liệu)
* Cách biểu diễn một cấu trúc dữ liệu trong bộ nhớ của máy tính gọi là cấu trúc
lưu trữ.
* Cấu trúc lưu trữ đơn giản nhất là dùng địa chỉ tính được để lưu trữ và tìm
kiếm phần tử và được gọi là lưu trữ kế tiếp.
* Lưu trữ kế tiếp được thể hiện như sau: Một số từ máy kế tiếp sẽ được dành
ra để lưu trữ các phần tử của mảng.
* Với mảng một chiều có n phần tử thì cần n từ máy kế tiếp để lưu trữ. Do kích
thước vector đã được định trước nên không gian nhớ n từ máy cũng được
ấn định trước.
Giả sử vector a có n phần tử là ai i : 1 → n , mỗi phần tử chiếm c từ máy,
nên cả vector chiếm c. n từ máy kế tiếp.
Lo
a1 a2 ... ai ... an
Lo là địa chỉ của phần tử đầu a1. Vây địa chỉ của phần tử ai sẽ là:
Loc(ai) = Lo +c(i-1)
Lo gọi là địa chỉ gốc ( địa chỉ của từ máy đầu tiên trong miềm nhớ kế tiếp lưu
trữ vector ) .
Hàm địa chỉ: f(i) = c.(i-1)
* Với mảng 2 chiều: Xét mảng 2 chiều có m dòng và n cột, phần tử là aij
i : 1 → m , j : 1 → n . Mỗi phần tử chiến c từ máy. Ta xét trong Pascal, cách lưu
trữ các phần tử của mảng 2 chiều theo ưu tiên hàng ( hết hàng này đến
hàng khác).
Lo

a11 a12 ... a1n a21 a22 ... a2n ... am1 am2 ...amn
Công thức tính địa chỉ phần tử aij là:
Loc(aij) = Lo + c (i-1) n + c(j-1)
* Với mảng 3 chiều: Giả sử xét mảng b có các phần tử bijk i : 1 → m , j : 1 → n
, k : 1 → t . Mỗi phân tử chiếm c từ máy. Lưu trữ ưu tiên hàng.
ví dụ m=2, n=2, t=3 các phần tử được lưu trữ theo thứ tự sau:
b111, b112, b113, b121, b122, b123, b211, b212, b213, b221, b222, b223
Lo là địa chỉ gốc ( địa chỉ của phần tử đầu tiên b111).
Hàm địa chỉ:
Loc(bijk) = Lo + c(i-1).n .t + c.(j-1).t + c.(k-1)
Ví dụ Loc(b223) = Lo+ c. 6+ c . 3 + c.2
* Ưu nhược điểm của lưu trữ kế tiếp mảng :
- Ưu điểm : Khi mảng được lưu trữ kế tiếp thì việc truy nhập vào phần tử của
mảng được thực hiện trực tiếp dựa vào địa chỉ tính được, nên tốc độ nhanh
và đồng đều đối với mọi phần tử.
- Nhược điểm : Cần một vùng nhớ trống liên tục có kích bằng kích thước của
mảng, không tận dụng được các vùng nhớ nằm rải rác nhiều nơi trong bộ
nhớ.
5. Lưu trữ kế tiếp của danh sách tuyến tính
Dùng mảng một chiều làm cấu trúc lưu trữ danh sách tuyến tính. Tức là dùng
vector lưu trữ (Vi) với 1≤ i ≤ m để lưu trữ một danh sách tuyến tính
(a1,a2,...,an). Phần tử ai được chứa ở Vi .
a1 a2... ai... an
V1 V2 ... Vi ... Vn ... Vm
Do số phần tử của danh sách tuyến tính luôn biến động tức là kích thước n
luôn thay đổi, do vậy m = max(n). Mặt khác n không thể xác định chính xác
mà chỉ dự đoán do vậy dẫn tới tình trang nếu max(n) lớn dẫn tới tình trạng
lãng phí bộ nhớ cũng như lãng phí thời gian để thực hiện các thao tác để
dồn các phần tử xuống khi ta thêm một phần tử vào danh sách tuyến tính.
Câu hỏi thảo luận và bài tập
1. Nêu khái niệm mảng, cho ví dụ minh hoạ.
2. Nêu khái niệm danh sách, so sánh mảng và danh sách.
3. Thế nào là cấu trúc lưu trữ. Lưu trữ mảng bằng bằng cách
nào ?
4. Tính địa chỉ của các phần tử mảng 1 chiều, 2 chiều, 3 chiều
trong cách lưu trữ kế tiếp mảng.
5. Lưu trữ kế tiếp danh sách tuyế tính như thế nào ? Cách này có
nhược điểm gì ?
6. Xét danh sách thí sinh thi đại học. Mỗi thí sinh coi như một bản
ghi có 5 trường: HT (họ tên), SBD (số báo danh ), DT (điểm
Toán), DL ( điểm Lý), DH ( điểm Hoá). Viết chương trình trong
Pascal có các thủ tục : tạo danh sách, chèn bản ghi, xoá bản
ghi, tìm kiếm thí sinh khi biết số báo danh, sắp xếp danh sách
theo số báo danh.
6. Cấu trúc ngăn xếp (Stack)
6.1. Đinh nghĩa
Stack là một kiểu danh sách tuyến tính đặc biệt mà phép bổ sung
và phép loại bỏ luôn luôn thực hiện ở một đầu gọi là đỉnh (Top).

Top( đỉnh)

Bottom ( đáy )

* Có thể hình dung Stack như cơ cấu của 1 hộp chứa đạn súng
trường, lắp đạn vào hay lấy đạn ra cùng ở đầu hộp. Viên đạn
mới nạp vào ở đỉnh ( top), viên đạn nạp vào đầu tiên thì nằm ở
đáy (bottom). Viên đạn được nạp vào sau cùng lại chính là viên
đạn lên nòng súng trước tiên.
* Nguyên tắc ‘Vào sau ra trước ‘ của tack đã đưa tới
một cách gọi khác : danh sách kiểu LIFO ( Last in
- First out)
* Stack có thể rỗng hoặc bao gồm một số phần tử.
6.2. Lưu trữ Stack bằng mảng ( lưu trữ kế tiếp)
Có thể lưu trữ Stack bởi một vector S gồm n phần
tử nhớ kế tiếp nhau. Nếu T là địa chỉ phần tử đỉnh
của stack thì T sẽ có giá trị biến đổi khi stack hoạt
động ( T là một biến trỏ).
Ta quy ước dùng địa chỉ tương đối thì khi stack
rỗng T=0.
Nếu mỗi phần tử của stack ứng với một từ máy thì
khi bổ sung 1 phần tử T sẽ tăng lên 1.
Khi 1 phần tử bị loại khỏi stack thì T giảm đi 1.
S1 S2 S3 … T Sn
Đỉnh

6.3. Các phép toán trên stack


* Bổ sung 1 phần tử vào stack
Procedure Push(S, T, X)
Thủ tục này thực hiện bổ sung phần tử X vào stack lưu trữ bởi vector S có n
phần tử, T là con trỏ trỏ tới đỉnh stack.
1. Xét xem stack có bị tràn ( over flow) không? Tràn khi stack không còn
chỗ.
If T>= n then Begin Write(‘ Stack tràn ‘)
Return
End;
2. Chuyển con trỏ
T := T+1
3. Bổ sung phần tử mới X
S[T] := X
3. Return
* Loại bỏ một phần tử ra khỏi stack
Function PoP(S,T)
Hàm này thực hiện việc loại bỏ phần tử ở đỉnh stack S
đang trỏ bởi T. Phần tử bị loại bỏ sẽ được thu nhận và đưa
ra.
1. Xét xem stack có cạn khổng? ( cạn là stack đã rỗng )
If T<= 0 then Begin
Write(‘ Stack cạn ‘)
Return(0)
End
2. Chuyển con trỏ
T := T-1
3. Đưa phần tử bị loại ra
Pop := S[T+1]
* Ví dụ ứng dụng stack trong bài toán đổi cơ số hệ đếm
Khi đổi số nguyên hệ 10 sang hệ 2 ta thực hiện lấy số hệ 10 chia liên tiếp cho 2,
kết quả là phần dư của phép chia lấy theo thứ tự ngược lại. Như vậy ta đã áp
dụng cơ chế vào trước ra sau. Mỗi lần chia ta lấy số dư của phép chia đẩy vào
stack ( thủ tuc Push). Khi đã kết thúc phép chia kết quả lấy các số dư từ stack ra (
hàm loại bỏ phần tử khỏi stack PoP).
26 hệ 10 chuyển thành 11010 hệ 2

1 1
1 1 11
0 0 0 000
1 1 1 1 1111
Push : 0 0 0 0 0 Pop : 0 0 0 0 0
Kết quả : 1 1 0 1 0
Procedure chuyen_doi ;
While n<> 0 do Begin
R:=n mod 2
Call Push(S,T,R);
n= n div 2
End;
While S<>NULL do Begin
R:=POP(S,T); { lay R tu dinh T cuas Stack S }
Write(R)
End;
Return;
7. Cấu trúc hàng đợi (Queue)
7.1. Định nghĩa
* Hàng đợi (queue) là kiểu danh sách tuyến tính
mà phép bổ sung được thực hiện ở một đầu,
gọi là lối sau (rear) và phép loại bỏ thực hiện ở
một đầu khác, gọi là lối trước (front).
Cơ cấu của Queue giống như một hàng đợi,
vào ở một đầu, ra ở đầu khác, nghĩa là vào
trước ra trước. Queue con được gọi là danh
sách kiểu FIFO (First in - First out )
7.2. Cài đặt queue bằng mảng (lưu trữ kế tiếp)
Dùng vector lưu trữ Q có n phần tử làm cấu trúc lưu trữ queue.
Để truy nhập vào hàng đợi ta dùng 2 biến trỏ: R trỏ vào lối sau
và F trỏ vào lối trước.
Khi queue rỗng thì R=F=0. Nếu mỗi phần tử của queue được
lưu trong 1 từ máy, khi bổ sung 1 phần tử vào queue thì R sẽ
tăng lên 1, còn khi loại bỏ một phần tử khỏi queue thì F tăng
lên 1.
Như vậy các phần tử của queue sẽ di chuyển khắp không gian
nhớ khi thực hiện phép bổ sung và loại bỏ. Khắc phục tình
trạng đó người ta coi không gian nhớ tổ chức cho queue kiểu
vòng tròn nghĩa là với vector lưu trữ Q thì phần tử Q[1] coi
đứng trước Q[n]
Q[1]
Q[n]

7.3. Các phép toán trên Queue


Thực hiện các phép toán này với việc dùng mảng lưu trữ queue
kiểu vòng tròn.
a. Bổ sung một phần tử vào queue
Procedure CQINSERT (F,R,Q,n,X)
Cho con trỏ F,R trỏ tới lối trước và lối sau của queue được lưu
trữ bới vector Q có n pần tử theo kiểu vòng tròn. X là phần tử
mới bổ sung.
Thuật giải gồm các bước sau:
1. { chỉnh lại con trỏ }
If R=n then R:=1
Else R:= R+1;
2. { kiểm tra tràn }
If F=R then Begin
Write( ‘ Queue tràn ‘);
Return
End;
3. { bổ sung X vào}
Q[R]:=X;
4. Return { kết thúc}
b. Loại bỏ phàn tử ra khỏi queue
Function CQDELETE(F,R,Q,n)
Cho F và R trỏ tới lối trước và lối sau của queue kiểu vòng tròn, được lưu trữ bới vector Q
có n phần tử. Hàm này loại bỏ phần tử ở lối trước của queue và đưa nọi dung của phần
tử đó ra. Giải thuật gồm các bước sau:
1. { Kiểm tra cạn}
If F=0 then Begin
Write(‘ queue cạn ‘)
Return(0)
End;
2. { Loại bỏ}
Y:=Q[F]
3. { Xử lý trường hợp queue trở thành rỗng sau phép loại bỏ}
If F=R then { F=R tức là queue chỉ có 1 phần tử}
Begin
F=R=0
Return(y)
End;
4. { Chỉnh lại con trỏ F sau loại bỏ }
If F=n then F:=1 else F:=F+1
5. { Kết thuc trả về Y}
Return(Y);
Chương 4: Danh sách móc nối ( danh sách liên kết: Linked list )

Việc sử dụng con trỏ hoặc mối nối để tổ chức danh sách tuyến tính ta gọi là
danh sách móc nối.
1. Danh sách nối đơn (Singly linked list)
1.1. Quy tắc tổ chức danh sách nối đơn
* Mỗi phần tử của danh sách được lưu trữ trong một phần tử nhớ mà ta gọi là
nút(node). Mỗi nút bao gồm một số từ máy kế tiếp. Một nút có thể nằm ở bất kỳ
chỗ nào trong bộ nhớ. Trong mỗi nút ngoài thông tin ứng với phần tử, còn có
chứa địa chỉ của phần tử đứng sau nó trong danh sách.
* Quy cách của mỗi nút :

INFOR LINK
- Trường INFOR chứa thông tin của phần tử.
- Trường LINK chứa địa chỉ nút tiếp theo ( địa chỉ móc nối).
* Riêng nút cuối cùng không có nút đứng sau nên không có địa chỉ như các nút
khác, ta gọi là mối nối không và kí hiệu là NULL.
* Để truy nhập vàp mọi nút trong danh sách thì phải truy nhập được vào nút đầu
tiên, do đó cần phải có con trỏ L trỏ tới nút đầu tiên.
L
A B C

: Kí hiệu mối nối không

* Nếu danh sách rỗng thì qui ước L = NULL.


* Để tổ chức một danh sách móc nối thì khả n ăng sau đây càn phải có:
1. Tồn tại phương tiện chia bộ nhớ ra thành các nútvà ở mỗi nút có thể truy
nhập vào từng trường.
2. Tồn tại cơ chế để xác định được một nút đang được sử dụng (bận) hoặc
không sử dụng (nút trống).
3. Tồn tại một cơ cấu như một “kho chứa nút trống” để cung cấp các nút trống
khi có yêu cầu sử dụng và thu hồi lại các nút đó khi không cần dùng nữa.
Ta qui ước :
X AVAIL là phép lấy 1 nút có địa chỉ là X từ danh sách chỗ
trống ra để sử dụng (cấp phát một nút)
X AVAIL là trả là phép trả 1 nút có địa chỉ là X về danh sách
chỗ trống ( phép thu hồi nút X).

1.2. Một số phép toán với danh sách nối đơn


* Ta kí hiệu với một nút có địa chỉ là p ( được trỏ bởi p) thì INFOR(p) chỉ
trường INFOR của nút ấy, LINK(p) chỉ trường LINK của nút ấy.
a. Bổ sung một nút mới vào danh sách móc nối
Cho con trỏ Love trỏ tới nút đầu cuẩ danh sách móc nối, M là con trỏ trỏ tới một
nút đang có trong danh sách.
Thủ tục bổ sung vào sau nút trỏ bởi M một nút mới mà tr ường INFOR của nó
có giá trị lấy từ ô có địa chỉ là X. Thủ tục gồm các b ước sau :
Procedure INSERT(L,M,X)
1.{ Tạo nút mới}
new AVAIL
INFOR(new):=X
2. { Thực hiện bổ sung: Nếu danh sách rỗng thì bổ sung nút mới vào thành nút
đầu tiên. Nếu danh sách không rỗng thì bổ sung nút mới vào sau nút M }
If L=NULL then begin
L:=new
LINK(new):=NULL
end
else begin
LINK(new):=LINK(M)
LINK(M):=new
end
b. Thủ tục loại một nút ra khỏi danh sách
Cho danh sách móc nối đơn trỏ bởi Love, loại một nút trỏ bởi M ra khỏi danh
sách đó.
Procedure DELETE(L,M)
1. { Trường hựp danh sách rỗng}
If L=NULL then begin
Write(‘ danh sách rỗng ‘)
Return
end
2. {Nút trỏ bởi M là nút đầu tiên của danh sách }
If M=L then begin
L:=LINK(M)
M AVAIL
Return
end
3. {Tìm đến nút đứng trước nút M }
P:=l
While LINK(p)<> M do p:=LINK(p)
4. { Loại bỏ nút trỏ bới M}
LINK(p):=LINK(M)
5. { Đưa nút bị loại về danh sách chỗ trông }
M AVAIL
6. { Kết thúc}
Return
c. Ghép 2 danh sách nối đơn
Procedure Combie(p,q)
Cho 2 danh sách nối đơn lần lượt trỏ bởi p và q, thuật toán này ghép 2 danh
sách trở thành một danh sách và cho p trỏ tới. Thuật toán có các bước sau:
1. {Danh sách trỏ bởi q rỗng}
If q = null then Return
2. { Trường hợp danh sách trỏ bởi p rỗng }
If p = null then begin
p:=q
return
end
3. { Tìm đến nút cuối danh sách p }
p1:= p
While link(p1) # null do
P1:=link(p1)
4. { Ghép }
Link(p1):=q
5. Return
* Ưu điểm nhược của danh sách tuyến tính:
- Với danh sách tuyến tính động, trong quá trình xử lý
luôn có bố sung, loại bỏ thì tổ chức danh sách móc
nối là hợp lý, tận dụng được các vùng nhớ nằm rải rác
trong bộ nhớ.
- Chí có phần tử đầu tiên là truy nhập trực tiếp, các
phần tử khác phải truy nhập qua phần tử đứng trước
nó.
- Tốn bộ nhớ do phải lưu cả 2 trường infor và link ở
mỗi nút.
Câu hỏi thảo luận và bài tâp
1. Thế nào là cấu trúc ngăn xếp. Cho ví dụ.
2. Thế nào là Cấu trúc hàng đợi. Cho ví dụ.
3. Thế nào là danh sách móc nối. Ưu điểm của danh sách móc
nối.
4. Trình bày qui ước biểu diễn danh sách móc nối đơn.
5. Xét danh sách thuê bao điện thoại. Mỗi thuê bao coi như một
bản ghi có 3 trường: HT (họ tên), DC (địa chỉ ), SDT (số điện
thoại).
a. Viết các thủ tục: Tạo danh sách kiểu cấu trúc ngăn xếp, thực
hiện các phép bổ sung và loại bỏ phần tử.
b. Viết các thủ tục: Tạo danh sách kiểu cấu trúc hàng đợi, thực
hiện các phép bổ sung và loại bỏ phần tử.
c. Viết các thủ tục: Tạo danh sách móc nối đơn, thực hiện các
phép bổ sung và loại bỏ phần tử.
2. Danh sách nối vòng (Circularly linked list )
* Một cải tiến của danh sách nối đơn là danh sách nối vòng.
* Danh sách nối vòng là một danh sách nối đơn nhưng mối nối ở nút cuối cùng
không phải là mối nối không mà nó lại địa chỉ của nút đàu tiên của danh sách.

L
A B C

* Ưu nhược điểm của danh sách nối vòng:


- Danh sách nối vòng làm cho việc truy nhập vào các nút trong danh sách linh
hoạt hơn. Ta có thể truy nhập vào danh sách bắt đầu từ một nút nào cũng được,
không nhát thiết phải từ nút đầu tiên. Nút nào cũng có thể là nút đầu tiên và con
trỏ L trỏ vào nút nào cũng được.
- Nhược điểm của danh sách nối vòng là trong xử lý nếu không cẩn thận sẽ dẫn
tới một chu trình không kết thúc.
* Để khắc phục nhược điểm của danh sách nối vòng ta đưa thêm vào một nút đặc
biệt gọi là “nút đầu danh sách” (list head node ). Trường Infor của nút này không
chứa dữ liệu, con trỏ HEAD trỏ tới nút đầu danh sách này cho phép ta truy nhập
vào danh sách.

HEAD

A B C
Việc dùng thêm nút đầu danh sách đã làm cho danh sách luôn có ít nhất 1 nút
nên không bao giờ rỗng. Danh sách có 1 nút HEAD như sau:

HEAD

với qui ước là LINK(HEAD)=HEAD

* Các thủ tục bổ sung và loại bỏ nút trong danh sách nối vòng tương tự danh
sách nối đơn.
3. Danh sách nối kép (Double linked list)
3.1. Khái niệm
* Danh sách nối kép (liên kết kép) là danh sách mà ở mỗi nút có 2 con trỏ, một
trỏ tới nút đứng trước và một trỏ tới nút đứng sau nó.
* Quy cách của một nút như sau:
LPTR INFOR RPTR
LPTR : Con trỏ trỏ tới nút đứng trước
RPTR : Con trỏ trỏ tới nút đứng sau
INFOR : Trường thông tin.
R
L
A B C

* LPTR của nút cực trái và RPTR của nút cực phải là NULL.
* Để truy nhập vào danh sách cả 2 chiều ta phải dùng 2 con trỏ: Con trỏ L trỏ
vào nút cực trái, con trỏ R trỏ vào nút cực phải.
* Khi danh sách rỗng thì L=R=NULL
3.2. Các phép toan trên danh sách nối kép
a. Chèn một nút vào danh sách nối kép
Procedure Doubin(L,R,M,X)
Cho con trỏ L, R trỏ tới nút cực trái và nút cực phải của một danh sách nối
kép, M là con trỏ trỏ tới một nút trong danh sách, X là ô chứa dữ liệu chèn vào.
Thuật giải này bổ sung một nút mới có dữ liệu chứa ở ô X vào trước nút
trỏ bởi M. Thuật giải có các bước sau:
1. { Tạo nút mới }
new AVAIL
INFOR(new):=X
2. { Trường hợp danh sách rỗng }
If R= NULL then begin
LPTR(new):=RPTR(new):=NULL
L:=new ; R:=new
Return
end
3. { M trỏ tới nút cực trái }
If M=L then begin
LPTR(new):= NULL
RPTR(new):= M
LPTR(M):= new
L:=new
Return
end
4. { Bổ sung vào giữa trước M }
LPTR(new):=LPTR(M)
RPTR(new):=M
LPTR(M):=new
RPTR(LPTR(new)):=new
Return
b. Loại bỏ một nút ra khỏi danh sách nối kép
Procedure Boubdel (L, R, M)
Cho con trỏ L, R trỏ tới nút cực trái và nút cực phải của một
danh sách nối kép, M là con trỏ trỏ tới một nút trong danh sách
mà ta loại bỏ. Thuật giải này gồm các bước sau:
1. { Trường hợp danh sách rỗng }
If R=NULL then begin
Write(‘ danh sach rong ‘)
Return
end
2. { Loại bỏ }
Case
L= R : Begin { Danh sach chi co 1 nút M trỏ tới }
L:=Null
R:=Null
end
M=L: Begin { Nút cực trái bị loại }
L:=RPTR(L)
LPTR(L):=null
end
M=R: Begin { Nút cực phải bị loại }
R:=LPTR(R)
RPTR(R):=null
end
ELSE
RPTR(LPTR(M)):=RPTR(M)
LPTR(RPTR(M)):=LPTR(M)
Endcase
M AVAIL
Return
4. Cài đặt Stack và Queue bằng danh sách nối đơn
a. Cài đặt Stack bằng danh sách nối đơn
Dùng danh sách nối đơn trỏ bởi L để cài đặt Stack, vậy L là
đỉnh Stack. Các phép bổ sung và loại bỏ đều được thực hiện ở
đỉnh Stack, tức là đều được thực hiện ở đầu danh sách. Thực
hiện tương tự danh sách nối đơn trong mục 1.
a. Cài đặt Queue bằng danh sách nối đơn
Cài đặt Queue bằng danh sách nối đơn trỏ bởi L, vậy L là lối
trước (F).
Khi loại bỏ một phần tử khỏi Queue thì loại bỏ ở lối trước, do
đó L phải trỏ tới nút tiếp theo.
Khi bổ sung một phần tử vào Queue thì bổ sung ở lối sau, do
đó phải tìm đến nút cuối cùng rồi thêm một nút vào sau nút
cuối cùng. Thực hiện tương tự danh sách nối đơn trong mục 1.
Câu hỏi thảo luận và bài tập
1. Thế nào là danh sách nối vòng. Nêu ưu nhược điểm của nó.
2. Để khắc phục hạn chế của danh sách nối vòng người ta làm thế nào.
3. Thế nào là danh sách nối kép? Qui ước biểu diễn một nút của danh sách nối
kép.
4. Nêu ưu nhược điểm của danh sách nối kép.
5. Cài đặt Stack bằng danh sach nối đơn như thế nào. Cần chú ý gì khi thực
hiện các phép bổ sung, loại bỏ phần tử.
6. Cài đặt Queue bằng danh sach nối đơn như thế nào. Cần chú ý gì khi thực
hiện các phép bổ sung, loại bỏ phần tử.
7. Xét danh sách điểm học sinh. Mỗi học sinh coi như một bản ghi có 3 trường:
HT (họ tên), SBD (số báo danh), DIEM (điểm).
a. Viết các thủ tục: Tạo danh sách nối kép, thực hiện các phép bổ sung và loại
bỏ phần tử.
b. Viết các thủ tục: Tạo danh sách nối vòng, thực hiện các phép bổ sung và
loại bỏ phần tử.
c. Viết các thủ tục: Tạo danh sách kiểu cấu trúc ngăn xếp, thực hiện các phép
bổ sung và loại bỏ phần tử.
d. Viết các thủ tục: Tạo danh sách kiểu cấu trúc hàng đợi, thực hiện các phép
bổ sung và loại bỏ phần tử.
Chương 5: Cây (TREE)

1.Các khái niệm cơ bản


1.1. Định nghĩa cây
* Định nghĩa: Cây là một tập hợp hữu hạn các nút, trong đó có một nút đặc biệt
gọi là gốc (Root). Giữa các nút có một quan hệ phân cấp gọi là quan hệ cha con.
* Một cây không có nút nào gọi là cây rỗng (Null tree).
* Các ví dụ về cây:
Ví dụ 1: Mục lục của một chương được biểu diễn dạng cây.
Chương 6
6.1 666
6.2
6.2.1
6.2.2
6.3 6.1
6 6.2 6.3
6.3.1
6.3.2

6.2.1 6.2.2
6 6.3.1 6.3.2
Ví dụ 2: Biểu thức số học được biểu diễn dạng dạng cây.
x+y*(z-t)+u/v

66+

/
+

u v
x *

y -

z t
Ví dụ 3: Các tập bao nhau được biểu diễn dạng cây

Có các tập A,B,C,D,E,F


bao nhau A
E
B

C D
F

Các tập trên được biểu diễn dậng cây như sau:

66A
+

E/ F
B
+

C
x D
*
1.2. Các khái niệm
* Gốc (Root): Gốc là nút đặc biệt không có cha.
Ví dụ 3: A là gốc. B,E,F là gốc cây con của A.
A là cha của B,E,F. B,E,F là con của A.
* Cấp (Degree) : Số con của một nút gọi là cấp của nút đó.
Ví dụ 3 : A có cấp là 3. E, F có cấp là 0. B có cấp là 2.
* Lá (leaf): Nút có cấp bằng không gọi là lá hay nút tận cùng.
Ví dụ 3 : C,D,E,F là lá.
* Nút nhánh (branch node) : Nút không là lá được gọi là nút
nhánh.
Ví dụ 3 : B là nút nhánh.
* Mức (Level): Gốc cây có mức là 1. Nếu nút cha có mức là i thì
nút con có mức là i+1.
Ví dụ 3 : A có mức là 1.
B, E, F có mức là 2
C, D có mức là 3
* Chiều cao của cây (Height) hay chiều sâu của cây (Depth) : Là
số mức lớn nhất của của nút có trên cây.
Ví dụ 1 : Cây có chiều cao là 3
Ví dụ 2 : Cây có chiều cao là 5
Ví dụ 3 : Cây có chiều cao là 3
* Đường đi (Path) : Nếu n1, n2, ..., nk là các dãy nút mà ni là là
cha của ni+1 (1≤i<k) thì dãy đó gọi là đường đi từ n1 đến nk .
Độ dài của đường đi bằng số nút trừ đi 1.
Ví dụ 3 : Đường đi từ A đến C cố độ dài là 3-1=2
Đường đi từ A đến E cố độ dài là 1.
* Nếu thứ tự các cây con của một nút được coi trọng thì cây đang
xét là cây có thứ tự, ngược lại là cây không có thứ tự.
Thường thì thứ tự các cây con của một nút được đặt từ trái
sang phải.
Hai cây con sau đây là 2 cây con có thứ tự khác nhau.
A
A

B C C B

Đối với cây, ngoài quan hệ cha con người ta còn mở rộng phỏng theo quan
hệ trong gia tộc.
Rừng : Nếu có một tập hữu hạn các cây phân biệt thì ta gọi tập đó là rừng.
2. Cây nhị phân
2.1. Định nghĩa và tính chất
2.1.1. Định nghĩa cây nhị phân
* Định nghĩa: Cây nhị phân là dạng đặc biệt của cấu trúc cây, đó là mọi
nút trên cây chỉ có tối đa là 2 con.
* Đối với cây con của một nút người ta phân biệt cây con trái và cây con
phải. Như vậy cây nhị phân là cây có thứ tự.

Ví dụ: Hai cây sau đây là khác nhau

A
A

B B

D C D
C
C

E E
* Cây nhị phân suy biến có dạng một danh sách tuyến tính.

A
A

B B

C DC

D E

a b
A
A

B B

C C

D
D

c d
Các cây a, b, c, d là các cây nhị phân suy biến.
a là cây lệch trái. b là cây lẹch phải, c, d là cây zíc zắc.
* Cây nhị phân hoàn chỉnh : là cây nhị phân mà các nút ở các mức trừ mức cuối
đều đạt tối đa.
Ví dụ cây sau là cây nhị phân hoàn chỉnh :

B C

E F G
D

H I
* Cây nhị phân đầy đủ : Là cây nhị phân có các nút tối đa ở mọi mức.
Ví dụ cây sau là cây nhị phân đầy đủ :

B C

E F G
D
2.1.2. Tính chất
a- Số lượng tối đa các nút ở mức i trên 1 cây nhị phân là 2i-1 (i≥1)
b- Số lượng tối đa các nút trên 1 cây nhị phân có chiều cao h là 2h -1

2.2. Lưu trữ cây nhị phân


2.2.1. Lưu trữ kế tiếp
* Với cây nhị phân đầy đủ, ta đánh số các nút từ 1 trở đi, hết mức này đến mức
khác, từ trái qua phải.
Dùng mảng V lưu trữ cây nhị phân , nút thứ i của cây được lưu trữ ở phần
tử V(i).
Ví dụ với cây đày đủ ở trên được lưu trữ như sau:

V(1) V(2) V(3) V(4) V(5) V(6) V(7)


A B C D E F G
* Với cách lưu trữ này khi biết địa chỉ của nút cha sẽ tính được địa chỉ của nút
con và ngược lại.
* Nếu cây không đầy đủ ta phải thêm các nút trống vào để đươc cây nhị phân
đầy đủ, sau đó lưu trữ cây đầy đủ đã tạo ra.

Ví dụ có cây nhị phân sau:

D
C
Ta tạo cây nhị phân đầy đủ như sau:

B Φ

D Φ Φ
C

Kí hiệu Φ nút trống. Được lưu trữ như sau:


V(1) V(2) V(3) V(4) V(5) V(6) V(7)
A B Φ C D Φ Φ
2.2.2. Lưu trữ bằng danh sách móc mối
Trong cách lưu trữ này , mỗi nút ứng với một phần tử nhớ có quy cách
như sau:
LPTR INFOR RPTR
LPTR : Con trỏ trỏ tới cây con trái của nút đó
RPTR : Con trỏ trỏ tới cây con phải của nút đó
INFOR : Trường thông tin.
Ví dụ cây nhị phân sau đây

B C

D E
2.3. Duyệt cây nhị phân
* Phép duyệt cây là phép thăm (visit) các nút một cách hệ thống, sao
cho mỗi nút chỉ được thăm một lần.
* Một nút với 2 con, ta có 3 cách duyệt , các cách duyệt được định
nghĩa đệ quy như sau:
a) Duyệt theo thứ tự trước ( preorder traversal)
- Thăm gốc
- Duyệt cây con trái theo thứ tự trước
- Duyệt cây con phải theo thứ tự trước
b) Duyệt theo thứ tự giữa ( inorder traversal)
- Duyệt cây con trái theo thứ tự giữa
- Thăm gốc
- Duyệt cây con phải theo thứ tự giữa
c) Duyệt theo thứ tự sau ( postorder traversal)
- Duyệt cây con trái theo thứ tự sau
- Duyệt cây con phải theo thứ tự sau
- Thăm gốc
Ví dụ với cây nhị phân sau:

B C

E F G
D

- Duyệt theo thứ tự trước: A B D E C F G


- Duyệt theo thứ tự giữa: D B E A F C G
- Duyệt theo thứ tự sau: D E B F G C A
* Các thủ tục duyệt cây nhị phân đều được viết ở dạng đệ qui
T là con trỏ trỏ tới gốc cây dạng danh sách móc nối. Phép thăm
thể hiện ở in giá trị trường Infor của nút đó.
+ Duyệt cây theo thứ tự trước:
Procedure Preorder(T)
If T = Null then Begin
Write(‘ Cay rong’)
Return
End
Else Begin
Write(Infor(T))
Call Preorder(Lptr(T))
Call Preorder(Rptr(T))
End;
Return
+ Duyệt cây theo thứ tự giữa:
Procedure Inorder(T)
If T = Null then Begin
Write(‘ Cay rong’)
Return
End
Else Begin
Call Inorder(Lptr(T))
Write(Infor(T))
Call Inorder(Rptr(T))
End;
Return
+ Duyệt cây theo thứ tự sau:
Procedure Postorder(T)
If T = Null then Begin
Write(‘ Cay rong’)
Return
End
Else Begin
Call Postorder(Lptr(T))
Call Postorder(Rptr(T))
Write(Infor(T))
End;
Return
Câu hỏi thảo luận và bài tập

1- Biểu diễn biểu thức sau ở dạng cây nhị phân.


(a+b/c)*(d-e*f)
-Lưu trữ cây nhị phân biểu diễn biểu thức ở dạng lưu trữ
kế tiếp, lưu trữ móc nối.
-Duyệt cây nhị phân đó theo cả 3 cách.
2- Cho cây nhị phân sau:

B C

F G
D

E G

- Hãy trữ kế tiếp cây nhị phân trên.


- Hãy trữ móc nối cây nhị phân trên.
- Duyệt cây nhị phân đó theo 3 cách.
3- Thế nào là cây. Cho ví dụ.
4- Thế nào là cây nhị phân. Cho ví dụ.
5- Thế nào là cây nhị phân hoàn chỉnh, cây nhị phân đầy
đủ. Cho các ví dụ.
6- Cài đặt cây nhị phân bằng lưu trữ kế tiếp. Cho ví dụ.
7- Cài đặt cây nhị phân bằng danh sách móc nối. Cho ví
dụ.
8- Duyệt cây nhị phân theo 3 cách. Cho ví dụ.
3. Cây tổng quát
3.1. Biểu diễn cây tổng quát
Nếu biểu diễn cây tổng quát bằng danh sách móc nối thì một
nút có bao nhiêu nhánh sẽ có bấy nhiêu trường móc nối, cách
biểu diễn này phức tạp. Nếu biểu diễn cây bằng mảng thì quá
trình xử lý cũng rất phức tạp.
Để đơn giản ta biểu diễn cây tổng quát bằng cây nhị phân.
Ta nhận thấy với bất kỳ nút nào tren cây tổng quát nếu có thì
chỉ có:
- Một nút con cực trái ( con cả)
- Một nút en kề cận phải
Khi đó cây nhị phân biểu diễn cây tổng quát theo 2 quan hệ
nêu trên được gọi là cây nhị phân tương đương.
Ví dụ : Cho cây tổng quát như sau :

66A
+

E/ F
B
+

C
x D
* G

Trong cây trên nút B có con cực trái là C, em kề cận phải là E.


Nút F có con cực trái là G, em kề cận phải là không có.
* Mỗi nút của cây tổng quát có có qui cách như sau :
CHILD INFOR SIBLING
- CHILD : con trỏ trỏ tới nút con cực trái.
- SIBLING : con trỏ trỏ tới nút em kề cận.
- INFOR : trường thông tin.
* Có thể biểu diễn cây tổng quát bằng cây nhị phân tương đương như sau:
- Mối nối phải của gốc là mối nối không vì gốc không có em kề cận phải.
- Các nút khác có thể có đầy đủ cả 2 thành phần hoặc không đủ.
Ví dụ cây tổng quát trên được biểu diễn bằng cây nhị phân tương đương
như sau:
A

C E
F

F
D

Từ cây nhị phân ta có thể biểu diễn bằng danh sách móc nối đơn như phần
trên đã xét.
3.2. Duyệt cây tổng quát
a) Duyệt theo thứ tự trước: Cho một cây T.
- Nếu T rỗng thì không làm gì.
- Nếu T không rỗng thì :
1- Thăm gốc của T
2- Duyệt cây con thứ nhất T1 của gốc theo thứ tự trước.
3- Duyệt các cây con còn lại T2 , T3 , .., Tk của gốc T theo thứ
tự trước.
Ví dụ trên duyệt theo thứ tự trước: A B C D E F G
b) Duyệt theo thứ tự giữa: Cho một cây T.
- Nếu T rỗng thì không làm gì.
- Nếu T không rỗng thì :
1- Duyệt cây con thứ nhất T1 của gốc theo thứ tự giữa.
2- Thăm gốc của T
3- Duyệt các cây con còn lại T2 , T3 , .., Tk của gốc T theo thứ
tự giữa.
Ví dụ trên duyệt theo thứ tự giữa: C B D A E G F
c) Duyệt theo thứ tự sau: Cho một cây T.
- Nếu T rỗng thì không làm gì.
- Nếu T không rỗng thì :
1- Duyệt cây con thứ nhất T1 của gốc theo thứ tự
sau.
2- Duyệt các cây con còn lại T2 , T3 , .., Tk của
gốc T theo thứ tự sau.
3- Thăm gốc của T
Ví dụ trên duyệt theo thứ tự sau: C D B E G F A
4. Áp dụng
4.1. Cây biểu diễn biểu thức
a) Cây biểu diễn biểu thức
Như ta đã biết biểu thức số học với các phép toán 2 ngôi như + - * / có thể
biểu diễn bởi cây nhị phân có các nút với quy cách như sau:
LPTR TYPE RPTR
- LPTR, RPTR : con trỏ trái, con trỏ phải
- TYPE : chỉ phép toán ứng với nút đó:
+ Nếu không phải nút lá thì giá trị của TYPE sẽ là 1, 2, 3, 4, 5 ứng
với các phép +, - , *, /, đổi dấu.
+ Nếu là nút lá thì TYPE có giá trị là 0, để chỉ biến hoặc hằng tương
ứng với nút đó thì RPTR trỏ tới địa chỉ ô chứa biến hoặc hằng, LPTR = Null.
Ta kí hiệu Value(F) là giá trị ô F
E là con trỏ trỏ tới gốc cây.
F là con trỏ phụ.
Ví dụ : Biểu diễn biểu thức a*b+c/2 bằng cây nhị phân sau

* /

c 2
a b
4.2. Định giá trị biểu thức
Thuật giải định giá trị biểu thức biểu diễn bởi cây nhị phân có
gốc E. Thuật giải này được viết dưới dạng đệ quy:
Function EVAL(E)
Case
TYPE(E)=0: Begin F:=RPTR(E)
Return(Value(F))
End
TYPE(E)=1: Return ( EVAL(LPTR(E))+EVAL(RPTR(E)))
TYPE(E)=2: Return ( EVAL(LPTR(E))-EVAL(RPTR(E)))
TYPE(E)=3: Return ( EVAL(LPTR(E))*EVAL(RPTR(E)))
TYPE(E)=4: Return ( EVAL(LPTR(E))/EVAL(RPTR(E)))
TYPE(E)=5: Return ( - EVAL(RPTR(E)))
Else Return(00)
End case
4.3. Bài toán xác định 2 biểu thức tương đương
Cho 2 cây nhị phân biểu diễn biểu thức trỏ bởi A, B. Hàm xác định 2 biểu thức tương
đương Similar cho giá trị True nếu 2 biểu thức tương đương, ngước lại cho giá trị False.
Function Similar(A,B)
Bước 1 { Kiểm tra loại gốc cây}
If TYPE(A)# TYPE(B) then Return(False)
Bước 2 { Kiểm tra tính tương đương }
Case
TYPE(A)=0 : If Value(RPTR(A)) # Value(RPTR(B)) then Return(False)
Else Return(True)
TYPE(A)=1 OR TYPE(A)=3 : { Phép + hoặc * }
Begin
Return (Similar( LPTR(A), LPTR(B)) AND
Similar(RPTR(A), RPTR(B)) OR
Similar( LPTR(A), RPTR(B)) AND
Similar(RPTR(A), LPTR(B)))
TYPE(A)=2 OR TYPE(A)=4 : { Phép - hoặc / }
Begin
Return (Similar( LPTR(A), LPTR(B)) AND
Similar(RPTR(A), RPTR(B)))
TYPE(A)=5 : { Phép đảo dấu }
Return (Similar(RPTR(A), RPTR(B)))
End Case
5. Cây nhị phân tìm kiếm (Binary search tree)
5.1. Định nghĩa cây nhị phân tìm kiếm
* Cây nhị phân tìm kiếm ứng với n khoá k1, k2, ..., kn là
một cây nhị phân mà mỗi nút của nó đều được định
danh bởi một khoá nào đó trong các khoá đã cho. Đối
với mọi nút trên cây tính chất sau đây luôn được thoả
mãn:
- Mọi khoá thuộc cây con trái của nút đó đều nhỏ hơn
khoá ứng với nút đó.
- Mọi khoá thuộc cây con phải của nút đó đều lớn hơn
khoá ứng với nút đó.
Chú ý : Khoá là số thì so sánh số bình thường, Khoá là
chữ thì ta so sánh xâu kí tự.
Ví dụ có các cây nhị phân tìm kiếm sau:

35

25 40

38 45
20 28
d

b h

g i
a c

b
2. Giải thuật tìm kiếm

* Đối với một cây nhị phân để tìm kiếm xem một khoá x nào đó có
trên cây đó không? Ta có thể thực hiện như sau:
So sánh x với khoá ở gốc và một trong 4 tình huống sau đây sẽ
xuất hiện:
1- Không có gốc cây ( cây rỗng): X không có trên cây, phép tìm
kiếm không thoả mãn.
2- X trùng với khoá ở gốc: Phép tìm kiếm được thoả mãn.
3- X nhỏ hơn khoá ở gốc: Tìm kiếm được thực hiện tiếp tục
bằng cách xét cây con trái của gốc với cách làm tương tự.
4- X lớn hơn khoá ở gốc: Tìm kiếm được thực hiện tiếp tục
bằng cách xét cây con phải của gốc với cách làm tương tự.
Ví dụ:
- Tìm x=28 trên cây a: So x và 35, x<35 nên ta tìm trên cây con trái của 35
X>25 nên lại tìm trong cây con phải. So sánh ta có x=cây con phải cũng là 28
nên phép tìm kiếm được thoả mãn.
- Nếu tìm x=m trên cây b thì phép tìm kiếm không thoả mãn.
* Nếu phép tìm kiếm không thoả mãn ta bổ sung luôn x vào cây nhị phân tìm
kiếm. Phép bổ sung không ảnh hưởng đến cây, đến vị trí các khoá trên cây.
* Mỗi nút của cây nhị phân tìm kiếm có dạng sau:
LPTR KEY INFOR RPTR
LPTR: con trỏ trỏ tới gốc cây con trái.
RPTR: con trỏ trỏ tới gốc cây con phải.
KEY: khoá của các nút.
INFOR: thông tin.
* Thuật giải tìm kiếm trên cây nhị phân tìm kiếm như sau:
Cho cây nhị phân tìm kiếm có gốc được trỏ bởi T. Tìm nút trên cây có khoá
bằng x.
Nếu tìm kiếm được thoả thì con trỏ trỏ tới nút đó, giá trị trả về là q, đó là địa
chỉ của nút ta tìm được ( con trỏ q trỏ tới nút đó)
Nếu không tìm được ta bổ sung x vào cây T, con trỏ q trỏ vào nút mới đó.
Function BST(T,X)
1. {Khởi tạo con trỏ}
P=Null
q= T
2. { Tìm kiếm }
While q# Null Do
Casse
X<key(q): p:=q;q:=LPTR(q)
X=key(q): Return(q)
X>key(q): p:=q; q:=RPTR(q)
End Case
3. {Bo sung}
q ⇐ AVAIL
Key(q):=X
LPTR(q):=RPTR(q):= Null
Case
T=Null: T:=q
X<key(p): LPTR(p):=q
Else: RPTR(p):=q
End case
Writeln(‘ khong tim thấy, đã bổ sung’)
Return(q)
Câu hỏi thảo luận và bài tập
1. Trình bày cách biểu diễn cây tổng quát bằng cây nhị phân tương đương.
2. Thế nào là cây nhị phân tìm kiếm.
3. Nêu thuật toán tìm kiếm nhị phân.
4. Cho cây như hình vẽ sau:

66A
+

E/ F
B
+

C
x D
* G H
a. Duyệt cây trên theo 3 cách.
b. Biểu diễn thành cây nhị phân tương đương.
5. - Viết chương trình tạo cây biểu diễn biểu thức sau:
a/b - c*d
- Tinh giá trị của biểu thức trên với các giá trị a,b,c,d
được nhập vào từ bàn phím.
Chương 6: Đồ thị
1. Các khái niệm
1.1. Địng nghĩa đồ thị
Đồ thị G(V,E) bao gồm một tập hữu hạn V các đỉnh
(hay nút) và một tập hữu hạn E các cặp đỉnh mà ta
gọi là cung ( hay cạnh).
Ví dụ 1: Một mạng gồm các máy tính và các kênh điện
thoại nối các máy tính này là một đồ thị.
Ví dụ 2: Một mạng gồm các thành phố, thị xã và các
đường bộ nối các thành phố, thị xã là một đồ thị.
1.2. Định nghĩa đồ thị vô hướng
Đồ thị vô hướng G=(V,E) bao gồm V là tập các đỉnh và
E là tập các cặp đỉnh không có thứ tự gọi là các cung.
Ví dụ:
1

2 3

4 5

1.3. Định nghĩa đồ thị có hướng


Đồ thị có hướng G=(V,E) bao gồm V là tập các đỉnh và E là tập các cặp đỉnh có
thứ tự gọi là các cung.
Ví dụ:
1

2 3

4 5
* Nếu (v1, v2) là một cung trong tập E(G) thì v1 và v2 gọi là lân
cận của nhau.
Ví dụ trên 1,2 là lân cân, 1,3 là lân cận.
* Một đường đi từ đỉnh u đến đỉnh v trong đồ thị là một dãy các
đỉnh
u=x0, x1, ..., xn-1, xn=v mà dãy các cạnh (x0, x1), (x1, x2), ...,
(xn-1, xn) là các cung thuộc E(G) .
* Số lượng cung trên đường đi gọi là độ dài của đường đi.
Ví dụ đường đi từ 1 đến 4 có độ dài là 2.
* Đường đi đơn: Là đường đi mà mọi đỉnh trên đó, trừ đỉnh đầu và
đỉnh cuối đều khác nhau.
* Một chu trình là một đường đi đơn mà đỉnh đầu và đỉnh cuối
trùng nhau.
Ví dụ: 1→ 3 → 5→ 4→1
* Trong đồ thị G hai đỉnh u và v gọi là liên thông nếu có một đường đi từ u đến
v.
Ví dụ: 1, 4 là liên thông. 1, 5 là liên thông.
* Đồ thị G gọi là liên thông nếu với mọi cặp đỉnh phân biệt vi, vj trong V(G) đều
có một đường đi từ vi với vj.
Ví dụ: Đồ thị sau là đồ thị vô hướng liên thông
1

2 3

4 5
Ví dụ: Đồ thị sau là đồ thị vô hướng không liên thông

2 3 6 7

4 5

Ví dụ: Đồ thị sau là đồ thị có hướng liên thông

2 3

4 5
1

2 3

4 5

* Đồ thị mà mỗi cung gắn với 1 giá trị nào đó được gọi là đồ thị có trọng số.
Ví dụ: mạng lưới giao thông, mỗi tuyến đường có độ dài, nên đó là đồ thị có
trọng số.
* Đồ thị vô hướng G=(V,E), bậc của đỉnh v là số cung liền thuộc với đỉnh đó.
Kí hiệu là: deg(v)
Ví dụ: Đồ thị vô hương sau
deg(1)=2 1
deg(2)=4
2 3

4 5

2. Biểu diễn đồ thị


2.1. Biểu diễn bằng ma trận lân cận ( ma trận kề )
Xét đồ thị G(V,E), V gồm n đỉnh (n>=1), giả sử các đỉnh được đánh số thứ tự
theo một quy định nào đó. Ma trận lân cận A biểu diễn G là một ma trận vuông
kích thước nxn. Các phần tử của ma trận có giá trị 0 hoặc 1.
Ai j = 1 khi tồn tại cung (vi, vj ) trong E.
Ai j = 0 khi không tồn tại cung (vi, vj ) trong E.
* Nếu đồ thị vô hướng thì ma trận A đối xứng qua đường chéo chính.
Ví dụ 1: Đồ thị vô hương sau

1 2

3 4
Ma trận lân cận A là ma trân vuông cấp 4 có giá trị như sau :
1 2 3 4
1 0 1 1 1
2 1 0 0 1
3 1 0 0 1
4 1 1 1 0
Ví dụ 2: Đồ thị đồ thị có hướng sau

2 3

4 5

Ma trận lân cận A là ma trân vuông cấp 5 có giá trị như sau :
1 2 3 4 5
1 0 0 1 0 0
2 1 0 0 0 0
3 0 0 0 0 1
4 0 1 0 0 0
5 0 0 0 1 0
* Với đồ thị có trọng số ma trận lân cận thay giá trị 1 bằng trọng số tương ứng
của cung đó.
2.2. Biểu diễn bằng danh sách lân cận ( danh sách kề )
Trong cách biểu diễn này n hàng của ma trận thay bằng n danh sách móc nối.
Mỗi đỉnh của G có một danh sách tương ứng. Các nút trong danh sách i biểu
diễn các đỉnh lân cận của nút i.
Mỗi nút trong danh sách có chứa 2 trường :

VERTEX LINK
VERTEX: Là thông tin của đỉnh lân cận đỉnh i.
LINK : Là địa chỉ nút tiếp theo.
Ví dụ 1 ở trên được biểu diễn bằng danh sách lân cận kề sau:
V[1] 2 3 4

V[2] 1 4

V[3] 1 4

V[4] 1 2 3

Với V[1], V[2], V[3], V[4] là các phần tử của véc tơ V chứa các địa chỉ đàu
danh sách (trỏ tới các danh sách).
Ví dụ 2 ở trên được biểu diễn bằng danh sách lân cận kề sau:
V[1] 3

V[2] 1

V[3] 5

V[4] 2

V[5] 1

Với V[1], V[2], V[3], V[4], V[5] là các phần tử của véc tơ V chứa các địa chỉ
đàu danh sách (trỏ tới các danh sách).
* Mỗi danh sách có nút đầu danh sách, các nút này được tổ chức lưu trữ kế tiếp
(mảng) để truy nhập được nhanh.
* Đồ thị vô hướng có n đỉnh, e cung thì cần n nút đầu danh sách và 2e nút danh
sách.
* Đồ thị vô hướng có n đỉnh, e cung thì cần n nút đầu danh sách và e nút danh
sách.
3. Phép duyệt đồ thị
* Xét đồ thị vô hướng G(V,E) và một đỉnh v∈V. Ta cần thăm tất cả các
đỉnh của G mà có thể “ với tới” từ đỉnh v ( nghĩa là đồ thị liên thông).
Có 2 cách duyệt đồ thị:
- Phép tìm kiếm theo chiều sâu ( Depth first search )
- Phép tìm kiếm theo chiều rộng (Breadth first search )
3.1. Phép tìm kiếm theo chiều sâu ( Depth first search )
Xét đồ thị vô hướng. Phép tìm kiếm theo chiều sâu thể hiện như sau:
- Đỉnh xuất phát v được thăm.
- Tiếp theo đó ta thăm đỉnh w là đỉnh chưa được thăm và là lân cận của
v. Phép tìm kiếm theo chiều sâu xuất phát từ w lại được thực hiện.
Trong trường hợp đỉnh u đã được thăm mà mọi đỉnh lân cận của nó đã
được thăm rồi thì ta quay lại đỉnh cuối cùng vừa được thăm ( mà
đỉnh này còn đỉnh w là lân cận của nó chưa được thăm) và phép tìm
kiếm theo chiều sâu xuất phát từ w lại được thực hiện.
Ví dụ 1: Cho đồ thị vô hướng sau:

+
66v1

v3
/
+
v2

v4
x v5
*
v6
v7

v8
Phép duyệt theo chiều sâu đi theo trình tự sau:
v1 → v2 → v4 → v8 → v5 → v8 → v6 → v3 → v7
* Thủ tục phép duyệt theo chiều sâu như sau:
Cho một đồ thị G(V,E) vô hướng có n đỉnh và véc tơ Visited(n) gồm n phần
tử, ban đầu véc tơ này có giá trị =0. Thuật giải này thực hiện thăm mọi đỉnh
“ với tới được “ từ đỉnh v.
Procedure DFS(v)
1. Visited(v):=1 { đánh dấu v được thăm }
2. FOR mỗi đỉnh w lân cận với v DO
If Visited(v):=0 then CALL DFS(w);
3. Return
* Đánh giá thuật toán:
+ Trường hợp biểu diễn đồ thị dùng danh sách móc nối: G có e cung, mỗi
nút với tới 1 lần, nên thời gian tìm kiếm là O(e).
+ Trường hợp biểu diễn đồ thị dùng ma trận lân cận : thì thời gian xác
định mọi điểm lân cận của v là O(n). Có n đỉnh nên thời gian tìm kiếm là
O(n2).
3.1. Phép tìm kiếm theo chiều rộng (Breadth first search )
Xét đồ thị vô hướng. Phép tìm kiếm theo chiều rộng thể hiện
như sau:
- Đỉnh xuất phát v được thăm.
- Tiếp theo các đỉnh chưa được thăm mà là lân cận của v sẽ
được thăm, rồi đến các đỉnh chưa được thăm là lân cận làn
lượt của các đỉnh này và cứ tương tự như vậy.
Ví dụ 1 ở trên: Phép duyệt theo chiều rông đi theo trình tự sau:
v1 → v2 → v3 → v4 → v5 → v6 → v7 → v8
* Thủ tục phép duyệt theo chiều sâu như sau:
Cho một đồ thị G(V,E) vô hướng có n đỉnh và véc tơ Visited(n)
gồm n phần tử, ban đầu véc tơ này có giá trị =0. Thuật giải này
thực hiện thăm mọi đỉnh “ với tới được “ từ đỉnh v. Bắt đầu từ
đỉnh v. Mọi đỉnh i được thăm đánh dấu bằng Visited(i):=1.
Dùng hàng đợi Q có kích thước n; F, R là lối trước và lối sau của
hàng đợi. Khi thăm 1 đình thì loại bỏ khỏi hàng đợi; khi chưa
thăm thì bổ sung vào hàng đợi
Procedure BFS(v)
1. Visited(v):=1 { đánh dấu v được thăm }
2. Khởi tạo hàng đợi Q với v được đưa vào.
3. While Q không rỗng DO
Begin
Call CQDELETE(v,Q) { loại bỏ v ra khỏi Q}
FOR mỗi đỉnh w lân cận với v DO
Begin
If Visited(w)=0 then
Begin
CALL CQINSERT(w,Q); { Bổ sung w vào Q}
Visited(w):=1
End
End
End
4. Return
* Đánh giá giải thuật: Vòng lặp While lặp lại n lần .
- Nếu biểu diễn đồ thị bằng ma trận lân cận thì thời gian thực
hiện là O(n2).
- Nếu biểu diễn đồ thị bằng danh sách lân cận thì thời gian thực
hiện là O(e).
4. Cây khung và cây khung với giá trị cực tiểu
4.1. Cây khung
* Nếu G là đồ thị liên thông thì phép tìm kiếm theo chiều sâu hoặc
theo chiều rộng xuất phát từ 1 đỉnh thăm mọi đỉnh. Như vậy các
cung trong G phân thành 2 tập:
- Tập T chứa các cung đã được duyệt qua.
- Tập b gồm các cung còn lại.
* Tất cả các cung và các đỉnh trong T sẽ tạo thành một cây con
bao gồm mọi đỉnh của G. Cây con như vậy gọi là cây khung
của G.
Ví dụ 1: Cho đồ thị sau
1 2

3 4
Các cây khung của nó là:
* Phép duyệt cây theo chiều sâu (DFS) cho cây khung theo chiều sâu.
* Phép duyệt theo chiều rộng cho cây khung theo chiều rộng.
Ví dụ 2: cho đồ thị sau

66v1
+

v2
v3
/
+

v4
x v5
* v6
v7

v8
+ Cây khung theo chiều sâu:

66v1
+

v3
/
+
v2

v4
x v5
* v6
v7

v8
+ Cây khung theo chiều rộng:

66v1
+

v3
/
+
v2

v4
x v5
*
v6
v7

v8
4.2. Cây khung với giá trị cực tiểu

* Bài toán: Xác định cây khung với giá trị cực tiểu của đồ thị liên
thông có trọng số.
Gí trị của cây khung là tổng các trọng số ứng với các cạnh của
cây khung.
* Có nhiều giải thuật xác định cây khung với giá trị cực tiểu nhưng
trong phần này ta chỉ xét giải thuật Kruskal. Với giải thuật này
cây khung T sẽ được xây dựng dần từng cung một. Các cung
đưa vào T thoả mãn:
- Cung có giá trị cực tiểu trong các cung còn lại.
- Không tạo ra chu trình với các cung đã có của T.
* Giải thuật Kruskal được viết như sau:
1. T=Φ { T rỗng
2. While T chứa ít hơn (n-1) cung Do
3. Begin Chọn 1 cung (v,w) từ E có giá trị nhỏ nhất.
4. Loại (v,w) ra khỏi E
5. If (v,w) không tạo nên chu trình trong T Then
đưa (v,w) vào T.
End;
6. Return
Ví dụ: Cho đồ thị sau:

66v1
+
5 12

v3
/
+
v2
8
6 15 20
v4
x v5
*

22 3
v6
Cây khung với giá trị cực tiểu :

66v1
+
5 12

v2 v3
/
+
8
6
v4
x v5
*

3
v6
* Đánh giá giải thuật:
Thời gian thực hiện giải thuật xác định qua thực hiện
bước 3 và 4.
Trường hợp xấu nhất sẽ là O(e.log e) trong đó e là số
cung của đồ thị G.
5. Bài toán tìm đường đi ngắn nhất
( Bài toán một nguồn mọi đích)
( Single source all destination )
* Cho đồ thị có hướng G(V,E), một hàm trọng số w(e)
cho các cung e của G và một đỉnh nguồn v0 .
Bài toán đặt ra là: Xác định đường đi ngắn nhất từ v0
đến mọi đỉnh còn lại của G ( độ dài đường đi là tổng
các trọng số trên các cung đường đi đó và các trọng
số đều dương )
Ví dụ: Cho đồ thị có hướng sau
45
66+
v0 50 v1x 10 v4

10 20 15 20 35 /
30

v2 15 3
v3 v5
x
Xuất phát từ V0, các đường đi ngắn nhất từ v0 tới v1,v2,v3,v4 sắp theo thứ tự
tăng dần ( không có đường đi từ vo đến v5):
1, v0,v2 độ dài 10
2, v0,v2,v3 độ dài 25
1, v0,v2,v3,v1 độ dài 45
1, v0,v4 độ dài 45
* Gọi S là tập các đỉnh kể cả v0 mà đường đi ngắn nhất
xác lập.
Đối với 1 đỉnh w ∈ S, gọi Dist(w) là độ dài của đường
đi ngắn nhất từ v0 qua các đỉnh trong S và kết thúc ở
w thì sẽ có một số nhận xét sau:
1. Nếu đường đi ngắn nhất tới w thì đường đi đó bắt
đầu từ v0 kết thúc ở w và chỉ đi qua những đỉnh thuộc
S.
2. Đích của đường đi sinh ra tiếp theo phải là một đỉnh
w nào đó ∉ S mà có Dist(w) ngắn nhất so với mọi đỉnh
∉S.
3. Nếu đã chọn được một đỉnh w như trong nhận xét 2
ở trên và sinh ra một đường đi ngắn nhất từ v0 đến w
thì w sẽ trở thành 1 phần tử của S.
* Dưa trên các quan điểm như các nhận xét nêu trên Diskstra đưa ra giải thuật
tìm đường đi ngắn nhất như sau:
- Giả thiết n đỉnh của G được đánh số từ 1 tới n.
- Tập S được thể hiện bằng véc tơ bít: S[i] = 0 nếu đỉnh i ∉ S
S[i] = 1 nếu đỉnh i ∈S
- Độ dài trọng số biểu diễn bằng ma trận lân cận Cost:
Cost[i,j] là trọng số cung (i,j)
Cost[i,j] = + ∞ nếu cung (i,j) không có.
Cost[i,j] = 0 nếu i=j
Ví dụ trên thì ma trận lân cân Cost như sau:
v0 v1 v2 v3 v4 v5
v0 0 50 10 +∞ 45 +∞
v1 + ∞ 0 15 +∞ 10 +∞
v2 20 +∞ 0 15 +∞ +∞
v3 + ∞ 20 +∞ 0 35 +∞
v4 + ∞ +∞ +∞ 30 0 +∞
v5 + ∞ +∞ +∞ 3 +∞ 0
* Giải thuật:
Procedure Shortest_Path(v,Cost,Dist,n)
{ Dist(j) 1<= j <=n là độ dài đường đi ngắn nhất từ v đến j trong đồ thị có
hướng G có n đỉn, Dist(v)=0. G được biểu diễn bởi ma trận lân cận Cost có
kích thước n x n. }
1. For i:=1 To n Do Begin
S[i]:=0; Dist[i]:= Cost[v,i];
End;
2. S[v]:=1; Dist[v]:=0; k:=1; { đưa v vào S }
3. While k<n { xác định n-1 đường đi từ đỉnh v }
4. Begin
Chọn u sao cho Dist[u]= min(Dist[i])) với S[i]=0;
5. S[u]:=1; k:=k+1; { đưa u vào S}
6. For mọi w với S[w]=0 Do
7. Dist[w]:= min(Dist[w], Dist[u]+Cost[u,w]) { tính lại khoảng cách theo
đường đi ngắn nhất }
End;
8. Return
Bài tập và thảo luận
1. Nêu khái niệm đồ thị, đồ thị vô hướng, đồ thị có hướng, đường
đi, cây khung, cây khung với gía trị cực tiểu.
2. Cho đồ thị sau đây.
a- Hãy biễu diễn đồ thị bằng ma trận lân cận, bằng danh sách
lân cận
b- Duyệt đồ thị theo chiều sâu, duyệt đồ thị theo chiều rộng.
c- Tìm cây khung theo chiều sâu, cây khung theo chiều rộng.
d-Tìm cây khung với giá trị cực tiểu.
e- Viết chương trình trong Pascal tìm cây khung với giá trị cực
tiểu.
+
66v1
16 12

v3
/
+
v2
2
4 15 25 20
v4
x v5
*

22 3
v7
v6
10
3. cho đồ thị có hướng sau.
a- Xây dựng đồ thị Cost.
b- Tìm đường đi ngắn nhất từ v0 đến các đỉnh còn lại trong đồ thị
c- Viết chương trình trong Passcal tìm đường đi ngắn nhất từ đỉnh v0 đến
mọi đỉnh.

25
66+
v0 30 v1x 15 v4

5 20 10 20
30 20

v2 5 3
v3 v5

10 15 20
v6
Chương 7: Sắp xếp

1. Sắp xếp bằng cách chèn trực tiếp (hay sắp xếp kiểu thêm dần:Insert Sort)
1.1. Phương pháp
* Phương pháp này được những người chơi bài hay dùng
* Giải thuật được thể hiện như sau: Giả sử ta sắp xếp dãy khoá a1, a2, ..., an.
- Các phần tử chia thành dãy đích: a1, . . ., ai-1 (kết quả) và dãy nguồn
ai, ..., an.
- Bắt đầu với i=2, ở mỗi bước phần tử thứ i của dãy nguồn được lấy ra và
chèn vào vị trí thích hợp trong dãy đích. Sau đó i tăng lên 1 và lặp lại.
Ví dụ: Cho dãy khoá 6 , 10, 1, 7, 4 với n=5 (dãy số có 5 phần tử).
Dãy đích Dãy nguồn
6 10, 1, 7, 4
i=2 6, 10 1, 7, 4
i=3 1, 6, 10 7, 4
i=4 1, 6, 7, 10 4
i=5 1, 4, 6, 7, 10
Thủ tục chèn trực tiếp được viết như sau:
Procedure Insert_sort(a,n)
1. a[0]:=-∞
2. For i:=1 to n Do
Begin
3. x:=a[i]; j:=i-1;
4. While x<a[j] Do
Begin
a[j+1]:=a[j]; j:=j-1;
End;
5. a[j+1]:=x; { đưa x vào đúng vi trí }
End;
6. Return
1.2. Đánh giá thuật toán
* Với giải thuật trình bày ở trên thif phép toán tích cực là phép so sánh ( x<a[j]).
Gọi C là số lượng phép so sánh. Thì C được tính như sau:
- Trường hợp thuận lợi nhất là dãy khoá a1, a2, ..., an đã được sắp, như vậy
mỗi lần chỉ cần 1 phép so sánh. Do vậy
n
Cmin= ∑1= n−1
i= 2

- Trường hợp xấu nhất nếu dãy khoá sắp theo thứ tự ngược với thứ tự sắp
xếp thì ở lượt i cần có: C= (i-1) phép so sánh. vì vậy:
n
n(n − 1)
Cmax= ∑ (i − 1) =
i= 2 2
- Trường hợp trung bình: Giả sử mọi giá trị khoá đều xuất hiện đồng khả
năng thì trung bình phép so sánh ở lượt thứ i là Ci = i/2, do đó số phép so sánh
trung bình của giải thuật này là:
n
n 2
+ n− 2
Ctb= ∑
i= 2
i
2
=
4
2. Sắp xếp bằng cách chọn trực tiếp (Selection Sort)
2.1. phương pháp
Giả sử cấn sắp xếp một dãy khoá a1, a2, ..., an
Giải thuật chọn trực tiếp được thể hiện như sau:
- Chọn phần tử có khoá nhỏ nhất .
- Đổi chỗ nó với phần tử a1.
- Sau đó lặp lại thao tác trên với n-1 phần tử còn lại, rồi lại lặp lại như
trên với n-2 phần tử còn lại,..., cho tới khi chỉ còn 1 phần tử.
Ví dụ: Cho dãy khoá ban đầu là: 6, 10, 1, 8, 9 với n=5.
I=1 1, 10, 6, 8, 9
I=2 1, 6, 10, 8, 9
I=3 1, 6, 8, 10, 9
I=4 1, 6, 8, 9, 10
Thủ tục chọn trực tiếp được thể hiện như sau:
Procedure Selection_sort(a,n);
For i:= 1 to n-1 Do
Begin
{ Tìm phần tử nhỏ nhất ở vị trí k }
k:=i;
For j:=i+1 to n Do
If a[j] < a[k] then k:=j
{ Đổi chỗ phần tử nhỏ nhất k cho phần tử i }
p:=a[k]
a[k]:=a[i]
a[i]:=p
End
Return
2.2. Đánh giá giải thuật
Với phương pháp sắp xếp này ở lượt thứ i (i=1, 2, . . . , n-1 ) để tìm khoá nhỏ
nhất cần n-1 phép so sánh. Số lượng phép so sánh này không phụ thuộc vào tình
trạng ban đầu của dãy khoá. Do đó ta có
n −1
n(n − 1)
Cmax=Cmin=Ctb= ∑ (n − 1) =
i =1 2

3. Sắp xếp bằng cách đổi chỗ trực tiếp ( Bubble Sort: nổi bọt)
3.1. Phương pháp
Giả sử cấn sắp xếp một dãy khoá a1, a2, ..., an
Giải thuật đổi chỗ trực tiếp được thể hiện như sau: Đổi chỗ 2 phần tử kề nhau
cho tới khi mọi phần tử được sắp.
- Đổi chỗ các phần tử liền kề nhau theo thứ tự tăng dần, lần thứ nhất ta
được số lớn nhất của dãy đẩy xuống cuối cùng ( gọi là phần tử được
sắp)
- Tiếp tục đổi chỗ các phần tử liền kề của dãy chưa sắp, lần thứ 2 ta được
số lớn nhất của dãy chưa sắp được đẩy xuống cuối cùng của dãy chưa
sắp.
- Cứ tiếp tục làm tương tự như trên cho đến khi dãy chỉ còn 1 phần tử.
Dãy có n phần tử ta cần thực hiện các bước như trên n-1 lần.
Ví dụ: Cho dãy khoá ban đầu là: 6, 3, 7, 10, 1, 8 với n=6.
I=1 3, 6, 7, 1, 8, 10
I=2 3, 6, 1, 7, 8, 10
I=3 3, 1, 6, 7, 8, 10
I=4 1, 3, 6, 7, 8, 10
I=5 1, 3, 6, 7, 8, 10

Thủ tục đổi chỗ trực tiếp được thể hiện như sau:
Procedure Bubble_sort(a,n)
For i:= 1 to n-1 Do
For j:= 1 to n-i Do
If a[j]>a[j+1] then Begin
X:=a[j];
a[j]:=a[j+1]
a[j+1]:=x
End;
Return
3.2. Đánh giá giải thuật
Giải thuật này tương tự như giải thuật sắp xếp bằng cách chọn trực tiếp(mục 2),
do đó có:
n −1
n(n − 1)
Cmax=Cmin=Ctb= ∑ (n − 1) =
i =1 2
* Nhận xét: Với 3 phương pháp sắp xễp trên nếu n vừa và nhỏ thì phương pháp
chèn trực tiếp ( insert sort) tỏ ra tốt hơn , nếu với n lớn thì cả 3 phương pháp đều
có cấp O(n2), đây là một chi phí thời gian khá cao.

4. Quic Sort ( Sắp xễp nhanh: Quick sort hay sắp xễp kiểu phân hoặch:
Partition sort )
4.1. Phương pháp
Phương pháp này được thể hiện như sau:
- Chọn ngẫu nhiên một phần tử x.
- Duyệt từ bên trái mảng cho tới khi có một phần tử ai>x
- Sau đó duyệt từ bên phải mảng cho tới khi có một phần tử aj<x
- Đổi chỗ ai và aj
- Tiếp tục duyệt và đổi chỗ cho tới khi 2 phía gặp nhau.
Kết quả mảng chia thành 2 phần: bên trái là các phần tử < x, bên phải là
các phần tử > x.
* Thủ tục thể hiện giải thuật này như sau:
Procedure Quick_sort( l,r );
Begin
i:=l; j:=r ; k:=(l+r) div 2;
x:=a[k];
Repeat
While a[i] <x Do i:=i+1;
While a[j] >x Do j:=j-1;
If i<j then đổi chỗ a[i] và a[j]
Until i=j
If j <> k then đổi chỗ a[k] và a[j]
Call Quick_sort( l,j-1 ); { Thực hiện trên nửa <x }
Call Quick_sort( j+1,r ); { Thực hiện trên nửa >x }

End;
{ Than chuong trinh chinh }
Begin
Quick_sort(1,n); { l=1, r=n }
End.
Ví dụ: Cho dãy khoá 9, 3, 6, 4, 7
l=1; r= 5; i=1; j=5 ; k=(1+5) div 2= 3

Ban đàu chốt là 6 : 9, 3, 6, 4, 7 i=1; j=4


Tiếp sau đổi chỗ 9 và 4 ta được: 4, 3, 6, 9, 7 i=3; j=3;
Tiếp theo tách 2 dãy con 4,3 và 9,7 thực hiện tương tự ,.. Ta được kết
quả : 3, 4, 6, 7, 9

4.2. Đánh giá


Người ta đã chứng minh được thời gian trung bình thực hiện giải thuật là:
Ttb= O(nlog2n )
Như vậy với n khá lớn Quick sort có hiệu lực hơn 3 thuật giải trên.
5. sắp xếp kiểu vun đống (Heap sort)
5.1. Phương pháp
* Tư tưởng giải thuật thể hiện như sau:
Sắp xếp kiểu vun đống chia làm 2 giai đoạn:
- Giai đoạn 1: Đầu tiên cây nhị phân biểu diễn bảng khoá biến đổi để trở
thành một " Đống" ( a heap ) . Đây gọi là giai đoạn tạo đống.
+ Một đống là một mcây nhị phân hoàn chỉnh mà mỗi nút được gán giá
trị khoá sao cho khoá ở nút cha bao giờ cũng > khoá ở nút con.
Cây nhị phân hoàn chỉnh được lưu trữ kế tiếp trong máy.
Với một đống, j là vị trí nút con thì vị trí nút cha là [j/2].
K[j/2] > K[j] với 1 <= [j/2]< j<=n
Ví dụ 1: Cây sau đây là một đống.

90

80 50

30 25 45

Khoá ở nút gốc của đống chính là khoá lớn nhất ( khoá trội ) so với các
khoá trên cây.
- Giai đoạn 2:
+ Đưa khoá trội về vị trí của nó bằng cách đổi chỗ cho khoá ở vị trí đó.
+ Vun lại đống với cây gồm các khoá còn lại ( sau khi đã loại khoá trội )
Quá trình trên lại được lặp lại.
Ví dụ: Có dãy khoá 42, 23, 74, 11, 65, 58
- Ta có cây hoàn chinh biểu diễn diễn dãy khoá:

42

23 74

11 65 58
- Giai đoạn đầu: Vun đống ban đầu

74

65 58

11 23 42

- Giai đoạn 2: Đổi chỗ 74 cho 42

42

65 58

11 23 74
- Loại khoá trội 74 . Dãy đã sắp s= (74) - Đổi chỗ 65 cho 23:

42 23

58 42 58
65

11 65
11 23

- Loại khoá trội 65 . Dãy đã sắp s= (65, 74)

23

42 58

11
- Lặp lại các bước tương tự cho các cây còn lại.
Cuối cùng ta thu được dãy đã sắp là s=(11, 23, 42,
58, 65, 74)
* Giải thuật vun đống:
- Một lá coi như cây con là một đống.
- Thuật toán tiến hành từ đáy lên: Chuyển đổi thành
đống cho một cây con mà cây con trái và cây con phải
của gốc đã là mọt đống.
Cây nhị phân hoàn chỉnh có n nút thì với chỉ số [n/2]
trở lên có thể là nút cha: [n/2], [n/2 ]-1, . . . , 1.
a) Thủ tục vun đống:
Chỉnh lý cây nhị phân hoàn chỉnh gốc i để trở thành “đống” với điều kiện cây
con trái và cây con phải có gốc là 2i và 2i+1 đã là đống.
Procedure ADJUST(i,n)
1. { Khởi đầu }
Key:=K[i]; j:=2*i;
2. { Chọn con ứng với khoá lớn nhất trong 2 con của i }
While j<=n Do
Begin
If (j<=n) and (K[j]<K[j+1]) then j:=j+1;
3. { So sánh khoá cha với khoá lớn nhất }
If Key < K[j] then Begin
K[j/2]:=Key;
Return;
End;
K[j/2]:=K[j]; j:=2*j;
End;
4. { Đưa Key vào vị trí của nó }
K[j/2]:=Key;
5. Return;
b) Thủ tục sắp xếp vun đống:
Procedure Heap_Sort(K,n)
1. { Tạo đống ban đầu }
For i:=[n/2] Downto 1 Do
Call ADJUST(i,n)
2. { Sắp xếp }
For i:= n-1 Downto 1 Do
Begin
x:=K[1]; K[1]:=K[i+1];
K[i+1]:=x;
Call ADJUST(1,i);
End;
3. Return
5.2. Đánh giá
Thời gian thực hiện trung bình của giải thuật này là O(nlog2n).
6. Sắp xếp kiểu hoà nhập ( MERGE SORT)
6.1. Phép hoà nhập 2 đường
Thực hiện hợp nhất các bản ghi của 2 bảng đã được sắp xếp
thành 1 bảng được sắp.
a) Phương pháp:
So sánh 2 khoá nhỏ nhất ( hoặc lớn nhất của 2 bảng) để đưa
vào miền sắp xếp.
Quá trình cứ tiếp tục cho tới khi 2 bảng đã cạn.
b) Giải thuật:
Bảng 1: (xb, ..., xm )
Bảng 2: (xm+1, ..., xn )
Bảng sắp: (zb, ..., zn)
Ví dụ: Bảng 1: (3, 5, 10, 16 )
Bảng 2: (1, 4, 15 )
Bảng sắp: (1, 3, 4, 5, 10, 15, 16)
* Thủ tục như sau:
Procedure MERGE(X,b,m,n,Z);
1. i:=k:=b; j:=m+1;
2. While (i<=m) and (j<=n) Do
Begin If x[i]<=x[j] then
Begin Z[k]:=x[i];
i:=i+1;
End
Else Begin z[k]:=x[j];
j:=j+1;
End;
k:=k+1;
End;
3. { Một trong 2 bảng con đã cạn }
If i>m then (zk, ..., zn):= (xj, ..., xn)
Else (zk, ..., zn):= (xi, ..., xm)
4. Return
6.2. Sắp xếp kiểu hòa nhập trực tiếp (Straight two way merge )
* Bảng con đã được sắp gọi là một mạch ( run).
* Mỗi bản ghi coi như 1 mạch có độ dài ( kích thước ) là 1. Nếu hoà nhập 2
bảng như vậy ta được 1 mạch mới có độ dài =2. Hoà nhập 2 mạch có độ
dài là 2 ta được một mạch có độ dài là 4, ...
* Thủ tục MPASS thực hiện một bước của sắp xếp hoà nhập. Nó hòa nhập
từng cặp mạch kề cận nhau, có độ dài L, từ bảng X sang bảng Y, n là số
lượng khoá ( bản ghi ) trong X.
Procedure MPASS(X,Y,n,L)
1. i:=1;
2. Hoà nhập cặp mạch có độ dài L }
While i<= n-2L-1 Do
Begin Call MERGE(X,i,i+L-1,i+2L-1, Y)
i:=i+2L;
End;
3. { Hoà nhập cặp mạch còn lại cuối cùng với tổng độ dài <2L}
If i+L-1 <n then Call MERGE(X,i,i+L-1,n,Y)
Else (yi, ..., yn):= (xi, ..., xn);
4. Return
Thủ tục MPASS trên sẽ được gọi tới trong thủ tục sắp xếp kiểu hoà nhập trình
bày ở sau đây.
* Thủ tục sắp xếp kiểu hoà nhập trực tiếp:
Thủ tục này lưu trữ các mạch mới khi hoà nhập dùng biến phụ là Y(n).
trong đó X và Y được dùng luân phiên. L là kích thước của mạch, sau mỗi lượt L
được tăng gấp đôi.
Procedure STRAIGHT_MSORT(X,n)
1. L:=1;
2. While L<n Do
Begin
Call MPASS(X,Y,n,L); L:=L+L;
Call MPASS(Y,X,n,L); L:=L+L;
End;
3. Return
Ví dụ: X= 9, 1, 7, 6, 4, 3, 8, 7
Bước 1: 1, 9, 6, 7, 3, 4, 7, 8 đưa vào Y L=1
Bước 2: 1, 6, 7, 9, 3, 4, 7, 8 đưa vào Y L=2
Bước 3: 1, 3, 4, 6, 7, 7, 8, 9 đưa vào Y L=4
6.3. Đánh giá
Thời gian thực hiện trung bình của giải thuật là:
Ttb = O(nlog2n)
* Nhận xét chung:
- Với n nhỏ có thể dùng các phương pháp: chọn
trực tiếp, chèn trực tiếp, đổi chỗ trực tiếp.
- Với n lớn: Nếu dãy khoá không sắp dùng Quick
sort, nếu dãy khoá có sắp dùng Heap sort.
Chương 8: Tìm kiếm
1. Bài toán tìm kiếm
* Bài toán tìm kiếm được phát biểu như sau:
Cho một bảng gồm n bản ghi r1, r2 , . . . , rn; ri ( 1<= i <=n ) tương
ứng với một khoá ki . Hãy tìm bản ghi có giá trị khoá tương ứng bằng
x cho trước.
* Gọi x là khoá tìm kiếm hay trị tìm kiếm. Công việc tìm kiếm sẽ hoàn
thành khi có một trong 2 tình huống sau xảy ra:
1- Tìm được bản ghi có giá trị khoá tương ứng bằng x. Lúc đó ta nói
phép tìm kiếm được thoả.
2- Không tìm được bản ghi nào có giá trị khoá bằng x . Khi đó ta nói
phép tìm kiếm không thoả.
Sau phép tìm kiếm không thoả nếu có yêu cần bổ sung bản ghi mới
có khoá x vào bảng. Giải thuật này gọi là “ Tìm kiếm có bổ sung”.
Khoá của mỗi bản ghi chính là đặc điểm nhận biết của bản ghi đó
trong tìm kiếm, ta coi nó là đại diện của bản ghi trong giải thuật.
2. Tìm kiếm tuần tự ( Sequential searching )
2.1. Phương pháp
Đây là giải thuật đơn giản, cổ điển.
Cách thức làm như sau: Bắt đầu từ bản ghi thứ nhất, lần lượt so
sánh khoá tìm kiếm với tương ứng của các bản ghi trong bảng cho
đến khi tìm thấy bản ghi mong muốn hoặc đã hết bảng mf chưa thấy.
* Giải thuật:
Khoá K có n phần tử. Tìm xem có khoá nào bằng x, nếu có đưa ra
thứ tự của khoá đó, nếu không đưa ra gí trị 0. Trong giải thuật sử
dụng khoá phụ kn+1=x.
Function SEQUEN_SEARCH(k,n,x)
1. { Khởi đầu }
i:=1; k[n+1]:=x;
2. { Tìm kiếm trong dãy}
While k[i] <> x Do i:=i+1;
3. { Không tìm thấy }
If i=n+1 then Return(0) Esle Return(i);
2.2. Đánh giá
Trong giải thuật này phép toán tích cực là phép so sánh k[i] <> x .
- Trường hợp thuận lợi ta tìm thấy ngay ở phần tử đầu nên Cmin = 1
- Trường hợp xấu nhất ta phải so sánh n+1 lần nên Cmax = n+1
- Giả sử hiện tượng khoá tìm kiếm x trùng với một khoá nào đó của bảng
là đồng khả năng thì Ctb = (n+1)/2
Do đó Ttb = O(n).
- Nếu trường hợp hiện tượng khoá tìm kiếm x trùng với một khoá nào đó
của bảng là không đồng khả năng, mà xác suất để xuất hiện ki = x là pi, pi ≠ 1/n.
Khi đó Ctb=1*p1 + 2p2 + . . . + n*pn
n
với ∑p
i=1
i =1

Từ công thức trên ta nhận thấy với p1>p2 > . . . >pn thì thời gian trung
bình sẽ nhỏ nhất. Như vậy ta phải sắp xếp trước khi tìm kiếm.
3. Tìm kiếm nhị phân (Binary searching )
3.1 Phương pháp
* Phương pháp sắp xếp này đượ thể hiện ở nội dung sau:
- Tương tự như tra tìm từ trong từ điển hoặc danh bạ điện
thoại. Chỉ khác là trong tra cứu ta chọn từ ngẫu nhiên, còn
trong tìm kiếm nhị phân luôn chọn khoá “ở giữa” dẫy khoá.
- Giả sử coa dãy khoá kL, . . ., kr thì khoá ở giữa là ki với i=
+ Tìm kiếm sẽ kết thúc nếu: x=ki
+ Nếu x<ki tìm kiếm sẽ được thực hiện tiếp với kL, . . . , ki-1
với cách tương tự.
+ Nếu x>ki tìm kiếm sẽ được thực hiện tiếp với ki+1, . . . , kr
với cách tương tự.
Qúa trình tìm kiếm kết thúc khi tìm thấy một khoá mong muốn
hoặc dãy khoá xét trở thành rỗng ( không tìm thấy ).
* Giải thuật:
Cho dãy K gồm n khoá, sắp xếp theo thứ tự tăng dần. Tìm khoá nào đó có giá
trị =x.
Dùng biến L, r, m: chỉ số đầu, chỉ số cuối, chỉ số giữa của khoá k.
Nếu tìm thấy cho ra chỉ số của khoá đó, nếu không tìm thấy cho ra 0.
Function Binary_search(K,n,x)
1. { Khới đầu }
L:=1; r:=n;
2. { Tìm kiếm }
While L<= r Do
Begin
3. { Tính chỉ số giữa }
m:=( L+r) div 2;
4. { So sánh }
If x<k[m] then r:=m-1
Else IF x>k[m] then L:=m+1
Else Return(0);
End;
5. { Tìm không thoả }
Return(0)
* Giải thuật viết dạng đệ quy như sau:
L, r là chỉ số đầu, chỉ số cuối của dãy K, biến nguyên Loc để đưa ra chỉ số
ứng với khoá cần tìm, nếu không tìm thấy thì Loc =0.
Function Binary_search(L,r,k,x)
1. If L>r then Loc:=0
Else m:=(L+r) div 2;
If x<k[m] then Loc:=Binary_search(L,m,k,x)
Else If x<k[m] then Loc:=Binary_search(m+1,r,k,x)
Else Loc:=m;
2. Return(Loc)
3.2. Đánh giá
Phép tính tích cực là phép so sánh.
Cmin=1.
Người ta đã tính được Cmax=[log2n ]+1
Ttb=O(log2n )
Tìm kiếm nhị phân tốt hơn tìm kiếm tuần tự nhưng dãy k phải được sắp.
4. Cây nhị phân tìm kiếm ( đã xét trong chương 5).
4.1. Định nghĩa cây nhị phân tìm kiếm

* Cây nhị phân tìm kiếm ứng với n khoá k1, k2, ..., kn là một cây
nhị phân mà mỗi nút của nó đều được định danh bởi một khoá
nào đó trong các khoá đã cho. Đối với mọi nút trên cây tính
chất sau đây luôn được thoả mãn:
- Mọi khoá thuộc cây con trái của nút đó đều nhỏ hơn khoá ứng
với nút đó.
- Mọi khoá thuộc cây con phải của nút đó đều lớn hơn khoá
ứng với nút đó.
Chú ý : Khoá là số thì so sánh số bình thường, Khoá là chữ thì ta
so sánh xâu kí tự.
Ví dụ có các cây nhị phân tìm kiếm sau:

35

25 40

38 45
20 28

b h

g i
a c

b
4.2. Giải thuật tìm kiếm
* Đối với một cây nhị phân để tìm kiếm xem một khoá x nào đó có
trên cây đó không? Ta có thể thực hiện như sau:
So sánh x với khoá ở gốc và một trong 4 tình huống sau đây sẽ
xuất hiện:
1- Không có gốc cây ( cây rỗng): X không có trên cây, phép tìm
kiếm không thoả mãn.
2- X trùng với khoá ở gốc: Phép tìm kiếm được thoả mãn.
3- X nhỏ hơn khoá ở gốc: Tìm kiếm được thực hiện tiếp tục
bằng cách xét cây con trái của gốc với cách làm tương tự.
4- X lớn hơn khoá ở gốc: Tìm kiếm được thực hiện tiếp tục
bằng cách xét cây con phải của gốc với cách làm tương tự.
Ví dụ Tìm x=28 trên cây a: So x và 35, x<35 nên ta tìm trên cây
con trái của 35
X>25 nên lại tìm trong cây con phải. So sánh ta có x=cây con
phải cũng là 28 nên phép tìm kiếm được thoả mãn.
Nếu tìm x=m trên cây b thì phép tìm kiếm không thoả mãn.
* Nếu phép tìm kiếm không thoả mãn ta bổ sung luôn x vào cây nhị phân tìm
kiếm. Phép bổ sung không ảnh hưởng đến cây, đến vị trí các khoá trên cây.
* Mỗi nút của cây nhị phân tìm kiếm có dạng sau:
LPTR KEY INFOR RPTR
LPTR: con trỏ trỏ tới gốc cây con trái.
RPTR: con trỏ trỏ tới gốc cây con phải.
KEY: khoá của các nút.
INFOR: thông tin.
* Thuật giải tìm kiếm trên cây nhị phân tìm kiếm như sau:
Cho cây nhị phân tìm kiếm có gốc được trỏ bởi T. Tìm nút trên cây có
khoá bằng x.
Nếu tìm kiếm được thoả thì con trỏ trỏ tới nút đó, giá trị trả về là q, đó là
địa chỉ của nút ta tìm được ( con trỏ q trỏ tới nút đó)
Nếu không tìm được ta bổ sung x vào cây T, con trỏ q trỏ vào nút mới đó.
Function BST(T,X)
1. {Khởi tạo con trỏ}
P=Null
q= T
2. { Tìm kiếm }
While q# Null Do
Casse
X<key(q): p:=q;q:=LPTR(q)
X=key(q): Return(q)
X>key(q): p:=q; q:=RPTR(q)
End Case
3. {Bo sung}
q ⇐ AVAIL
Key(q):=X
LPTR(q):=RPTR(q):= Null
Case
T=Null: T:=q
X<key(p): LPTR(p):=q
Else: RPTR(p):=q
End case
Writeln(‘ khong tim thấy, đã bổ sung’)
Return(q)
Bài tập chương 8
Bài 1: Cho 1 dãy số a1, a2, ..., an. Hãy tìm phần tử bằng
giá trị x nhập vào từ bàn phím. Lập trình theo 3 cách
tìm kiếm nêu trên.
Bài 2: Cho 1 danh sách điểm của sinh viên. Mỗi bản ghi
gồm các trường: Họ tên, số báo danh, điểm thi. Hãy
tìm sinh viên có số báo danh bằng giá trị x nhập vào
từ bàn phím. Lập trình theo 3 cách tìm kiếm nêu trên.

You might also like