Thuật toán Lawler (1973)

Một phần của tài liệu (LUẬN văn THẠC sĩ) bài toán tìm bộ ghép cực đại trên đồ thị, ứng dụng giải một số bài toán trong thực tế (Trang 53)

Trong thuật toán Edmonds, sau khi chập mỗi Blossom thành một đỉnh thì đỉnh đó hoàn toàn lại có thể nằm trên một Blossom mới và bị chập tiếp. Thuật toán Lavvler chỉ quan tâm tới đỉnh chập cuối cùng, đại diện cho Blossom ngoài nhất đỉnh chập cuối cùng này được định danh bằng đỉnh cơ sở của Blossom ngoài nhất.

Cũng chính vì thao tác chập/nở nói trên mà ta cần mở rộng khái niệm Blossom, có thể coi một Blossom là một tập đỉnh nở ra từ một đỉnh chập chứ không đơn thuần là một chu trình pha cơ bản nữa.

Expan

Xét một Blossom B có đỉnh cơ sở là đỉnh r. Với v B, v  r, ta lưu lại hai đường pha từ r tới V, một đường kết thúc bằng cạnh đậm và một đường kết thúc bằng cạnh nhạt, như vậy có hai loại vết gán cho mỗi đỉnh v(hai vết này được cập nhật trong quá trình tìm đường):

+ S[v] là đỉnh liền trước V trên đường pha kết thúc bằng cạnh đậm, nếu không tồn tại đường pha loại này thì S[v] = 0.

+ T[v] là đỉnh liền trước V trên đường pha kết thúc bằng cạnh nhạt, nếu không tồn tại đường pha loại này thì T[v] = 0.

Bên cạnh hai nhãn S và T, mỗi đỉnh v còn có thêm:

+ Nhãn b[v] là đỉnh cơ sở của Blossom chứa v. Hai đỉnh u và v thuộc cùng một Blossom b[u] = b[v].

+ Nhãn match[v] là đỉnh ghép với đỉnh v. Nếu v chưa ghép thì match[v] = 0.

Khi đó, thuật toán tìm đường mở bắt đầu từ đỉnh x chưa ghép có thể phát biểu như sau:

Bước 1: (Init)

+ Hàng đợi Queue dùng để chứa những đỉnh đậm chờ duyệt, ban đầu chỉ gồm một đỉnh đậm x.

+ Với mọi đỉnh u khởi gán b[u] = u và match[u] = 0 với u. + Gán S[x] 0; với u = x, gán S[u] =0

+ Với v: gán T[v] = 0.

Bước 2: (BFS)

Lặp lại các bước sau cho tới khi hàng đợi rỗng:

+ Nếu V chưa thăm: Nếu:

V là đỉnh chưa ghép  tìm thấy đường mở kết thúc ở v, dừng. V là đỉnh đã ghép  thăm V thăm luôn match[v] và đẩy match[v] vào Queue.

Lưu vết: Cập nhật hai nhãn S và T. + Nếu V đã thăm:

Nếu V là đỉnh nhạt hoặc b[v] = b[u] => bỏ qua

Nếu V là đỉnh đậm và b[v]  b[u] ta phát hiện được Blossom mới chứa u và v khi đó:

Phát hiện đỉnh cơ sở: Truy vết đường đi ngược từ hai đỉnh đậm u và V theo hai đường pha về nút gốc, chọn lấy đỉnh a là đỉnh đậm chung gặp đầu tiên trong quá trình truy vết ngược. Khi đó Blossom mới phát hiện sẽ có đỉnh cơ sở là a.

Gán lại vết: Gọi (a = i[l], i[2],.., i[p] = u) và (a = j[l], j[2], ..., j[q] = v) lần lượt là hai đường pha dẫn từ a tới u và v. Khi đó (a = i[l], i[2],.., i[p] = u, j[q] =v, j[q-l]v, ... j[l] = a) là một chu trình pha đi từ a tới u và V rồi quay trở về a. Bằng cách đi dọc theo chu trình này theo hai hướng ngược nhau, ta có thể gán lại tất cả các nhãn S và T của những đỉnh trên chu trình. Lưu ý rằng không được gán lại các nhãn S và T cho những đỉnh k mà b[k] = a, và với những đỉnh k có b[k] = a thì bắt buộc phải gán lại nhãn S và T theo chu trình này bất kể S[k] và T[k] trước đó đã có hay chưa.

Chập Blossom: Xét những đỉnh v mà b[v]  {b[i[l]], b[i[2]], ... , b[i[p]], b[j[l]], b[j[2]], ... , b[j[q]]}, gán lại b[v] = a. Nếu v là đỉnh đậm (có nhãn S[v]  0) mà chưa được duyệt tới (chưa bao giờ được đẩy vào Queue)

