Tải bản đầy đủ (.doc) (28 trang)

SKKN ỨNG DỤNG lý THUYẾT đồ THỊ TRONG VIỆC bồi DƯỠNG học SINH GIỎI TIN học 11

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

ỨNG DỤNG LÝ THUYẾT ĐỒ THỊ TRONG VIỆC
BỒI DƯỠNG HỌC SINH GIỎI TIN HỌC 11.
A. MỞ ĐẦU
1. Lý do chọn đề tài.
Đổi mới phương pháp dạy học là một nhiệm vụ quan trọng của ngành giáo
dục nhằm nâng cao chất lượng đào tạo, góp phần thực hiện công nghiệp hoá hiện
đại hóa đất nước.
Lý thuyết đồ thị (trong Tin học) là một chuyên ngành quan trọng đã được ứng
dụng vào nhiều ngành khoa học, kỹ thuật khác nhau vì lý thuyết đồ thị là phương
pháp khoa học có tính khái quát cao, có tính ổn định vững chắc để mã hóa các
mối quan hệ của các đối tượng được nghiên cứu.
Đồ thị thường thể hiện quan hệ nhị phân giữa các đối tượng rời rạc. Đó là
quan hệ thường gặp trong nhiều bài toán thực tế. Khoa học và kỷ thuật phát triển
làm xuất hiện hàng loạt bài toán trong thực tiển được quy về mô hình đồ thị.
Cùng với thời gian, nhiều thuật toán được xây dựng cho phép giải các bài toán có
kích thước dữ liệu lớn hơn và tốc độ thực hiện chương trình nhanh hơn.
Trong các kỳ thi học sinh gỏi Tin học THPT và các kỳ thi Olympic Tin học, bài
toán về lý thuyết đồ thị là một trong những nội dung được quan tâm nhiều.
Vận dụng lý thuyết đồ thị trong dạy học học sinh giỏi để mô hình hóa các mối
quan hệ chuyển thành phương pháp dạy học đặc thù sẽ nâng cao được hiệu quả
dạy học thúc đẩy quá trình tự học tự nghiên cứu của học sinh theo hướng tối ưu
hóa đặc biệt nhằm rèn luyện năng lực hệ thống hóa kiến thức và năng lực sáng
tạo của học sinh.
Nhiều bài toán thực tế đặt ra với những yêu cầu phức tạp, nếu chúng ta giải
theo cách thông thường sẽ rất vất vả, chương trình sẽ dài, lủng củng và chạy
thường không đúng với những bộ test lớn. Việc cung cấp thêm một phương pháp
giải bài tập cho học sinh Tin học 11 tham gia bồi dưỡng học sinh giỏi là một nhu
cầu cần thiết. Mặt khác việc vận dụng lý thuyết đồ thị vào giải toán giúp ta đạt
được hai mục tiêu:
- Giải được một lớp bài tập.
- Hỗ trợ cho việc lập trình.


Hiện nay việc nghiên cứu khai thác một số yếu tố của lý thuyết đồ thị vào giải
toán cũng được một số tác giả quan tâm nhưng chưa có những công bố có tính
chất hệ thống, xuất phát từ những lý do trên tôi lựa chọn đề tài:
“Ứng dụng lý thuyết đồ thị trong việc dạy bồi dưỡng học sinh giỏi Tin học 11”.
1
2. Mục đích nghiên cứu.
Chỉ ra hướng vận dụng lý thuyết đồ thị vào giải các bài toán và tìm ra các biện pháp
để giúp học sinh Tin học 11 tham gia bồi dưỡng học sinh giỏi tại trường THPT hình
thành và phát triển năng lực vận dụng lý thuyết đồ thị vào giải bài tập.
3. Nhiệm vụ nghiên cứu.
- Tìm hiểu những nội dung cơ bản của lý thuyết đồ thị được trang bị cho học sinh
Tin học.
- Chỉ ra hệ thống bài tập có thể vận dụng lý thuyết đồ thị để giải.
- Chỉ ra được những dấu hiệu cụ thể để nhận dạng “Bài toán” có thể khai thác lý
thuyết đồ thị trong quá trình giải bài toán.
- Chỉ ra các phương án vận dụng lý thuyết đồ thị vào giải toán.
- Kiểm tra hiệu quả của các biện pháp, phương án lý thuyết đồ thị vào giải toán
trong thực tế.
4. Giả thuyết khoa học.
Nếu ta có các phương pháp giúp học sinh Tin học 11 vận dụng kiến thức về lý thuyết
đồ thị vào giải các bài toán thì sẽ giúp học sinh giải quyết được một số lớp bài toán góp
phần nâng cao chất lượng dạy học giải bài tập.
5. Phương pháp nghiên cứu.
a. Nghiên cứu lý luận.
- Nghiên cứu các văn bản, tài liệu chỉ đạo của Bộ GD & ĐT liên quan đến đổi mới
phương pháp dạy học, đổi mới ra đề kiểm tra, danh mục thiết bị dạy học Tin học.
- SGK, phân phối chương trình, sách GV, chuẩn của bộ môn Tin ở trung học phổ
thông, sách nâng cao, sách chuyên đề.
- Các tài liệu về lý thuyết đồ thị và những ứng dụng của nó trong thực tiễn cuộc sống
và trong dạy học.

- Các công trình nghiên cứu các vấn đề liên quan trực tiếp đến phương pháp đồ thị.
b. Thực nghiệm sư phạm.
- Chỉ ra cho học sinh các dấu hiệu "nhận dạng" và cách thức vận dụng lý thuyết đồ
thị vào giải bài tập.
- Biên soạn hệ thống bài tập luyện tập cho học sinh và một số đề bài kiểm tra để
đánh giá khả năng vận dụng lý thuyết đồ thị vào giải các bài toán.
- Tiến hành thực nghiệm và đánh giá kết quả thực nghiệm.
2
B. NỘI DUNG
I. CƠ SỞ LÝ LUẬN CỦA VẤN ĐỀ.
1. Đồ thị và tầm quan trọng.
Lý thuyết đồ thị là một lĩnh vực nghiên cứu đã có từ lâu và có nhiều ứng dụng hiện
đại. Các bài toán đặt ra nếu được đưa về lý thuyết đồ thị để giải sẽ tiết kiệm được rất
nhiều thời gian, ý tưởng thuật toán sẽ rõ ràng, chương trình ngắn gọn và dễ hiểu. Nếu
hiểu và biết vận dụng tốt lý thuyết đồ thị sẽ giúp chúng ta giải quyết được rất nhiều bài
toán đặt ra trong thực tế (xem ở phần giải quyết vấn đề). Khoa học và kỹ thuật phát
triển làm xuất hiện hàng loạt bài toán trong thực tiển được quy về mô hình đồ thị.
1.1. Định nghĩa đồ thị: Cho tập hợp X khác rỗng, E là tập hợp các cặp phần tử của
X được sắp xếp thứ tự hoặc không sắp thứ tự. Cặp (X, E) được gọi là một đồ thị.
Kí hiệu đồ thị là G= (X,E) hoặc đôi khi nếu không gây nhầm lẫn kí hiệu tắt là G.
1.2. Một số khái nhiệm.
- Các phần tử thuộc tập X gọi là đỉnh của đồ thị G.
- Cho 2 đỉnh x1, x2∈X, nếu e=(x1,x2)∈E là cặp sắp thứ tự thì e được gọi là một
cung của đồ thị, hoặc nếu e là cặp không sắp thứ tự thì e được gọi là một cạnh của đồ
thị.
- e=(x1,x2) là cung thì x1 là đỉnh đầu của cung, x2 là đỉnh cuối của cung e.
- e=(x1,x2) là cạnh thì x1 và x2 là 2 đỉnh kề của cạnh e hoặc 2 đỉnh thuộc cạnh e.
- Hai đỉnh x1 và x2 (x1≠x2) của đồ thị được gọi là 2 đỉnh kề nhau nếu chúng là 2
đầu của một cạnh hoặc một cung.
- Hai cạnh a, b (hoặc 2 cung a, b) gọi là 2 cạnh kề nhau (hoặc 2 cung kề nhau) nếu

