dinh ke voi i phai co mau khac i }Tryk+1; Tra_trang_thaii,k; end; if ok = false { incsomau; Dat_trang_thaisomau,k; Tryk+1; Tra_trang_thaisomau,k; Decsomau; } End; Tuy nhiên với dữ liệu l
Trang 1ài toán tô màu rất quen thuộc với tất cả các bạn Mà bài toán điển hình chính là bài toán 4 màu được phát biểu như sau : Mọi bản đồ trên mặt phẳng đều có thể tô bằng 4 màu sao cho không có hai nước láng giềng nào lại bị tô cùng 1 màu Vậy chúng ta sẽ mở rộng bài toán trên thành một bài toán tổng quát mà hầu hết chúng ta đều đã quen thuộc :
Cho một đồ thị vô hướng N đỉnh ( N <= 200 ), mỗi đỉnh được nối với 1 số đỉnh khác bằng 1 cung nối trực tiếp duy nhất ( không có 2 đỉnh nào có nhiều hơn 1 đường nối trực tiếp ) Bài toán đặt ra là : Hãy tô màu các đỉnh sao cho không có hai đỉnh nào có 2 màu giống nhau mà lại nối trực tiếp với nhau với số màu cần tô là ít nhất
Bài toán trên có rất nhiều cách giải Trong đó cách đơn giản nhất để giải bài toán trên là Duyệt có chặn nhánh Thuật toán trên khá hiệu quả Có thể phát biểu qua cách trên như sau :
Procedure try(k : integer) ;
Var ok : boolean;
Begin
If ( k > N) then
Begin
Cap_nhat_min;
Exit;
End;
Ok := false;
For i := 1 to somau do
If To_duoc_mau(i,k) then
Begin
Ok := true;
Dat_trang_thai(i,k); { Ghi nhan i co mau k, va danh so cac
Trang 2dinh ke voi i phai co mau khac i }
Try(k+1);
Tra_trang_thai(i,k);
end;
if (ok = false) {
inc(somau);
Dat_trang_thai(somau,k);
Try(k+1);
Tra_trang_thai(somau,k);
Dec(somau);
} End;
Tuy nhiên với dữ liệu lớn một chút thì thuật toán Duyệt sẽ không tối ưu về mặt thời gian Do đó chúng ta sẽ áp dụng thuật toán tham lam để giải bài toán trên
Thuật toán 1 : Tư tưởng chặt nhị phân rồi tham lam:
Dau = 0;
Cuoi = N; { số màu tối đa có thể tô }
While (cuoi > dau) do
Begin
Lim := (dau + cuoi ) div 2;
Trang 3If (To_duoc) then cuoi = lim else dau = lim;
End;
Lim = cuoi; Chính là đáp án cần tìm
Trongthủ tục To_duoc bạn áp dụng thuật toán tham lam Có nhiều cách cho bạn lựa chọn Sau đây là một cách : Tại mỗi bước bạn tìm tập có nhiều đỉnh nhất thỏa mãn và tô màu cho chúng Muốn tìm như vậy mỗi lần bạn tìm đỉnh kề với ít đỉnh nhất mà không kề với đỉnh đã có trong tập đang xét ta cập nhật đỉnh đó vào tập đỉnh có thể tô màu Cứ làm như vậy, nếu số tập tìm được > Lim thì sẽ không To_duoc còn ngược lại số tập <= Lim thì có thể To_duoc Duyệt chặn nhánh thì có thể ra kết quả tối ưu nhưng không đáp ưng nhu cầu về thời gian trong trường hợp N lớn Còn làm theo thuật toán tham lam thứ nhất thì kết quả sẽ không tối ưu trong nhiều trường hợp
Để khắc phục cả 2 khuyết điểm của 2 thuật toán trên, tôi xin đưa ra một thuật toán dựa trên tư tưởng tham lam, nhưng với tư tưởng khá mới mẻ, và thuật toán trên tương đối chuẩn xác
Dưới đây là tư tưởng thuật toán:
-Trước hết ta định nghĩa bậc của một đỉnh làsố đỉnh trên đồ thị chưa được tô mầu
mà đỉnh này được nối trực tiếp với đỉnh đang xét bằng một cạnh nối trực tiếp Nếu đỉnh đó không được nối với bất kỳ đỉnh nào thì bậc của đỉnh đó là 0
-Bước 1 : Tìm đỉnh có bậc cao nhất trên đồ thị ( gọi là đỉnh u )
-Bước 2 : Tăng số màu cần tô lên 1 và tô màu cho đỉnh vừa tìm
-Bước 3 : Tìm đỉnh v để tô màu giống u thỏa mãn các điều kiện sau : v đi đến đỉnh u thông qua duy nhất 1 đỉnh w khác và v có số bậc lớn nhất Nếu không tìm được đỉnh như vậy ta sẽ tìm đỉnh v có số bậc lớn nhất mà không kề với u Nếu tìm được v thỏa mãn thì ta tô màu cho v chính là màu của đỉnh u Sau đó nhập đỉnh u
và v vào làm 1 đỉnh Tức : đỉnh w kể với v thì cũng coi như kề với u ( a[v,w] =1 a[u,w] =1 and a[w,u] = 1 )
Trang 4-Bước 4 : Lập lại bước 3 cho đến khi v = 0.
-Bước 5 : Lặp lại bước 1 cho đến khi tô hết mầu các đỉnh của đồ thị
Sau đây là đoạn chương trình mô tả thuật toán trên :
{$Q+,R+,B-,N+}
var
somau: integer; { Lưu trữ số màu cần tô }
N : integer; { Số đỉnh của đồ thị }
color : array[1 100] of integer; { Mảng lưu mầu của đỉnh đồ thị, color [i] = 0 nghĩa là đỉnh i chưa được tô màu }
a : array[1 100,1 100]of integer; { a[u,v] = 1 tức hai đỉnh u và c có cạnh nối, a[u,v] = 0 tức hai đỉnh không có cạnh nối}
count : integer; { Đếm số đỉnh đã tô màu ,Count = N ngừng chương trình } function dinh_bac_max : integer; { Hàm tìm ra đỉnh có bậc lớn nhất }
var max,dem,i,j,u : integer;
begin
max := -1;{Vi co the co dinh co bac la 0}
for i :=1to N do
if color[i] = 0 then { Xét các đỉnh chưa được tô màu để tìm ra đỉnh bậc lớn nhất } begin
dem := 0;
Trang 5for j :=1 to n do
if (color[j] = 0) and (a[i][j] = 1) then
inc(dem);
if (dem > max) then { Cập nhật giá trị lớn nhất }
begin
max := dem;
u := i;
end;
end;
dinh_bac_max := u;
end;
procedure tomau(u : integer);
begin
count := count + 1;
color[u] := somau;
end;
procedure tim_dinh_cung_mau(u : integer; var v : integer);
var i,j,k,max,dem : integer;
begin
max := 0; { Max ở đây phải gán là 0 , do nếu tồn tại v thì bậc lớn nhất của v phải
Trang 6> 0 }
for i := 1 to n do
if (color[i] = 0) and (a[u][i] = 0) then { Xét các đỉnh mà u không kề mà đi đến u qua duy nhất 1 đỉnh khác }
begin
dem := 0;
for j := 1 to n do
if (color[j] = 0) and (a[i][j] = 1 ) and (a[u][j] = 1) theninc(dem);
if (dem > max) then { Cập nhật giá trị max }
begin
max := dem;
v := i;
end;
end;
if (v > 0) then exit; { Nếu tồn tại v chưa tô màu đi đến được u thông qua duy nhất
1 đỉnh khác }
max := -1; { Ở đây max phải = 0 vì có thể tồn tại v mà bậc chỉ là 0 }
for i :=1 to n do
if (color[i] = 0) and (a[u][i] =0) then { Xét các đỉnh i không kề với u và chư được
tô màu với số đỉnh lớn nhất }
begin
Trang 7dem := 0;
for j := 1 to n do
if (color[j] = 0) and (a[i][j] = 1) then inc(dem);
if (dem > max) then { Cập nhật giá trị lớn nhất }
begin
max := dem;
v := i;
end;
end;
end;
procedure Nhapdinh(u,v : integer); { Nhập 2 đỉnh u và v coi như một đỉnh } var i : integer;
begin
for i := 1 to n do
if a[v,i] = 1 then { Đỉnh i kề với đỉnh v thì coi như nó kề với đỉnh u } begin
a[u,i] = 1;
a[i,u] = 1;
end;
end;
Trang 8procedure To_Mau; { Thủ tục tô màu để tìm ra số màu ít nhất }
var u, v : integer;
begin
somau := 0;
repeat
somau := somau+1;
u := dinh_bac_max; { u là đỉnh có bậc lớn nhất }
tomau(u); { gán màu cho đỉnh u }
repeat
tim_dinh_cung_mau(u,v); { Tìm đỉnh v có thể tô màu giống mầu của u }
if (v > 0) then
begin
tomau(v); { Gán mầu cho đỉnh v }
nhap_dinh(u,v); { Nhập 2 đỉnh u,v coi như một đỉnh }
end;
until (v = 0);
until (count = n);
end;
Thuật toán trên chạy với tốc độ khá nhanh và rất hiệu quả Độ chính xác của thuật toán trên là rất cao nếu không nói là thuật toán chuẩn Trong trường hợp tồi tệ
Trang 9nhất cài đặt theo ma trận kề, độ phức tạp của thuật toán trên là O(n3).
Để cải tiến tốc độ cho thuật toán trên ta có thể sử dụng danh sách kề hoặc danh sách liên kết Khi đó độ phức tạp thuật toán chỉ cỡ khoảng O(n2log(n)) Muốn cải tiến hơn nữa thì tại mỗi bước ta cập nhật số đỉnh kề của một đỉnh để tăng tốc độ của hàm tìm đỉnh max ( u )và đỉnh tô màu cùng với u ( v ) Khi đó độ phức tạp thuật toán chỉ khoảng O(N2) Tuy nhiên cài đặt khá rắc rối Theo tôi cách tốt nhất
là bạn cài đặt theo danh sách kề ( hoặc danh sách liên kết ) độ phức tạp là O(n2log(n));
Sau đây là một vài bài toán ứng dụng của thuật toán nêu trên:
Bai 1 :XẾP LỊCH THI
Một số trường đại học tổ chức học theo tín chỉ Nếu sinh viên tích lũy đủ số chứng chỉ cho một số môn quy định của một ngành là có quyền nhận bằng tốt nghiệp của ngành đó Đối với các đại học như thế, việc học và thi không tổ chứa theo lớp mà theo các môn học Hàng năm nhà trường thông báo các môn sẽ học
để sinh viên tự đăng ký học các môn học theo ngành mình chọn Cuối năm nhà trường tổ chức thi cho các môn đã giảng trong năm Mỗi môn thi trong một ngày nhưng trong một ngày có thể tổ chức thi nhiều môn Do một sinh viên có thể đăng
ký thi nhiều môn nên lịch thi cần phải bố trí để nếu có một sinh viên đăng ký thi hai môn nào đó thì các môn đó không được thi cùng ngày
Yêu cầu : Hãy xếp lịch thi sao cho tổng số ngày thi càng ít càng tốt.
Input : LICHTHI.INP
+ M , N : lần lượt là số thí sinh và số môn học ( M <= 1000, N <= 100);
+ M dòng tiếp theo ghi thông tin về đăng ký thi của mỗi sinh viên, mỗi dòng gồm
N số 0 hoặc số 1 cách nhau một dấu cách, số thứ j trong dòng thứ i là 1 nếu sinh viên thứ i đăng ký học và thi môn thứ j và bằng 0 nếu sinh viên thứ i không đăng
ký thi môn thứ j
Trang 10Output : LICHTHI.OUT
+ Gồm N dòng mỗi dòng có một số tự nhiên Số ở dòng thứ i là ngày thi tương ứng đối với môn thứ i
Ví dụ :
LICHTHI.INP LICHTHI.OUT
10 8
1 0 1 1 1 0 0 0
0 1 1 0 1 1 0 0
1 0 0 0 1 0 1 1
0 1 1 0 1 0 1 0
1 0 0 0 1 1 0 1
0 0 1 1 1 0 0 0
0 0 0 1 1 0 0 1
0 1 1 0 1 1 0 0
1 0 0 0 1 0 1 1
0 1 0 0 1 1 0 1
1 1 2 3 4 3 3 2
Gợi ý : Tư tưởng thuật toán của bài toán trên là đưa về bài toán tô màu đồ thị
Ta xây dựng đồ thị như sau :
+ Mỗi đỉnh của đồ thị là một môn học ( <= 100 )
+ Hai đỉnh của đồ thị được gọi là có cạnh nối nếu có một sinh viên tham gia học 2 môn đó
Trang 11Như vậy ta xây dựng được một đồ thị vô hướng N đỉnh, trong đó a[i,j] = 1 nếu
có cạnh nối, a[i,j] = 0 nếu không có cạnh nối
Sau khi xây dựng được đồ thị trên ta áp dụng thuật toán trên Ta sẽ tìm ra được kết quả ( số ngày ít nhất cần tổ chức thi )
Trên đây tôi đã trình bày cho các bạn một giải thuật của bài toán tô màu Hi vọng sẽ giúp ích cho các bạn Có gì thắc mắc các bạn có thể liên hệ với tôi theo địa chỉ mail :
khanhtn09@yahoo.com Chúc các bạn thành công !!!
School@net