thì đẩy v vào Queue chờ duyệt tiếp tại những bước sau. Bước 3:

Nếu bước 2 tìm được đường mở thì trả về đường mở, nếu bước 2 không tìm thấy đường mở và thoát ra do hàng đợi rỗng thì kết luận không tìm thấy đường mở.

Tư tưởng chính của phương pháp Lawler là dùng các nhãn b[v] thay cho thao tác chập trực tiếp Blossom, dùng các nhãn S và T để truy vết tìm đường tránh thao tác nở Blossom. Phương pháp này dựa trên một nhận xét: Mỗi khi tìm ra đường mở, nếu đưòng mở đó xuyên qua một Blossom ngoài nhất thì chắc chắn nó phải di vào Blossom này từ nút cơ sở và thoát ra ngoài bằng một cạnh nhạt.

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.1 Phát biểu bài toán

Trong thành phố có N nút giao thông mà khách hàng thường yêu cầu hãng Taxi TN cho tới. Khoảng cách giữa hai nút giao thông là C[i,j].

Hãng Taxi TN có k chiếc Taxi hiện đang đỗ tại k nút giao thông trong N nút kể trên. Giả sử có k yêu cầu của khách hàng tới k nút giao thông trong thành phố.

Hãy tìm cách di chuyển k Taxi đến k địa điểm khách đứng sao cho tổng khoảng cách di chuyển của các xe là ngắn nhất.

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ài toán điều hành Taxi sử dụng thuậ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 đó:

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.

Cho đến khi tìm được dây chuyề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]).

chuyền đổi ngược dần về cung nhạt đầu tiên của dây chuyền.

Tìm dây chuyề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ò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; }

}

3.2 BÀI TOÁN XẾP LỚP HỌC THEO TÍN CHỈ 3.2.1 Mô hình đào tạo theo học chế tín chỉ 3.2.1 Mô hình đào tạo theo học chế tín chỉ

Khác với mô hình đào tạo theo niên chế, đào tạo theo học chế tín chỉ là một mô hình đào tạo rất mềm dẻo, tăng cường tính chủ động, tự học, tự nghiên cứu của sinh viên; nhà trường, giảng viên tạo điều kiện thuận lợi tối đa cho sinh viên tích luỹ kiến thức, kỹ năng; đồng thời học chế tín chỉ cũng quản lý chặt chẽ quá trình học tập của từng sinh viên để đảm bảo chất lượng đào tạo.

Đầu mỗi khoá học, nhà trường thông báo cho sinh viên về: Chương trình đào tạo toàn khoá cho từng ngành học. Quy chế học tập và các quy định của trường.

Quyền lợi và nghĩa vụ của sinh viên.

Đầu mỗi học kỳ, nhà trường có trách nhiệm thông báo cho sinh viên về:

Danh mục các học phần và số lượng tín chỉ của mỗi học phần dự kiến giảng dạy trong học kỳ, điều kiện để đăng ký học các học phần đó.

Số lớp học dự kiến tổ chức cho mỗi học phần và thời khoá biểu cho các lớp học đó.

Đầu mỗi học kỳ, sinh viên phải tìm hiểu, nghiên cứu để nắm được chương trình đào tạo và đãng ký các học phần sẽ học trong học kỳ đó theo phiếu đăng ký quy định của trường.

3.2.2 Phát biểu bài toán

Qua tìm hiểu về mô hình đào tạo theo học chế tín chỉ em thấy rằng: việc cho sinh viên đăng ký học các học phần như hiện nay có thể dẫn đến tình trạng có những lớp số lượng sinh viên đăng ký học sẽ rất đông trong khi một số lớp học khác lại quá ít sinh viên theo học.

Trên cơ sở thực tế như vây em cho rằng nên cho sinh viên đăng ký học theo một cách mới mềm dẻo hơn đó là:

Sinh viên cần đăng ký số lượng học phần muốn học trong học kỳ; đồng thời đưa ra một danh sách các học phần có thể theo học với tổng số học phần trong danh sách phải lớn hơn hoặc bằng số lượng học phần muốn học đã đăng ký.

Căn cứ vào phiếu đăng ký của sinh viên, nhà trường sẽ bố trí cho mỗi sinh viên học đủ số lượng học phần đã đăng ký và các học phần mỗi sinh viên theo học đều nằm trong danh sách các học phần có thể học mà sinh viên đăng

Một phần của tài liệu (LUẬN văn THẠC sĩ) bài toán tìm bộ ghép cực đại trên đồ thị, ứng dụng giải một số bài toán trong thực tế (Trang 53)

Tải bản đầy đủ (PDF)

(80 trang)