chúng có một đỉnh chung.
- Khuyên là cạnh (hoặc cung) có 2 đầu trùng nhau.
- Đỉnh treo là đỉnh thuộc duy nhất một cạnh hoặc cung.
- Đỉnh cô lập là đỉnh không thuộc cạnh hoặc cung nào.
1.3. Phân loại đồ thị.
Cho đồ thị G=(X, E), nếu E chỉ gồm các cạnh thì G là đồ thị vô hương. Nếu E chỉ
gồm các cung thì đồ thị G là đồ thị có hướng. Nếu E gồm cả cạnh và cung thì G là đồ
thị hỗn hợp.
- Đa đồ thị: Đồ thị G=(X,E) vô hướng (hoặc có hướng) là đa đồ thị khi và chỉ khi
nó là đồ thị không khuyên và có ít nhất một cặp đỉnh được nối với nhau bằng ít nhất 2
cạnh (hoặc 2 cung nối theo thứ tự của cặp đỉnh).
- Đơn đồ thị: Đồ thị G=(X,E) vô hướng (hoặc có hướng) là đơn đồ thị khi và chỉ
khi nó là đồ thị không khuyên và mỗi cặp đỉnh được nối với nhau không quá một cạnh
(hoặc cung).
3
2. Biểu diễn đồ thị.
Biểu diễn đồ thị trên máy tính theo cấu trúc nào thì sẽ có giải thuật theo cấu trúc đó.
Với học sinh Tin học 11, biểu diễn bằng ma trận (mảng 2 chiều) là dễ hiểu và phù hợp
nhất. Cách khai thác trên mảng 2 chiều đã được học sinh làm nhiều ở SGK Tin học 11.
Các cách biểu diễn đồ thị:
2.1. Biểu diễn bằng hình học.
Minh họa cách biểu diễn
`

`
2.2. Biểu diễn đồ thị bằng ma trận liên thuộc (Ma trận kề).
Giả sử đồ thị G=(X, E) có tập đỉnh X=(x
1
,x
2

, x
3
,…,x
n
), tập cạnh (hoặc cung) là E. Ta
xây dựng ma trận vuông A cấp n sao cho ∀ i,j, 1≤i,j≤n có:
Ma trận A là ma trận liên thuộc (ma trận kề)
Nhận xét : Nếu G là độ thị vô hướng thì
Ma trận A sẽ đối xứng qua đường chéo chính,
A
ij
=A
ji
∀ i,j, 1≤i,j≤n.
4
Huế
Hà Tây
Thanh Hoá
Nam Định
TP. HCM
Đ.Nai
Khánh Hoà
Long An
An Giang
Hà Nội
Hình 1: Đơn đồ thị, vô hướng
A[i,j]=
0 khi (x
i
, x

j
)∉E
1 khi (x
i
,x
j
)∈E
1
2 3
5
4
A=
0 1 0 0 0
1 0 1 0 1
0 1 0 0 1
0 0 0 0 1
0 1 1 1 0
Hình 4: Đồ thị và ma trận kề
1
2
3
4
5
6
Hình 2: Đa đồ thị, vô hướng
1
2
3
4
5

Hình 3: Đa đồ thị, có hướng
2.3. Biểu diễn bằng ma trận trọng số.
Trong nhiều bài toán về đồ thị, mỗi cạnh (hoặc cung) e=(xi,xj) của đồ thị thường
được gắn với một số c (e) gọi là trọng số của cạnh (hoặc cung) e. Khi đó thường xây
dựng ma trận vuông cấp n là ma trận C có mỗi phần tử C[i,j]=c (e) nếu tồn tại cạnh
(hoặc cung) e=(xi,xj), ngược lại khi không có cạnh nối xi với xj thì C[i,j]=∝ (kí hiệu ∝
là giá trị không xác định). Trong nhiều trường hợp, ngậm định C[i,i]=0 với mọi đỉnh i
trong đồ thị không khuyên.
3. Tìm kiếm trên đồ thị và tìm thành phần liên thông trên đồ thị.
Hiểu được bản chất của các phép tìm kiếm và tìm thành phần liên thông trên đồ thị
chúng ta có thể giải quyết được rất nhiều các dạng bài toán đặt ra (thể hiện ở phần áp
dụng). Qua tìm kiếm trên đồ thị chúng ta có thể kết hợp tính toán, thống kê, sắp xếp và
tổng hợp được các kết quả.
3.1. Một số khái niệm.
Định nghĩa 1: Đường đi có độ dài k (k nguyên dương) từ đỉnh u tới đỉnh v trên đồ
thị vô hướng G=(V, E) là dãy các đỉnh u=x
0
, x
1
, x
2
, x
3
,…, x
k
=v mà các cạnh (x
i
, x
i+1
)∈E,

i=0,1,2,…,k-1. Đường đi này còn có thể biểu diễn dưới dạng dãy các cạnh: (x
0
,x
1
),
(x
1
,x
2
),….,(x
k-1
,x
k
). Đỉnh u gọi là đỉnh đầu (xuất phát), đỉnh v gọi là đỉnh cuối (đỉnh đích)
của đường đi. Đường đi có đỉnh đầu trùng với đỉnh cuối gọi là một chu trình.
Đường đi hay chu trình được gọi là đơn nếu không có cạnh nào bị lặp lại.
Đường đi hay chu trình được gọi là cơ bản nếu không có đỉnh nào bị lặp lại (trừ
trường hợp trong chu trình thì đỉnh đầu trùng đỉnh cuối là được lặp lại)
Định nghĩa 2: Đường đi có độ dài k (k nguyên dương) từ đỉnh u tới đỉnh v trên đồ
thị có hướng G=(V, E) là dãy các đỉnh u=x
0
, x
1
, x
2
, x
3
,…, x
k
=v mà các cung (x

i
, x
i+1
)∈E,
i=0,1,2,…,k-1. Đường đi này còn có thể biểu diễn dưới dạng dãy các cung: (x
0
,x
1
),
(x
1
,x
2
),….,(x
k-1
,x
k
). Đỉnh u gọi là đỉnh đầu (xuất phát), đỉnh v gọi là đỉnh cuối (đỉnh đích)
của đường đi. Đường đi có đỉnh đầu trùng với đỉnh cuối gọi là một chu trình (mạch
vòng).
Đường đi hay chu trình được gọi là đơn nếu không có cung nào bị lặp lại.
Đường đi hay chu trình được gọi là cơ bản nếu không có đỉnh nào bị lặp lại (trừ
trường hợp trong chu trình thì đỉnh đầu trùng đỉnh cuối là được lặp lại)
Định nghĩa 3: Đồ thị vô hướng G=(V,E) được gọi là liên thông nếu luôn tìm được
đường đi giữa 2 đỉnh bất kỳ của nó.
Định nghĩa 4: Cho đồ thị vô hướng G=(V,E) và đồ thị con của G là đồ thị
G’=(V’,E’). Đồ thị G’ được gọi là một vùng liên thông (hoặc thành phần liên thông)
của G nếu:
+ G’ liên thông;
+ Không tồn tại đường đi nào từ một đỉnh thuộc G’ tới 1 đỉnh không thuộc G’ (nói

cách khác là bảo đảm tính tối đại của liên thông trong G’).
5
VD: Trong hình 5 xét 2 đồ thị G và H: G chỉ có 1 vùng liên thông duy nhất, H có 3
vùng liên thông là H1, H2, H3.
Định nghĩa 5: Đỉnh v được gọi là đỉnh khớp (đỉnh rẻ nhánh) của đồ thị vô hướng
G=(V,E) nếu khi loại bỏ đỉnh v và các cạnh liên thuộc với nó thì số thành phần liên
thông của G tăng thêm.
Cạnh e∈E được gọi là cầu nếu loại bỏ nó khỏi đồ thị G thì số thành phần liên thông
của G tăng thêm 1 đơn vị.
3.2. Tìm kiếm trên đồ thị.
Tìm kiếm trên đồ thị là duyệt (thăm) tất cả các đỉnh của đồ thị, mỗi đỉnh đúng 1 lần.
Rất nhiều thuật toán được xây dựng dựa trên cơ sở duyệt tất cả các đỉnh của đồ thị
sao cho mỗi đỉnh của nó được viếng thăm đúng 1 lần. Vì vậy, việc xây dựng những
thuật toán cho phép duyệt một cách hệ thống tất cả các đỉnh của đồ thị là một vấn đề
quan trọng. Các thuật toán này giữ một vai trò quan trọng trong việc thiết kế các thuật
toán trên đồ thị.
Trên đồ thị có 2 thuật toán tìm kiếm cơ bản:
- Thuật toán tìm kiếm theo chiều sâu (DFS.)
- Thuật toán tìm kiếm theo chiều rộng (BFS).
3.3. Tìm đường đi và kiểm tra tính liên thông.
Tìm đường đi và kiểm tra tính liên thông là một hình thức ứng dụng các thuật toán
tìm kiếm trên đồ thị. Đường đi tìm được theo thuật toán tìm kiếm theo chiều rộng là
đường đi ngắn nhất (theo số cạnh) từ đỉnh s đến đỉnh t.
4. Đường đi ngắn nhất trên đồ thị.
Trong các ứng dụng thực tế. Bài toán tìm đường đi ngắn nhất giữa 2 đỉnh của một đồ
thị liên thông có một ý nghĩa to lớn. Có thể dẫn về bài toán như vậy nhiều bài toán thực
tế quan trọng. Ví dụ, Bài toán chọn một hành trình tiết kiệm nhất (theo tiêu chuẩn
khoảng cách hoặc thời gian hoặc chi phí) trên một mạng giao thông đường bộ, đường
thủy hoặc đường không; bài toán chọn một phương pháp tiết kiệm để đưa một hệ động
lực từ trạng thái xuất phát đến trạng thái đích, bài toán lập lịch thi công các công đoạn

