BÀI TOÁN TÌM ĐƯỜNG ĐI NGẮN NHẤT
(The Traveling Salesman Problem - TSP)
I/ GIỚI THIỆU BÀI TOÁN
Đây là một bài toán cổ điển: Một thương gia phải đi qua nhiều thành phố. Hãy vạch lộ
trình đi qua tất cả các thành phố đó sao cho quãng đường đi là ngắn nhất. Biết rằng mỗi
thành phố chỉ đi qua một lần.
Bài toán TSP khó giải quyết, vì để tìm được lời giải ta phải tiến hành tìm kiếm trên tất cả
lộ trình có thể, và như vậy dẫn tới phí tổn thời gian tính toán rất lớn. Backtracking và các
kỹ thuật khác có thể rút ngắn phạm vi tìm kiếm trong một số điều kiện nhưng vẫn chỉ là sự
hoàn thiện của giải pháp tìm kiếm toàn diện. Khoa học máy tính vẫn chưa tìm ra được một
giải thuật cụ thể có hiệu quả để giải những bài toán tương tự như TSP có kích thước lớn.
Giải thuật Gene tỏ ra hiệu quả trong việc giải các bài toán có thông tin không đầy đủ. Vì
vậy giải thuật này sẽ được sử dụng để hiện thực chương trình.
II/ CÁC KỸ THUẬT GHÉP CHÉO MỚI
Giải thuật Gene mô phỏng sự tiến hoá của thế giới tự nhiên, ở đó thông tin về một cấu trúc
sống được mã hoá dưới dạng nhiễm sắc thể. Do đó để áp dụng giải thuật Gene vào bài toán
TSP, ta cũng phải tìm cách mã hoá các lời giải của bài toán dưới dạng các chuỗi nhiễm sắc
thể và xây dựng qui tắc đánh giá độ thích nghi của nó theo ngữ cảnh của bài toán.
Kí hiệu các thành phố là A, B, C, ... mỗi nhiễm sắc thể - sự mã hoá của lời giải - sẽ là một
danh sách hoán vị của A, B, C ... biểu diễn lộ trình mà người thương gia đã đi qua. Thí dụ
GFHBACDE sẽ là kí hiệu của hành trình từ G -> F -> H -> ... -> E. Mỗi thành phố (gene)
sẽ chỉ xuất hiện trong danh sách này chỉ một lần. Vấn đề này dẫn tới khó khăn không thể
áp dụng các kĩ thuật sinh sản, đột biến thường được sử dụng trong giải thuật Gene chuẩn.
Thí dụ: Với ý nghĩa ghép chéo thông thường ta nhận được từ cặp bố mẹ 1, 2 các con cái
sau:
- Ghép chéo tại một điểm:
Bố mẹ Con cái
----------------------------------------------------
#1 ABC|DEFGH ABC|BACDE
#2 GFH|BACDE GFH|BACDE
- Ghép chéo tại hai điểm:
Bố mẹ Con cái
----------------------------------------------------
#1 AB|CDE|FGH AB|HBA|FGH
#2 GF|HBA|CDE GF|CDE|CDE
Điều kiện chỉ xuất hiện một lần trong danh sách hoán vị ở hai thí dụ trên đã bị vi phạm. Do
đó đòi hỏi phải xây dựng các kĩ thuật ghép chéo mới phù hợp với ý nghĩa của bài toán.
2.1- GHÉP CHÉO RIÊNG PHẦN (Partially matched crossover - PMX)
PMX được cải tiến từ phương pháp ghép chéo hai điểm thông thường. Bắt đầu bằng việc
chọn ra hai điểm, thí dụ 2 và 5:
Cha mẹ 1: AB|CDE|FGH
Cha mẹ 2: GF|HBA|CDE
Giải thuật PMX nhận thấy gene H có trong nhiễm sắc thể 2 sẽ được thay thế bởi gene C
trong nhiễm sắc thể 1, vì vậy nó sẽ chuyển đổi H của nhiễm sắc thể 1 thành C và C trong
nhiễm sắc thể 2 sang H. Quá trình được thực hiện tương tự với hai gene còn lại và cuối
cùng nhận được các con cái:
Con cái 1: ED|HBA|FGC
Con cái 2: GF|CDE|HBA
Mỗi con cái là một hoán vị mới, hoán chuyển một số gene trong khi vẫn giữ nguyên các
đoạn cấu trúc của bố mẹ.
2.2- GHÉP CHÉO CÓ THỨ TỰ (Order crossover - OX)
OX dời chỗ một số gen và dịch chuyển các gene khác. Lấy ví dụ ở trên, với kí hiệu "-" biểu
thị gene bỏ trống ở vị trí đó:
Cha mẹ 1: AB|CDE|FGH
Cha mẹ 2: GF|HBA|CDE
Con cái 1: --|HBA|FG-
Con cái 2: GF|CDE|---
tiếp theo, ở sau điểm ghép chéo thứ hai, OX sẽ dịch các gene sang trái (có thể coi các đầu
mút của nhiễm sắc thể nối với nhau nếu cần thiết), điền các vị trí trống của gene và để
trống đoạn hoán đổi:
Con cái 1: BA|---|FGH
Con cái 2: DE|---|GFC
Cuối cùng OX sẽ hoán đổi đoạn ghép chéo nằm giữa hai điểm, tạo ra các con cái mới:
Con cái 1: BA|CDE|FGH
Con cái 2: DE|HBA|GFC
Trong khi PMX giữ nguyên vị trí của gene trong chuỗi nhiễm sắc thể, thì OX lại duy trì thứ
tự các gene trong chuỗi.
2.3- GHÉP CHÉO CÓ CHU KÌ (Cycle crossover - CX)
Nguyên tắc làm việc của CX hoàn toàn khác, nó thực hiện việc hoán đổi một tập hợp cụ
thể các gen. Thí dụ:
Cha mẹ 1: ABCDEFGH
Cha mẹ 2: GFHBACDE
Để tạo con cái, CX bắt đầu với các thành phố đầu tiên trong các nhiễm sắc thể bố mẹ:
Con cái 1: G-------
Con cái 2: A-------
Tìm trong cha mẹ 1, CX thấy G ở vị trí 7 và thực hiện hoán đổi ở vị trí đó:
Con cái 1: G-----D-
Con cái 2: A-----G-
Quá trình tìm và hoán đổi tiếp tục cho đến khi gene bị thay thế đầu tiên trong cha mẹ 1, A,
được bắt gặp. Cuối cùng con cái nhận được như sau:
Con cái 1: GECBAFDH
Con cái 2: ABHDECGE
2.4- PHÉP ĐẢO VỊ TRÍ (Inversion operator)
Để tăng tính đa dạng có thể đưa thêm vào bài toán phép hoán vị, thực hiện việc đảo ngược
vị trí các gene trong chuỗi nhiễm sắc thể. Thí dụ:
ABC|DEFGH đảo thành ABCHGFED
G|FHB|ACDE đảo thành GBHFACDE
2.5- PHÉP ĐỘT BIẾN
Được định nghĩa lại dưới dạng hoán vị vị trí của hai gene. Thí dụ: Hoán vị các gene A và E
trong chuỗi ABCDEFGH sẽ cho con cái là EBDEAGFH.
III/ SƠ ĐỒ GIẢI THUẬT GEN CỦA BÀI TOÁN TSP
Sau khi xây dựng phép mã hoá dưới dạng nhiễm sắc thể và định nghĩa các phép toán sinh
sản, có thể đưa ra sơ đồ giải thuật Gene để hiện thực chương trình:
PHÉP HOÁN VỊ JOSEPHUS
Theo sơ đồ, cộng đồng ban đầu được hình thành bằng phép hoán vị Josephus. Giả sử có
một danh sách các thành phố được kí hiệu từ A tới H, có thể tạo một hoán vị bằng việc
chọn điểm xuất phát và một số đếm - thí dụ F và 5. Giả thiết xem danh sách có dạng vòng
tròn bằng cách nối liền hai đầu mút, đếm 5 thành phố ở bên phải F ta được C - thành phố
đầu tiên trong danh sách hoán vị. Tiếp tục từ C đếm 5 thành phố khác sẽ được thành phố
thứ hai trong danh sách hoán vị - H. Lập lại quá trình này cuối cùng ta nhận được danh
sách hoán vị CHFEGBDA.
Như vậy bằng cách chọn ngẫu nhiên điểm bắt đầu và số đếm, chương trình có thể tạo ra
một tập gồmcác danh sách hoán vị khác.
IV/ HIỆN THỰC CHƯƠNG TRÌNH
Số thành phố được chọn trước là 10 , kí hiệu A, B, C, .... mà không tạo phần input.
Population: Số thành viên (lộ trình tìm kiếm) cần
kiểm tra. Việc giữ lại thành viên tốt
nhất của thế hệ cha mẹ trong thế hệ con
cái, và sự tham gia của 2 cá thể cha mẹ
trong các phép toán sinh sản đòi hỏi
Population phải là số lẻ.
Generations: Số thế hệ cần kiểm tra.
Report Frequency: Bao nhieu thế hệ thì thông báo kết quả
một lần?
Show Best: Số nhiễm sắc thể được xuất cho từng
lần thông báo.
Oper Probability: Xác xuất các phép toán sinh sản được
sử dụng khi tạo con cái.
Mutation, Inversion, PM Crossover, Order
Crossover:
Nếu đánh dấu checkbox thì các phép
toán sinh sản tương ứng sẽ được chọn
theo xác xuất tương ứng.
Linear Normalization Các thông số này dùng để điều chỉnh lại
các fitness của các thành viên trong
cộng đồng vì chúng có thể chênh lệch
không nhiều.
Sơ đồ giải thuật Gene của bài toán TSP ở trên có thể hiện thực bằng đoạn chương trình
chính sau đây:
typedef size_t CityChrom[10];
static const size_t CSZ = 10 * sizeof(size_t);
const size_t POP_SZ = PopSize;
static const char *cityName[10] =
{
"A ", "E ",
"G ", "I ",
"C ", "F ",
"D ", "H ",
"B ", "E "
};
static const double distance[10][10] =
{
{0.0, 220.0, 90.0, 155.0, 133.0, 123.0, 182.0, 89.0, 105.0, 141.0},
{220.0,0.0, 135.0, 55.0, 173.0, 117.0, 124.0, 122.0, 222.0, 85.0},
{90.0, 135.0, 0.0, 92.0, 69.0, 34.0, 95.0, 56.0, 98.0, 57.0},
{155.0,55.0, 92.0, 0.0, 145.0, 84.0, 116.0, 68.0, 184.0, 54.0},
{133.0,173.0, 69.0, 145.0, 0.0, 60.0, 72.0, 125.0, 70.0, 91.0},
{123.0,117.0, 34.0, 84.0, 60.0, 0.0, 61.0, 76.0, 106.0, 33.0},
{182.0,124.0, 95.0, 116.0, 72.0, 61.0, 0.0, 134.0, 137.0, 66.0},
{89.0, 122.0, 56.0, 68.0, 125.0, 76.0, 134.0, 0.0, 142.0, 73.0},
{105.0,222.0, 98.0, 184.0, 70.0, 106.0, 137.0, 142.0, 0.0, 139.0},
{141.0,85.0, 57.0, 54.0, 91.0, 33.0, 66.0, 73.0, 139.0, 0.0}
};
if (distance==NULL)
MessageBox("Khong du bo nho");
//tạo các vùng lưu trữ cộng đồng và mảng fitness
CityChrom *pop = new CityChrom[POP_SZ];
CityChrom *newpop = new CityChrom[POP_SZ];
double *fit = new double[POP_SZ];
// các pointers dùng cho việc sắp xếp mảng fitness
CityChrom *ptrp = pop - 1;
double * ptrf = fit -1;
// khai báo các biến
size_t g, i, j, k, l, n, s, t, p1, p2, inc;
double vf;
CityChrom vp;
RouletteWheel *rw;
RandDev devgen;
// bánh xe roulette dùng chọn phép toán sinh sản
double operwt[5];
if (Mutation)
operwt[0] = WeightM;
else
operwt[0] = 0.0;
if (Inversion)
operwt[1] = WeightI;
else
operwt[1] = 0.0;
if (PMX)
operwt[2] = WeightP;
else
operwt[2] = 0.0;
if (CX)
operwt[3] = WeightC;
else
operwt[3] = 0.0;
if (OX)
operwt[4] = WeightO;
else
operwt[4] = 0.0;
RouletteWheel ow(5,operwt);
// khởi tạo cộng đồng ban đầu bằng phép hoán vị Josephus
for (i = 0; i < POP_SZ; ++i)
{
int plist[10];
memset(plist,0,CSZ);
s = size_t(devgen() * 8.0) + 1;
j = size_t(devgen() * 10.0);
k = 0;
while (1)
{
pop[i][k] = j;
plist[j] = 1;
if (k == 9)
break;
for (l = 0; l < s; ++l)
{
do
{
++j;
if (j>9) j = 0;
}
while (plist[j] == 1);
}
++k;