CHƯƠNG 3. MỘT SỐ BÀI TOÁN ỨNG DỤNG TRONG THỰC TẾ
3.1 BÀI TOÁN ĐIỀU HÀNH TAXI
3.1.2 Phân tích bài toán và xây dựng thuật toán
Bài toán điều hành Taxi đƣợc đặt ra là do khi c nhiều khách hàng c ng yêu cầu hãng Taxi cho xe tới đ n thì việc các xe di chuyển từ noi đỗ đến địa điểm đ n khách sẽ hao tốn một lƣợng nhiên liệu xác định và chi phí đ sẽ do hãng phải chi trả, điều đ làm ảnh hưởng đến doanh thu của hãng. Do đ , để h n chế tối đa những chi phí đ nhà điều hành phải lựa chọn và điều động các xe sao cho chi phí trên đường đến đ n khách là ít nhất.
Trước yêu cầu thực tế đ em đã chọn cách ứng dụng tin học giải quyết bài toán đ , giúp nhân viên điều động Taxi trong một thời gian rất ngắn đã c thể chọn cách di chuyển cho các xe Taxi của hãng để đ t đƣợc hiệu quả kinh tế cao nhất.
B to n đ ều n ax sử dụn t uật to n Tìm cặp ghép có tổng trọng số trên các cung là nhỏ nhất.
Bài toán đƣợc biểu diễn bằng mô hình đồ thị nhƣ sau:
Xây dựng đồ thị G = (V, E), trong đ :
Mỗi một đỉnh vi V của đồ thị đ i diện cho một nút giao thông
trong thành phố.
Mỗi c nh ei E nối hai đỉnh của đồ thị đ i diện cho đường đi giữa hai nút giao thông. C nh ei mang một trọng số biểu diễn khoảng cách giữa hai nút giao thông đƣợc nối bởi n .
T i mỗi nút giao thông c Taxi đỗ đƣợc thể hiện bởi một lá cờ c chữ Start, t i mỗi nút giao thông c khách hàng đứng đƣợc thể hiện bằng chữ Finish.
Khi thực hiện thuật toán, đường di chuyển của các Taxi được tô đậm từ điểm đỗ đến điểm đ n khách.
Kết quả của bài toán chính là bộ ghép c tổng trọng số nhỏ nhất tìm đƣợc.
* Mô tả thuật toán:
Bước 1:
+ Xây dựng ma trận chi phí C, với C[i,j] là trọng số nhỏ nhất để đi từ nút giao thông i đến nút giao thông j.
+ Ma trận T là ma trận chứa chỉ số của nút giao thông trung gian, với T[i,j]
<>0 T[i,j] chứa đỉnh trung gian trên quãng đường đi từ nút giao thông i đến nút giao thông j.
+ T o đồ thị hai phía biểu diễn bởi ma trận A (Old là phía các đỉnh c Taxi đỗ c k đỉnh, New là phía đỉnh c khách hàng đứng, c k đỉnh). Có A[i,j] là giá trị âm của trọng số quãng đường đi từ nút giao thông Old[i] đến nút giao thông New[j].
+ T o nhãn ban đầu chấp nhận đƣợc Fx và Fy theo quy tắc:
Fx[i] = Max( A[i,j], j: 1 <= j <= k) Fy[j] = 0 , j : 1 < = j < = k
(FX, FY gọi là chấp nhận đƣợc nếu thoả mãn bất đẳng thức Fx[i] + Fy[j] <=
A[ij])
Nếu Fx[i] + Fy[j] = C[i,j] thì ta coi c nh (Old[i], New[j]) là c nh đậm (là đã chọn đường cho Taxi đỗ t i nút giao thông O1d[i] đến đ n khách t i nút giao thông New[j]), các c nh còn l i là c nh nh t.
Vậy bằng cách t o nhãn ban đầu nhƣ trên chúng ta c cặp ghép M ban đầu (ghép đƣợc một số Taxi với khách hàng để c đƣợc tổng khoảng cách di chuyển là nhỏ nhất).
Bước 2:
For (i:=l k) Begin
Nếu còn Taxi chƣa đ n khách thì:
Tìm dây chuyền.
Nếu không c dây chuyền thì sửa nhãn.
Ngƣợc l i t ng cặp ghép trên dây chuyền này.
C o đến k tìm được dâ c u ền;
End;
Sửa nhãn: Phải thực hện khi không tìm được đường cho 1 Taxi nào đ đi đ n khách (ta gọi là dây chuyền ―dở dang‖). Trên các cung nối một đỉnh bên Old đã n p vào dây chuyền tới các đỉnh j thuộc New chƣa thuộc dây chuyền, chọn giá trị bé nhất trong các giá trị: Fx[i] + Fy[j] - A[i,j]. Giá trị này đƣợc chọn làm lƣợng sửa nhãn (kí hiệu là m). Sửa nhãn theo cách nhƣ sau:
Nhãn các đỉnh của Old thuộc dây chuyền sẽ giảm đi một lƣợng là m, nhãn các đỉnh của New thuộc dây chuyền sẽ t ng thêm một lƣợng là m, để đảm bảo Fx[i] + Fy[j] - A[i,j] >= 0. Sau khi các đỉnh thuộc dây chuyền ―dở dang‖
đã đƣợc sửa nhãn thì n c khả n ng mới kết
hợp với các đỉnh j bên New t o nên một dây chuyền hoàn chỉnh (vì sẽ xuất hiện những cặp (i,j) mới mà Fx[i] + Fy[j] = A[i,j]).
n cặp ép: Đổi màu các cung, bắt đầu từ cung nh t cuối c ng của dây
chuyền đổi ngƣợc dần về cung nh t đầu tiên của dây chuyền.
Tìm dâ c u ền: Tìm kiếm theo chiều sâu. Yêu cầu dây chuyền xuất phát từ một đỉnh nh t của Old, kết thúc bằng một đỉnh nh t của New, đồng thời các cung nh t và đậm liên tiếp xen kẽ nhau .
Ví dụ:
Trong thành phố c 10 nút giao thông đƣợc đánh số từ 1 đến 10.
Khoảng cách giữa các nút giao thông đƣợc cho trong ma trận sau:
1 2 3 4 5 6 7 8 9 10
1 0 7 7 1 2 1 1 5 1 3
2 2 0 1 1 1 1 5 4 1 7
3 1 1 0 1 1 1 3 7 2 4
4 5 2 4 0 2 4 10 1 7 1
5 7 1 3 7 0 10 2 4 1 1
6 10 1 1 2 1 0 1 4 2 1
7 1 1 4 1 1 3 0 1 10 1
8 7 1 7 1 1 3 4 0 1 1
9 7 7 1 2 1 1 4 2 0 10
10 1 3 4 1 2 4 1 1 1 0
C 4 Taxi, với các điểm đỗ của các Taxi đ là: 1.2.3.4 khách hàng đang đứng t i các nút giao thông : 10, 9, 8, 7 Khi đ : thực hiện theo thuật toán ta đƣợc kết quả:
Taxi 1: Đi từ nút GT 1 —> nút GT 7 Taxi 2: Đi từ nút GT 2 —> nút GT 9
Taxi 3: Đi từ nút GT 3 —> nút GT 4 —> nút GT 8 Taxi 4: Đi từ nút GT 4 —> nút GT 10
Với tổng khoảng cách di chuyển của cả 4 Taxi là: 5 (km) Chương trình
Dữ liệu vào từ File:
Dòng đầu ghi hai số N và K
N dòng tiếp theo cho ma trận khoảng cách C: số ở dòng i, cột j là độ dài đường đi một chiều từ nút giao thông i đến nút giao thông j trong thành phố.
Hai dòng cuối c ng:
+ Dòng thứ nhất gồm K số hiệu của nút giao thông mà các Taxi đang đỗ.
+ Dòng thứ hai gồm K số hiệu của nút giao thông mà các khách hàng của hãng Taxi TN đang đứng.
Dữ liệu ra:
Cách di chuyển K Taxi tới K địa điểm khách hàng đứng mà tổng khoảng cách di chuyển là ngắn nhất.
Các hàm và thủ tục chính của chương trình:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BTTaxi {
public class BaiOto {
public int[] Old;
public int[] New;
public int[] Fx;
public int[] Fy;
public int[][] A;
public int[][] T;
public int[] Tr;
public int[] Dx;
public int[] Dy;
public int[][] C;
public int[][] Ng = new int[0][];
public G G;
public TX Tx;
public Dich Dich;
public int n = 0, k = 0;
public int[] r = new int[0], l=new int[0], sld = new int[0];
public bool ok = true;
public void Init() {
C = new int[G.SoDinh][];
for (int i=0; i < G.SoDinh; i++) {
this.C[i] = new int[this.G.SoDinh];
for (int j=0; j < G.SoDinh; j++) this.C[i][j] = 0;
}
this.Old = new int[this.Tx.SoLuong];
this.New = new int[this.Dich.SoLuong];
this.Fx = new int[this.G.SoDinh];
this.Fy = new int[this.G.SoDinh];
this.A = new int[this.G.SoDinh][];
for (int i = 0; i < this.G.SoDinh; i++) this.A[i] = new int[this.G.SoDinh];
this.T = new int[this.G.SoDinh][];
for (int i = 0; i < G.SoDinh; i++) this.T[i] = new int[this.G.SoDinh];
this.Tr = new int[this.G.SoDinh];
this.Dx = new int[this.G.SoDinh];
this.Dy = new int[this.G.SoDinh];
}
public void DocDL() {
this.n = this.G.SoDinh;
this.k = this.Tx.SoLuong;
for (int i = 0; i < this.G.SoCanh; i++)
C[this.G.DScanh[i].DinhDau][this.G.DScanh[i].DinhCuoi] = this.G.DScanh[i].TrongSo.GiaTri;
for (int i = 0; i < this.G.SoCanh; i++) for (int j = 0; j < this.G.SoCanh; j++) {
if (this.C[i][j] == 0) this.C[i][j] = 20000;
if (i == j) this.C[i][j] = 0;
}
for (int i = 0; i < this.Tx.SoLuong; i++) this.Old[i]
= this.Tx.DS[i].Dinh;
for (int i = 0; i < this.Dich.SoLuong; i++) this.New[i] = this.Dich.DS[i];
}
public void TinhCP() {
for (int i = 0; i < this.G.SoDinh; i++) for (int j = 0; j < this.G.SoDinh; j++) this.T[i][j] = -1;
for (int m = 0; m < this.n; m++) for (int i = 0; i < this.n; i++) for (int j = 0; j < this.n; j++) {
if (this.C[i][m] < this.C[i][j]) this.C[i][j] = this.C[i][m] + this.C[m][j];
} }
public void TaoMT() {
int p = 0;
for (int i = 0; i < this.k; i++) {
p = -20000;
for (int j = 0; j < this.k; j++) {
this.A[i][j] = - this.C[this.Old[i]][this.New[j]];
if (this.A[i][j] > p) p = this.A[i][j];
}
this.Fx[i] = p;
for (int j = 0; j < this.G.SoDinh; j++) this.Fy[j]
= 1;
} }
public void TangCapGhep(int j) {
int p = 0, i = 0;
do {
i = this.Tr[j];
p = this.r[i];
this.r[i] = j;
this.l[j] = i;
j = p;
} while (j == -1);
}
public void TimDayChuyen(int i) {
if (this.ok) return;
this.Dx[i] = 1;
for (int j = 0; j < this.k; j++) {
if(this.Dy[j] == -1)
if (this.Fx[i] + this.Fy[j] == this.A[i][j]) {
this.Tr[i] = j;
if (this.l[j] == -1) {
this.l[j] = j;
this.ok = true;
} else {
this.Dy[j] = 1;
this.TimDayChuyen(this.l[j]);
} } }
}
public int Min() {
int ph = 20000;
for (int i = 0; i < this.k; i++) {
if(this.Dx[i] == 1) {
for (int j = 0; j < this.k; j++) {
if (this.Dy[j] == -1)
if (this.Fx[i] + this.Dy[j] < ph) ph = this.Fx[i] + this.Dy[j] - this.A[i][j];
} } }
return ph;
}
public void SuaNhan() {
int d = this.Min();
for (int i = 0; i < this.k; i++) {
if (this.Dx[i] == 1) this.Fx[i] += d;
if (this.Dy[i] == 1) this.Fy[i] += d;
}
}
public void ThucHien() {
this.l = new int[this.G.SoDinh];
this.r = new int[this.G.SoDinh];
for (int i = 0; i < this.G.SoDinh; i++) {
this.l[i] = -1;
this.r[i] = -1;
}
for (int i = 0; i < this.k; i++) {
if (this.r[i] == -1) {
do {
this.ok = false;
for (int z = 0; z < this.G.SoDinh; z++) {
this.Dx[z] = -1;
this.Dy[z] = -1;
this.Tr[z] = -1;
}
this.TimDayChuyen(i);
if (!this.ok)
this.SuaNhan();
else this.TangCapGhep(this.l[i]);
} while (this.ok);
} } }
int dem = 0;
public void Tim(int tt, int x, int y) {
int tg = this.T[x][y];
if (tg == -1) {
if (dem == 0 || (dem > 0 && x != this.Ng[tt][dem - 1]))
{
dem++;
this.Ng[tt][dem - 1] = x;
} dem++;
this.Ng[tt][dem - 1] = y;
} else {
Tim(tt, x, tg);
Tim(tt, tg, y);
} }
public void KetQua() {
string text = string.Empty;
DateTime TDateTime = DateTime.Now;
int j, z, tt, tong;
this.Ng = new int[this.k][];
for(int i=0; i<this.k; i++) this.Ng[i] = new int[this.G.SoDinh];
this.sld = new int[this.k];
tong = 0;
for (int i = 0; i < this.k; i++) tong +=
this.A[i][this.r[i]];
tong = -tong;
for (int i = 0; i < this.k; i++) {
dem = 0;
this.Tim(i, this.Old[i], this.New[this.r[i]]);
this.sld[i] = dem;
}
//Ket qua trong mang this.Ng }
}
public class G {
public int SoDinh;
public int SoCanh;
public DanhSachCanh[] DScanh;
}
public class DanhSachCanh {
public int DinhCuoi;
public int Dinh;
public int DinhDau;
public TrongSo TrongSo;
}
public class TrongSo {
public int GiaTri;
}
public class TX {
public int SoLuong;
public DanhSachCanh[] DS;
}
public class Dich {
public int[] DS;
public int SoLuong;
} }