trong một công trình thi công lớn,…
6
H1
H2
H3
G
Hình 5: G liên thông, H gồm 3 vùng liên thông
H
II. THỰC TRẠNG CỦA VẤN ĐỀ.
2. Thuận lợi.
- Lý thuyết đồ thị có thể giải quyết được nhiều bài toán đặt ra trong thực tế phù
hợp với đối tượng học sinh giỏi Tin học 11, đặc biệt là những bài toán thể hiện quan
hệ nhị phân giữa các đối tượng rời rạc.
- Vận dụng lý thuyết đồ thị giúp học sinh có thêm một luồng kiến thức mới để làm
giàu hơn tư duy thuật toán của mình.
- Có khá nhiều tài liệu giới thiệu về các vấn đề liên quan đến lý thuyết đồ thị như:
sách cấu trúc dữ liệu và giải thuật, Sách Toán rời rạc,…và các tài liệu trên mạng
Internet.
- Giáo viên và học sinh phát huy được tính năng động trong quá trình dạy - học
đạt kết quả cao hơn.
- Một số kiến thức dễ sử dụng và hiệu quả cao. Ví dụ: phép tìm kiếm và kiểm tra
vùng liên thông trên đồ thị.
3. Khó khăn.
- Trong việc nắm bắt và hiểu được các khái niệm cơ bản liên quan đến lý thuyết đồ
thị.
- Lý thuyết đồ thị rất rộng và nhiều phần kiến thức khó nên không thể truyền tải hết
tới học sinh và khó để đưa vào hết trong đề tài.
- Đưa ra các giải thuật bằng ngôn ngữ Pascal để minh hoạ các kiến thức đưa ra ở
phần cơ sở lý luận.
- Đưa ra hệ thống các dạng bài tập có thể giải quyết hiệu quả bằng lý thuyết đồ thị

và cách giải các bài tập đó.
Để khắc phục được một phần khó khăn nêu trên, trong đề tài tôi chỉ đề cập đến
những phần quan trọng của lý thuyết đồ thị có ứng dụng nhiều trong thực tế và phù
hợp với học sinh THPT, đặc biệt là học sinh Tin học 11.
III. THỰC TIỂN ÁP DỤNG.
1. Hai thuật toán và chương trình tìm kiếm trên đồ thị.
1.1. Tìm kiếm theo chiều rộng.
Ý tưởng: Đỉnh xuất phát v ở đây cũng được thăm đầu tiên nhưng có khác với
DFS ở chổ là: Sau đó các đỉnh chưa được thăm mà là lân cận của v sẽ được thăm kế
tiếp theo nhau, rồi mớ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.
VD: Với đồ thị như hình 6 thì 1 được thăm rồi đến 2, 3…tiếp theo là 4, 5, và 6, 7,
cuối cùng là 8
7
1
2
4
8
5
6
3
7
Hình 6
Quá trình duyệt theo chiều rộng có thể mô tả bởi thủ tục đệ quy như sau:
Procedure BFS(v); {Tìm kiếm theo chiều rộng bắt đầu từ đỉnh v}
Begin
Queue:=φ;
Queue <= v; {nạp đỉnh v vào Queue}
Chuaxet[v] :=false ;
While Queue <> φ do

Begin
u<= Queue; {Lấy đỉnh p từ Queue ra}
Thăm_đỉnh(u);
For y∈ Ke(u) do
If chuaxet[y] then
Begin
Queue <= y; {nạp đỉnh v vào Queue}
Chuaxet[y]:=false;
End;
End;
End;
BEGIN
For v∈ V do Chuaxet[v]:=true; {Khởi tạo}
For v∈ V do
If Chuaxet[v] then BFS(v);
END.
Chương trình duyệt đồ thị theo chiều rộng được cài đặt cụ thể là:
Var chuaxet:array[1 100] of boolean;
A:array[1 20,1 20] of 0 1;
Queue:array[1 20] of byte;
n,y,i,j,S:integer;
(*============================================*)
Procedure Nhapsolieu; {Nhap so lieu cho ma tran ke}
Begin
Write(‘ Nhap so dinh cua do thi:’); readln(n);
Write(‘ Nhap so lieu ma tran ke:’);
For i:=1 to n do
begin
For j:=i+1 to n do
Begin

Write(‘a[‘,i, ‘,’,j,’]=’);readln(a[i,j]);
a[j,i]:=a[i,j];
End;
8
a[i,i]:=0; writeln;
end;
End;
(*============================================*)
Procedure BFS(i:integer);{Tìm kiếm theo chiều rộng bắt đầu từ đỉnh i}
Var u, dauQ, CuoiQ:integer;
Begin
dauQ:=1; CuoiQ:=1;
Queue[cuoiQ] :=i ;
Chuaxet[i]:=fasle;
While dauQ<=CuoiQ do
Begin
u:=Queue[dauQ]; dauQ:=dauQ+1;
For y :=1 to n do
If (a[u,y]=1) and (chuaxet[y]) then
Begin
cuoiQ:=cuoiQ+1; Queue[cuoiQ] :=y ;
Chuaxet[y] :=false ;
End ;
End ;
End;
BEGIN
Nhapsolieu;
Fillchar(Chuaxet, sizeof(chuaxet), true); {khởi tạo mảng Chuaxet}
For s:=1 to n do if not chuaxet[s] then BFS(s);
END.

1.2. Tìm kiếm theo chiều sâu
Ý tưởng: Đỉnh xuất phát v được thăm. Tiếp theo đó, một
đỉnh y chưa được thăm, mà là lân cận của v, sẽ được chọn và
một phép tìm kiếm theo chiều sâu xuất phát từ y lại được thực
hiện.
Khi một đỉnh u đã được “với tới” mà mọi đỉnh lân cận của
nó đều đã được thăm rồi, thì ta sẽ quay ngược lên đỉnh cuối
cùng vừa được thăm, (mà còn có đỉnh y lân cận với nó chưa
được thăm), và một phép tìm kiếm theo chiều sâu xuất phát từ y
lại được thực hiện. Phép tìm kiếm sẽ kết thúc khi không còn
một nút nào chưa được thăm mà vẫn có thể
với tới được từ một nút đã được thăm.
VD: Ta có đồ thị như hình 6.
9
1
2
4
8
5
6
3
7
Hình 6
Đầu tiên đỉnh 1 được thăm, rồi tới 2 (tất
nhiên có thể là 3) rồi 4, 8, 5; do 5 không
có lân cận nào chưa được thăm nên phải
quay lại 8 để thăm tiếp 6 rồi 3, 7
Quá trình duyệt theo chiều sâu có thể mô tả bởi thủ tục đệ quy như sau:
Procedure DFS(v); {tìm theo chiều sâu bắt đầu từ đỉnh v, các biến Chuaxet, ke
là cục bộ}

Begin
Thăm đỉnh (v);
Chuaxet[v]:=false;
For mỗi đỉnh y Ke( v) do
If chuaxet[y] then DFS(
ω
);
End;
Khi đó, tìm kiếm theo chiều sâu trên đồ thị được thực hiện nhờ thuật toán sau:
BEGIN
For v

V do Chuaxet[v]:=true;
For v

V do if Chuaxet[v] then DFS(v);
END.
Chương trình được cài đặt cụ thể là:
Var chuaxet:array[1 100] of boolean;
A:array[1 100,1 100] of 0 1;
S:integer;
(*============================================*)
Procedure Nhapsolieu; {Nhap so lieu cho ma tran ke}
Begin
Write(‘ Nhap so dinh cua do thi:’); readln(n);
Write(‘ Nhap so lieu ma tran ke:’);
For i:=1 to n do
begin
For j:=i+1 to n do
Begin

Write(‘a[‘,i, ‘,’,j,’]=’);readln(a[i,j]);
a[j,i]:=a[i,j];
End;
a[i,i]:=0; writeln;
end;
End;
(*============================================*)
Procedure DFS(v:integer); {Thắm bắt đầu từ đỉnh v chưa thăm}
Var y:integer;
Begin
Write(v, ‘ ‘);
Chuaxet[v]:=false; {Đánh dấu đã thăm đỉnh v}
10
For y:=1 to n do {Duyệt mọi đỉnh y}
If (a[v,y]=1) and (not chuaxet[y]) then DFS(y);
{y kề với v và chưa được thăm thì thăm y}
End;
BEGIN
Nhapsolieu;
Fillchar(Chuaxet, sizeof(chuaxet), true); {khởi tạo mảng Chuaxet}
For s:=1 to n do if not chuaxet[s] then DFS(s);
END.
2. Tìm đường đi và kiểm tra tính liên thông.
a. Bài toán tìm đường đi giữa 2 đỉnh
Giả sử s và t là 2 đỉnh nào đó của đồ thị. Hãy tìm đường đi từ s đến t.
Ý tưởng: Như trên đã phân tích, thủ tục DFS(v) (hoặc BFS(v)) sẽ cho phép thăm tất
cả các đỉnh thuộc cùng một thành phần liên thông với s. Vì vậy, sau khi thực hiện xong
thủ tục, nếu Chuaxet[t]=true, thì điều đó có nghĩa là không có đường đi từ s đến t. Còn
nếu Chuaxet[t]=false thì t thuộc cùng thành phần liên thông với s, hay nói cách khác là
tồn tại đường đi từ s đến t. Trong trường hợp tồn tại đường đi, để ghi nhận đường đi, ta

dùng thêm biến Truoc[v] để ghi nhận đỉnh đi trước đỉnh v trong đường đi tìm kiếm từ s
đến v. Khi đó, đối với thủ tục DFS(v) cần sửa đổi câu lệnh if trong nó như sau:
If Chuaxet[y] then
Begin
Truoc[y]:=v;
DFS(y) ;
End ;
Còn đối với thủ tục BFS(v) cần sửa đổi câu lệnh if trong nó như sau:
If chuaxet[y] then
Begin
Queue <= y; {nạp đỉnh v vào Queue}
Chuaxet[y]:=false;
Truoc[y]:=u;
End;
b. Tìm các thành phần liên thông của đồ thị.
Bài toán đặt ra: Hãy cho biết đồ thị có bao nhiêu thành phần (vùng) liên thông
và mỗi thành phần liên thông gồm những đỉnh nào.
Ý tưởng: Do thủ tục DFS(v) (hoặc BFS(v)) cho phép thăm tất cả các đỉnh thuộc
cùng thành phần liên thông với v, nên số thành phần liên thông của đồ thị chính bằng số
lần gọi đến thủ tục này. Nghĩa là nếu trong chương trình chính gọi đến thủ tục DFS(v) 3
lần thì kết luận có 3 thành phần liên thông. Vấn đề còn lại là ghi nhận các đỉnh của từng
thành phần liên thông. Ta dùng thêm 1 biến có tên DemLT để đếm số thành phần liên
thông. Khởi tạo biến này có giá trị bằng 0.
11
Chương trình tìm đường đi từ đỉnh s tới đỉnh t và tìm số thành phần liên thông
của đồ thị như sau:
Var A:array[1 20,1 20] of 0 1;
Queue, chuaxet, Truoc:array[1 20] of byte;
n,y,i,j,DemLT, s, t:integer;
(*============================================*)

Procedure Nhapsolieu; {Nhap so lieu cho ma tran ke}
Begin
Write(‘ Nhap so dinh cua do thi:’); readln(n);
Write(‘ Nhap so lieu ma tran ke:’);
For i:=1 to n do
begin
For j:=i+1 to n do
Begin
Write(‘a[‘,i, ‘,’,j,’]=’);readln(a[i,j]);
a[j,i]:=a[i,j];
End;
a[i,i]:=0; writeln;
end;
End;
(*============================================*)
Procedure Doctep;
Var f:text;
fn:string;
Begin
Write(‘ Nhap ten tep du lieu:’) ; readln(fn) ;
Assign(f, fn) ;reset(f);
Readln(f, n);
Write(‘ Nhap so lieu ma tran ke:’);
For i:=1 to n do
For j:=1 to n do read(f, a[i,j]);
Close(f);
End;
(*============================================*)
Procedure Insolieu;
Begin

Writeln(‘ Ma tran ke:’);
For i:=1 to n do
Begin
For j:=1 to n do write(a[i,j]:3);writeln;
12
End;
End;
(*============================================*)
Procedure Inketqualienthong;
Begin
Insolieu;
If DemLT=1 then write(‘ Do thi la lien thong’ )
Else
Begin
Writeln(‘ So thanh phan lien thong cua do thi la:’, DemLT);
For i:=1 to DemLT do
Begin
Writeln(‘ Thanh phan lien thong thu ’, i, ‘ gom cac dinh:’);
For j:=1 to n do
If Chuaxet[j]=i then write(j:3); writeln;
End;
End;
End;
(*============================================*)
Procedure BFS(i:integer);
Var u, dauQ, CuoiQ:integer;
Begin
dauQ:=1; CuoiQ:=1;
Queue[cuoiQ] :=i ;
Chuaxet[i]:=DemLT;

While dauQ<=CuoiQ do
Begin
u:=Queue[dauQ]; dauQ:=dauQ+1;
For y :=1 to n do
If (a[u,y]=1) and (chuaxet[y]=0) then
Begin
cuoiQ:=cuoiQ+1; Queue[cuoiQ] :=y ;
Chuaxet[y] :=DemLT ;
Truoc[y]:=u;
End ;
End ;
(*============================================*)
Procedure DFS(v:integer);
Var y:integer;
Begin
13
Chuaxet[v]:=DemLT;
For y:=1 to n do
If (a[v,y]=1) and (not chuaxet[y]=0) then
Begin
Truoc[y]:=v;
DFS(y);
End ;
End;
(*============================================*)
Procedure Lienthong;
Begin
{Khoi tao so lieu}
For j:=1 to n do Chuaxet[j]:=0;
DemLT:=0;

For i:=1 to n do
If chuaxet[i]=0 then
Begin
demLT:=demLT+1;
DFS(i); {hoac BDF(i)}
End;
Inketqualienthong;
End;
(*============================================*)
Procedure Ketquaduongdi;
Begin
If Truoc[t]=0 then writeln(‘ Khong co duong di tu ’, s, ‘ den’, t)
Else
Begin
Write(‘ Duong di tu’, s, ‘ den ’, t, ‘ la:’);
J:=t;
Write(t, ‘<==’) ;
While Truoc[j]<>s do
Begin
Write(Truoc[j], ‘<==’);
J:=Truoc[j];
End;
Writeln(s);
End;
(*============================================*)
14
Procedure Duongdi;
Begin
Insolieu;
Write(‘ Nhap dinh xuat phat:’); readln(s);

Write(‘ Nhap dinh den:’); readln(t);
For j:=1 to n do {khoi tao so lieu}
Begin
Truoc[j]:=0;
Chuaxet[j]:=0
End;
DemLT:=1;
BFS(s); {hoac DFS(s)}
Ketquaduongdi;
End;
(*============================================*)
BEGIN {Chuong trinh chinh}
CLRSCR;
Writeln(‘ 1. Nhap so lieu tu ban phim’);
Writeln(‘ 2. Nhap so lieu tu tep van ban’);
Writeln(‘ 3. Kiem tra tinh lien thong cua do thi’);
Writeln(‘ 4. Tim duong di giua 2 dinh bat ky’);
Writeln(‘ 5. Thoat khoi chuong trinh’);
Writeln(‘==========================’);
Writeln(‘ Hay go phim so (15) de chon chuc nang…’#7);
Ch:=readkey;
Writeln(ch);
Repeat
Case ch of
‘1’: nhapsolieu;
‘2’: doctep;
‘3’: Lienthong;
‘4’ : Duongdi ;
End ;
Until (ch= ‘5’) or (upcase(ch)=’Q’);

END.
15
Một số ví dụ áp dụng.
Có rất nhiều bài toán vận dụng thuật toán tìm kiếm trên đồ thị và thuật toán tìm
số vùng liên thông để giải mà chương trình ngắn gọn, hiệu quả. Đặc biệt là các bài toán
liên quan đến duyệt, tìm kiếm mà cách lưu trữ dữ liệu được đưa về dưới dạng ma trận
kề (mảng 2 chiều). Dưới đây tôi xin trích ra một số ví dụ để minh họa cho điều này:
(Trong các ví dụ dưới đây, tôi chỉ đưa ra cách giải từng ví dụ mà không đưa
chương trình kèm theo vì đưa vào làm cho đề tài quá dài)
Ví dụ 1: Miền liên thông.
Cho bảng chữ nhật chia thành M xN ô vuông đơn vị (M dòng đánh số từ 1 đến M
theo chiều từ trên xuống dưới, N cột đánh số từ 1 đến N theo chiều từ trái qua phải. Mỗi
ô vuông ghi 1 số 0 hoặc 1. Một miền 0 của bảng là tập hợp các ô chung cạnh và chứa
số 0). Địa chỉ của một miền là toạ độ [dòng, cột] của ô đầu tiên thuộc miền theo thứ tự
duyệt từ trái qua phải, từ trên xuống dưới. Hãy tìm số miền 0 của bảng và tìm miền 0 có
diện tích lớn nhất.
Dữ liệu vào : File Mien.INP có cấu trúc :
- Dòng đầu ghi 2 số nguyên dương M và N (0<M,N

100).
- M dòng tiếp theo thể hiện bảng số theo thứ tự từ trên xuống dưới, mỗi
dòng N số theo thứ tự từ trái sang phải.
Dữ liệu ra : File Mien.Out có cấu trúc :
- Dòng thứ nhất ghi số lượng miền 0.
- Dòng thứ 2 ghi diện tích của miền 0 có diện tích lớn nhất.
- Các dòng tiếp theo, mỗi dòng ghi địa chỉ một miền 0 có diện tích lớn nhất.
Cách giải : Chúng ta có thể giải theo cách thông thường không vận dụng lý thuyết
đồ thị. Nhưng rõ ràng, giải theo cách tìm kiếm trên đồ thị sẽ ngắn gọn, hiệu quả và
nhanh hơn nhiều. Thuật toán thể hiện rõ ràng, dễ hiểu. Cụ thể như sau :
• Thực hiện vòng lặp.

1. Tìm một ô chứa số 0 chưa thăm là ô x có toạ độ dòng và cột là (i,j).
2. Thực hiện thuật toán BDF (tìm kiếm theo chiều rộng) để tìm được miền 0
chứa ô x. Trong quá trình duyệt cũng tính diện tích của miền (mỗi lần đến một ô
mới thì tăng diện tích lên một đơn vị).
3. Mỗi lần thực hiện xong thủ tục BDF thì tìm được một miền 0 chứa ô (i,j),
lưu kết quả (toạ độ i, j và diện tích của miền vào mảng KQ) đồng thời tăng biến
đếm số miền 0.
• Hiện số lượng miền 0.
• Duyệt mảng KQ để tìm miền 0 có diện tích lớn nhất. Hiện diện tích miền 0
lớn nhất
• Duyệt mảng KQ lần thứ 2 để hiện toạ độ từng ô đại diện cho mỗi miền 0
có diện tích bằng diện tích của miền lớn nhất.
16
Ví dụ 2: Hệ thống đảo cung cấp xăng.
Vùng Hạ Long có N hòn đảo được đánh số từ 1 đến N. Toạ độ hòn đảo thứ i trên
mặt phẳng toạ độ được cho bởi cặp số nguyên x
i
, y
i
. Trên mỗi đảo có bể chứa xăng có
khả năng cung cấp đầy các thiết bị chứa xăng của ca nô. Biết rằng các thiết bị chứa
xăng của ca nô không thể chứa đủ số xăng đi hết M km. Hãy tìm hành trình cho ca nô đi
từ đảo U đến đảo V (0<U, V

N) mà số lần ghé vào các đảo để lấy xăng là nhỏ nhất.
Dữ liệu vào : File văn bản DAO.INP có cấu trúc
- Dòng đầu ghi 4 số nguyên dương N, M, U, V
- Các dòng tiếp theo, dòng thứ i trong các dòng này ghi 2 số nguyên dương
x
i

, y
i.
Kết quả : Ghi ra file văn bản DAO.OUT
- Nếu có đường đi thì dòng đầu tiên ghi số đảo ghé vào lấy xăng (trừ U và
V)
- Dòng thứ 2 ghi số hiệu các đảo đó theo thứ tự hành trình. Nếu không có
đường đi thì ghi ‘KHONG CO DUONG DI’
VD :
DAO.INP DAO.OUT
12 10 1 12
0 0
8 0
8 6
0 8
10 4
15 4
20 8
20 0
25 8
25 4
25 0
30 4
4
2 6 7 9
Cách giải : Áp dụng thuật toán tìm kiếm theo chiều rộng để được đường đi qua
ít đảo nhất. Điều kiện để đi được từ đảo A sang đảo B là khoảng cách AB

M km.
Trong quá trình tìm kiếm theo chiều rộng tổ chức thêm mảng Truoc(N) với
Truoc[i]=j có nghĩa là đảo j ngay trước đảo i trên hành trình đi từ U đến V.

Ví dụ 3: Quét vôi khu nhà cao tầng.
Một quần thể nhà cao tầng được xây dựng trên một nền hình chữ nhật, trên đó được
chia thành M*N ô vuông (M dòng, N cột). Các dòng được đánh số từ 1 đến M và các
cột được đánh số từ 1 đến N. Người ta xem khu nhà được tạo ra bởi các khối có đáy là
một ô vuông với những chiều cao nào đó mà người ta gọi là những đơn nguyên. Một
đơn nguyên được xác định bởi tạo độ dòng, cột của ô đáy và chiều cao tương ứng. Một
17
1 2 3 0 2 1
1 0 1 0 0 1
2 1 1 0 0 1
0 0 0 1 1 0
khối nhà được định nghĩa là một tập hợp gồm các đơn nguyên có đáy tạo thành một
miền gồm những ô vuông kề cạnh. Thí dụ, hình vẽ dưới mô tả một quần thể gồm 3 khối
nhà : người ta đánh số các khối nhà bằng những số nguyên liên tục bắt đầu từ 1 theo
trình tự duyệt các ô đáy theo từng dòng từ 1 đến M, và trên
mỗi dòng, duyệt các ô đáy theo từng cột từ 1 đến N. Ví dụ,
các khối nhà cho trong hình vẽ bên được đánh số theo thứ tự
các ô đáy (có màu xám, con số là chiều cao) :
Người ta muốn quét vôi các bức tường xung quanh tất cả
các khối nhà. Hãy xác định :
- Số lượng các khối nhà.
- Tổng số diện tích quét vôi.
- Số nhà có diện tích quét vôi lớn nhất và giá trị diện tích này.
Dữ liệu vào : từ file văn bản QV.INP có dạng :
M N
H[1,1] H[1,2] H[1,N]
H[2,1] H[2,2] H[2,N]

H[M,1] H[M,2] H[M,N]
Trong đó H[i,j] là chiều cao của đơn nguyên [i,j] với quy ước bằng 0 khi đơn

nguyên này không có. Giả thiết rằng các giá trị này đều là những số
nguyên và tính theo đơn vị 1 cạnh ô vuông. Các số trên cùng 1 dòng
ghi cách nhau ít nhất 1 dấu cách. Ví dụ, hình vẽ ở trang trước mô tả
3 khối nhà tương ứng với file dữ liệu như bảng bên.
Kết quả: Ghi ra file văn bản QV.OUT.
- Dòng đầu ghi số lượng các khối nhà.
- Dòng thứ 2 ghi tổng số diện tích quét vôi.
- Dòng thứ 3 ghi số hiệu của khối có diện tích quét vôi lớn nhất và giá trị
của diện tích này (ghi cách nhau ít nhất 1 dấu cách).
Ví dụ, với file dữ liệu trên, file kết quả có số liệu như bảng bên.
Cách giải:
Trước hết dùng thuật toán tìm kiếm theo chiều sâu hoặc theo chiều rộng để tìm ra
các vùng liên thông (mỗi khối nhà là một vùng liên thông). Trong quá trình tìm kiếm
cũng đánh số hiệu cho từng khối nhà.
Để tìm ra tổng diện tích cần quét vôi cho các bức tường xung quanh mọi đơn nguyên
của một khối nhà ta duyệt lại từng ô của khối đó.
Xét đơn nguyên Đ nằm trên ô (i,j) có chiều cao là h. Ta để ý tới 4 đơn nguyên xung
quanh Đ là các đơn nguyên Đ
1
, Đ
2
, Đ
3
, Đ
4
tương ứng có chiều cao là h
1
, h
2
, h

3
, h
4
. Rõ
ràng là nếu Đ
i
(i=1,…,4) nào đó thấp hơn Đ thì bề mặt tường của đơn nguyên Đ (i,j) về
18
4 6
1 2 3 0 2 1
1 0 1 0 0 1
2 1 1 0 0 1
0 0 0 1 1 0
3
50
1 3
phía đó lộ ra phải quét vôi. Ngược lại, nếu về phía đó có đơn nguyên Đ
i
cao hơn hoặc
bằng Đ thì tường của đơn nguyên Đ bị che khuất bởi đơn nguyên Đ
i
đó, dẫn tới mặt
tường này của Đ không cần quét vôi. Vậy diện tích tường bao quanh đơn nguyên Đ lộ ra
phải quét vôi là:
S=Max(0, h - h
1
)+Max(0, h - h
2
)+Max(0, h - h
3

)+Max(0, h - h
4
)
Tổng diện tích quét vôi của một khối nhà bằng tổng diện tích tường bao quanh lộ ra
của các đơn nguyên thuộc khối nhà đó.
Ví dụ 4:
Có một số con cừu trong trại chăn nuôi của Mickey. Trong khi Mickey đang ngủ say,
những con sói đói đã vào trại và tấn công đàn cừu.
Trại có dạng hình chữ nhật gồm các ô tổ chức thành hàng và cột. Kí tự dấu chấm ‘.’
Là ô rỗng, kí tự ‘#’ là hàng rào, kí tự ‘o’ là cừu và kí tự ‘v’ là chó sói.
Chúng ta coi 2 ô là cùng một miền nếu có thể chuyển từ ô nọ tới ô kia bằng đường đi
chỉ gồm các đường đi theo chiều ngang hoặc thẳng đứng không vướng hàng rào. Các ô
mà từ chúng có thể thoát khỏi sân không được xem là một phần của bất kì miền nào.
May thay, những con cừu biết tự vệ. Chúng có thể chiến đấu với những con sói trong
miền (húc chết sói) nếu số lượng cừu lớn hơn số lượng sói trong cùng một miền. Ngược
lại những con sói sẽ ăn hết các con cừu trong cùng một miền.
Ban đầu các con cừu và các con sói đã được xác định trong các miền của trại.
Viết một chương trình tính số lượng cừu và số lượng sói còn lại trong sáng hôm đó.
Dữ liệu vào: File văn bản SOICUU.INP:
Dòng đầu tiên chứa 2 số nguyên R và C (3<=R,C<=250) là số hàng và số cột của
trại.
Mỗi dòng trong R dòng sau gồm C kí tự. Tất cả các kí tự này biểu diễn các vị trí có
hàng rào, cừu và chó sói trong trại.
Kết quả: Ghi ra file SOICUU.OUT chỉ một dòng gồm 2 con số: Số cừu và số sói còn
lại trong trại. Ví dụ:
SOICUU.INP SOICUU.INP SOICUU.INP
6 6
. . . # . .
. # # v# .
#v . # . #

# . o # . #
. # # # . #
. . . # # #
8 8
. # # # # # # .
. . . o . . . #
# . # # # # . #
# . # v . # . #
# . # . o # o #
# o . # # . . #
# . v . . v . #
. # # # # # # .
9 12
.###.#####
#.oo# #v#.
# o#.#.#.#.
# ##o# #.
#.#v#o###.#.
# #v# #.
# v#v####.
.####.#vv.o#
####.
SOICUU.OUT SOICUU.OUT SOICUU.OUT
0 2 3 1 3 5
19
Cách giải : Sử dụng thuật toán tìm kiếm các miền liên thông. Trong mỗi miền liên
thông đếm số cừu và số sói trong đó. Nếu số cừu lớn hơn số sói thì coi như số sói còn lại
trong miền này bằng 0, nếu ngược lại thì số cừu còn lại trong miền này bằng 0. Khi tìm
tới ô nào thì xoá ô đó bằng cách gán kí tự ‘#’ trên ô đó.
3. Đường đi ngắn nhất trên đồ thị.

Bài toán: Cho đồ thị vô hướng, có trọng số không âm. Hãy tìm đường đi cơ bản
(không có đỉnh lặp lại) ngắn nhất từ đỉnh xuất phát x đến đỉnh đích y (y≠x).
Có nhiều thuật toán để giải bài toán đặt ra, tuy nhiên trong đề tài này tôi chỉ đề cập
tới thuật toán có nhiều ứng dụng và rất hiệu quả, đó là thuật toán Dijkstra.
3.1. Tổ chức dữ liệu:
Mảng A(N, N) thể hiện ma trận trọng số. Mảng V(N) là nhãn độ dài đường đi ngắn
nhất: V[i] là độ dài đường đi ngắn nhất từ đỉnh xuất phát x tới đỉnh i. D(N) là mảng
đánh dấu đỉnh đã có nhãn tối ưu. Gán cho D[i]=True khi v[i] đã là độ dài tối ưu của
đường đi từ x tới i, khi đó đỉnh i được gọi là đỉnh đã cố định nhãn. D[i]=false khi v[i]
chưa đạt giá trị tối ưu, khi đó đỉnh i được gọi là đỉnh tự do. Mảng TR(N) với ý nghĩa nếu
TR[i]:=j thì đỉnh j là đỉnh ngay trước đỉnh i trên hành trình ngắn nhất.
3.2. Thuật toán.
B1: Khởi trị
- Khởi trị nhãn đường đi ngắn nhất từ đỉnh x tới đỉnh i là V[i]:=A[x,i] (nếu không
có đường đi trực tiếp từ x tới i thì A[x,i] bằng vô cùng). Lưu lại đỉnh trước khi tới
i trên hành trình ngắn nhất là Tr[i]:=x
- Đánh dấu mọi đỉnh i là tự do (nhãn V[i] chưa tối ưu) bằng cách gán D[i]:=false
- Khởi trị nhãn đỉnh x là V[x]:=0; D[x]:=True (nhãn của đỉnh x đã tối ưu).
B2: (Vòng lặp vô hạn)
B2.1. Tìm đỉnh i
0
tự do có nhãn V[i
0
] nhỏ nhất.
B2.2. Nếu không tìm được i
0
(i
0
=0), hoặc i
0

=y thì thoát khỏi vòng lặp còn không
thì:
+ Đánh dấu i
0
đã được cố định nhãn: D[i
0
]:=true;
+ Sửa nhãn cho các đỉnh j tự do kề với i0 theo công thức V[j]=Min{V[j],
V[i
0
]+A[i
0
,j]}
+ Nếu V[j]=V[i
0
]+A[i
0
,j] thì lưu vết đỉnh trước j là i
0
: Tr[j]:=i
0
B3: Tìm và ghi kết quả
V[y] là độ dài đường đi ngắn nhất từ đỉnh x đến y. Lần ngược mảng Tr để tìm hành
trình này.
Một số lưu ý:
+ Nếu khỏi lặp vô hạn với i
0
=0 thì không tồn tại đường đi ngắn nhất từ x đến y.
+ Nếu B2 chỉ cho thực hiện lặp k lần thì nhãn của mỗi đỉnh i sau khi thoát khỏi vòng
lặp chính là độ dài đường đi ngắn nhất từ x tới i qua k đỉnh.

20
+ Mỗi lần sửa nhãn có thêm một đỉnh được cố định nhãn. Do đó số bước lặp của B2
trong thuật toán trên tối đa là N.
Chương trình tìm đường đi ngắn nhất trên đồ thị theo thuật toán của Dijkstra.
Const fi=’dijkstra.inp’;
fo=’dijkstra.out’;
Max=100;
Vc=100*50*maxint
Type m1=array[1 max, 1 max] of longint;
m2=array [1 max] of longint;
m3=array[1 max] of byte;
Var a:m1;{ma tran trong so}
V:m2; {nhan do dai duong di ngan nhat}
t, d:m3; {t: ghi nhan dinh truoc cua 1 dinh, d:danh dau dinh co nhan toi uu}
m:integer;
n,x,y:byte;
(*===============================*)
Procedure Doctep;
Var f:text;
k.w:integer;
i,j:byte;
Begin
Assign(f, fi);reset(f);
Fillchar(a,sizeof(a),0); {mang A la ma tran trong so}
Readln(f, n, m, x, y);{so dinh, so canh, dinh xuat phat, dinh dich}
For k:=1 to m do
Begin
Readln(f, i, j, w);{canh (i,j) co trong so la w}
a[i,j]:=w; a[j,i]:=w;
end;

close(f);
end;
(*===============================*)
Procedure khoitao; {khoi tao 1 so bien va mang can thiet}
Var i,j:byte;
Begin
For i:=1 to n do
For j:=1 to n do
If a[i,j]=0 then a[i,j]:=vc;
Fillchar(d, sizeof(d), 0); {cac dinh chua co nhan toi uu goi la cac dinh con tu do}
For i :=1 to n do v[i]:=a[x, i];
21
v[x]:=0;
fillchar(t, sizeof(t),0) ; {mang t dung de luu dinh truoc dinh hien tai}
end ;
(*===============================*)
Function tim_vmin:byte;{tim dinh tu do co nhan do dai duong di nho nhat}
Var li, i:byte;
P:longint;
Begin
Li:=0;
P:=vc;{khoi tri gia tri nho nhat can tim trong cac nhan v[i] voi i la dinh tu do}
For i:=1 to n do
If d[i]=0 then {i la dinh tu do}
If v[i]<p then
Begin
P:=v[i];
Li:=i;
End;
Tim_vmin:=li;

End;
(*===============================*)
Procedure SuaNhan(j); {sua lai nhan cac dinh j tu do, ke voi dinh i}
Var i:byte;
Begin
d[i]:=1;
for j:=1 to n do
if d[j]= 0 then {j la dinh tu do}
if (a[i,j]>0) and (a[i,j]<>vc) then {dieu kien j la dinh ke cua i}
if v[j]> v[i]+a[i,j] then {dieu kien co the sua nhan}
begin
v[j]:=v[i]+a[i,j]; {sua nhan}
t[j]:=i; {dinh ngay truoc dinh j trong hanh trinh ngan nhat la dinh i}
end;
end;
(*===============================*)
Procedure Dijkstra;
Var i:byte;
Begin
Khoitao;
Repeat
i:=tim_vmin;{dinh tu do co dinh nho nhat}
22
If i=0 then break;
SuaNhan(i);
Until (d[y]>0);
End;
(*===============================*)
Procedure Ghiketqua;
Var f:text;

i,j:byte;
kq:m3;
Begin
assign(f, fo); rewrite(f);
if v[y]=vc then {khong co duong di tu x den y}
begin
writeln(f,-1);
close(f);
halt;
end;
writeln(f, v[y]); {do dai duong di ngan nhat tu x den y la v[y]}
i :=y ; {lan nguoc hanh trinh ngan nhat bat dau tu y ve x}
j :=0 ;
repeat
inc(j) ;
kq[j]:=i; {luu hanh trinh nguoc vao mang kq}
i:=t[i];
until i=0; {dieu kien da ghi nhan xong dinh ngay truoc dinh x}
for i:=j downto 1 do writeln(f, kq[i]); {dua ra hanh trinh ngan nhat}
close(f);
end;
BEGIN
Doctep;
Khoitao;
Dijkstra;
Ghiketqua;
END.
23
Một số ví dụ áp dụng.
Nhiều bài toán thực tế quan trọng có thể dẫn về bài toán tìm đường đi ngắn nhất

trên đồ thị. Các bài toán dễ nhận thấy nhất là: chọn một hành trình đi tiết kiệm nhất,
lập lịch thi công các công đoạn trong một công trình thi công lớn, bài toán lựa chọn
đường truyền tin với chi phí nhỏ nhất trong mạng thông tin, bài toán tìm đường đi của
Robot….Hiện nay có rất nhiều phương pháp để giải các bài toán như vậy. Thế nhưng,
thông thường các thuật toán được xây dựng dựa trên cơ sở lý thuyết đồ thị tỏ ra là các
thuật toán có hiệu quả cao nhất. Sau đây xin đưa ra vài ví dụ minh họa cho điều này
(Dưới đây tôi đưa ra 2 ví dụ về hai bài toán khó mà giải theo thuật toán tìm
đường đi ngắn nhất hiệu quả)
Ví dụ 1: Chọn xâu
Cho một số nguyên k (0=<k<=255) và N xâu kí tự có độ dài L (N nguyên, 0<N<=100,
L<=255) là S
1
, S
2
, ,S
N
đôi một khác nhau chỉ gồm các chữ cái thường. Hãy tìm xâu H
nhỏ nhất thỏa mãn tính chất sau đây : tồn tại k vị trí khác nhau trên xâu H là vị trí xuất
hiện của một trong các xâu S
1
, S
2
, , S
N
(p là vị trí xuất hiện của xâu S trong H nếu hàm
Copy(H, p, L))=S).
Dữ liệu vào : File XAU.INP :
- Dòng đầu tiên ghi N, L, K.
- N dòng tiếp theo : dòng thứ i ghi xâu Si
Kết quả : Ghi ra file XAU.OUT :

- Dòng đầu ghi độ dài nhỏ nhất.
- Dòng thứ 2 ghi xâu H thỏa mãn bài toán
- K dòng tiếp theo, mỗi dòng thể hiện một vị trí xuất hiện gồm 2 số u, p cho biết
xâu S
u
xuất hiện tại vị trí p trên H.
VD :
XAU.INP XAU.OUT
2 10 2
aaaaaaaxyz
xyzabcdefg
17
aaaaaaaxyzabcdefg
1 1
2 8
Cách giải : Đây là một bài toán khó. Nếu chúng ta giải theo cách thông thường sẽ rất
phức tạp, chương trình sẽ rất dài và dễ sai khi thử các bộ test khác nhau.
Cách giải theo lý thuyết đồ thị như sau :
Trước hết xây dựng đồ thị : Mỗi đỉnh là một xâu trong các xâu S
1
, S
2
, , S
N
. Để tìm
trọng số cung (i, j) là a[i,j] ta làm như sau : Đặt phần cuối của S
i
trung với phần đầu của
S
j

sao cho đoạn trùng nhau của chúng dài nhất. Khi đó độ dài đoạn còn lại của Sj không
trùng với S
i
được chọn là a[i,j]. Ví dụ : S
i
=’bcyaabaaaab’ và S
j
=’aabaaaabhk’ thì đoạn
không trùng nhau ngắn nhất của S
j
là ‘bhk’ do đó a[i,j]=3. Bài toán trở thành tìm đường
đi ngắn nhất qua k đỉnh (độ dài đường đi này chính là độ dài xâu H). Để giải bài toán
này, có thể tiến hành xây dựng nhãn F cho mỗi đỉnh, đó là độ dài đường đi ngắn nhất
24
qua k đỉnh và kết thúc tại mỗi đỉnh đó. Ban đầu xét các đường chỉ gồm 1 đỉnh, đương
nhiên nhãn mỗi đỉnh là độ dài xâu tương ứng với đỉnh đó. Giả sử các đỉnh đã có nhãn ở
bước trước là độ dài đường đi ngắn nhất gồm t đỉnh (t<k), để xây dựng nhãn các đỉnh ở
bước trước là độ dài đường đi ngắn nhất gồm t+1 đỉnh thì cần sửa lại nhãn các đỉnh đó ở
bước trước (qua t đỉnh) bằng công thức :
∀j : F[j]
bước t+1
:=Min{F[i]
bước t
+a[i,j] | ∀i}

Ví dụ 2: K đường đi ngắn nhất.
Vùng đất X có N thành phố (4≤N≤300) được đánh số từ 1 đến N. Giữa 2 thành phố
có thể có đường nối trực tiếp hoặc không. Các con đường này được đánh số từ 1 đến M
(1≤M<2000). Ban lãnh đạo thể dục thể thao vùng X tổ chức cuộc chạy đua tiếp sức
“thông minh” theo quy luật sau:

+ Thành phố xuất phát là thành phố 1, thành phố đích là thành phố N.
+ Mỗi đội thi đấu có K người dự thi. Lần lượt từng người chạy từ thành phố 1 về
thành phố N.
+ Khi người thứ 1 đến được thành phố N thì người thứ 2 mới bắt đầu rời khỏi thành
phố 1,…, người thứ i đến thành phố N thì người thứ i+1 mới bắt đầu rời khỏi thành phố
1,…, (i<k). Người thứ K về tới đích tại thời điểm nào thì thời điểm đó được coi là thời
điểm về đích của toàn đội.
+ Đường chạy của các đội viên không được giống nhau hoàn toàn.
+ Có thể chạy lại đoạn đường đã chạy.
Hãy viết chương trình tính thời gian nhỏ nhất để một đội hoàn thành cuộc chạy đua
tiếp sức nêu trên nếu các vận động viên có tốc độ chạy như nhau.
Dữ liệu vào: File văn bản PATHK.INP
- Dòng đầu tiên ghi 3 số K, N, M
- M dòng tiếp theo, mỗi dòng chứa 3 số nguyên i, j, w thể hiện một đường
đi trực tiếp giữa 2 thành phố i và j mất thời gian chạy là w (đơn vị thời gian,
1≤w≤9000).
Kết quả: Ghi ra file văn bản PATHK.OUT:
- Dòng thứ 1 chứa 1 số nguyên duy nhất là thời gian chạy nhỏ nhất của một đội.
- K dòng tiếp theo, mỗi dòng thể hiện hành trình chạy của một vận động viên trong
đội là dãy số hiệu các thành phố liên tiếp trên hành trình đó.
Cách giải: Ta xem vùng đất X là một đồ thị vô hướng gồm N đỉnh. Đường nối giữa
2 thành phố thể hiện cạnh của đồ thị.
Lúc đó bài toán được giải theo thuật toán Dijkstra cải tiến như sau:
Gọi L[i,j] là độ dài đường đi thứ j trong k đường đi ngắn nhất từ đỉnh 1 đến đỉnh i
(i=1, 2, …, N; j=1, 2,…, k). Khởi tạo L[i,j] bằng vô cùng với mọi i, j, L[1,1] bằng 0.
- Mỗi lần tìm một cặp (ii,jj) chưa đánh dấu có nhãn L[ii,jj] nhỏ nhất.
- Từ (ii,jj) sửa nhãn cho (i,j) nếu các đỉnh i kề với đỉnh ii: đường ngắn nhất thứ j
tới đỉnh i sẽ thành đường ngắn nhất thứ j+1 tới i nếu L[ii,jj]+ C[ii,i] > L[i,j] (*)
và đường ngắn nhất thứ j tới i sẽ thành đường đi qua ii trước, rồi tới i. Do đó khi
25

×