Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 28 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
28
Dung lượng
2,4 MB
Nội dung
Các thuật toán đồ thị 275 0_cạnh ghép xen kẽ Vì đường pha đường đồ thị định hướng nên việc xác định đỉnh đến từ x ∈ X đường pha sử dụng thuật tốn tìm kiếm đồ thị (BFS DFS) Những đỉnh cạnh duyệt qua tạo thành pha gốc x Một đường mở (Augmenting Path) đường pha từ X_đỉnh chưa ghép tới Y_đỉnh chưa ghép Như vậy: Đường trực tiếp từ X_đỉnh chưa ghép tới Y_đỉnh chưa ghép qua 0_cạnh chưa ghép đường mở Dọc đường mở, số 0_cạnh chưa ghép nhiều số 0_cạnh ghép cạnh 12.3.2 Thuật toán Hungari Bước 1: Khởi tạo: Một ghép M := ∅ Bước 2: Với đỉnh x*∈X, ta tìm cách ghép x* sau Bắt đầu từ đỉnh x* chưa ghép, thử tìm đường mở bắt đầu x* thuật tốn tìm kiếm đồ thị (BFS DFS - thơng thường nên dùng BFS để tìm đường qua cạnh nhất) có hai khả xảy ra: Hoặc tìm đường mở dọc theo đường mở, ta loại bỏ cạnh ghép khỏi M thêm vào M cạnh chưa ghép, ta ghép nhiều ghép cũ cạnh đỉnh x* trở thành ghép Hoặc không tìm đường mở ta sử dụng thuật tốn tìm kiếm đồ thị nên xác định hai tập: VisitedX = {Tập X_đỉnh đến từ x* đường pha} VisitedY = {Tập Y_đỉnh đến từ x* đường pha} Gọi ∆ trọng số nhỏ cạnh nối đỉnh thuộc VisitedX với đỉnh không thuộc VisitedY Dễ thấy ∆ > ∆ = tồn 0_cạnh (x, y) với x∈VisitedX y∉VisitedY Vì x* đến x đường pha (x, y) 0_cạnh nên x* đến y đường pha, dẫn tới y ∈ VisitedY, điều vô lý Biến đổi đồ thị G sau: Với ∀x ∈ VisitedX, trừ ∆ vào trọng số cạnh liên thuộc với x, Với ∀ y ∈ VisitedY, cộng ∆ vào trọng số cạnh liên thuộc với y Lặp lại thủ tục tìm kiếm đồ thị thử tìm đường mở xuất phát x* tìm đường mở Bước 3: Sau bước X_đỉnh ghép, in kết ghép tìm Mơ hình cài đặt thuật tốn viết sau: ; Lê Minh Hồng 276 for (x*∈X) begin repeat ; Ví dụ minh hoạ: Để khơng bị rối hình, ta hiểu cạnh khơng ghi trọng số 0_cạnh, cạnh không vẽ mang trọng số lớn trường hợp khơng cần thiết phải tính đến Những cạnh nét đậm cạnh ghép, cạnh nét cạnh chưa ghép 1 2 3 X* = X1, tìm thấy đường mở X1 → Y1 Tăng căp 2 3 X* = X2, tìm thấy đường mở X2 → Y1→ X1 → Y2 Tăng căp 2 3 9 1 2 4 4 X* = X3, tìm thấy đường mở X3 → Y3 Tăng căp 2 Đại học Sư phạm Hà Nội, 1999-2002 Các thuật toán đồ thị 277 1 X = X4, không thấy đường mở VisitedX = {X3, X4} VisitedY = {Y3} Giá trị xoay ∆ = (=c[3,2]) Trừ trọng số cạnh liên thuộc với {X3,X4} Cộng trọng số cạnh liên thuộc với {Y3} lên 1 * 2 1=∆ -1 -1 -2 +1 3 +2 1 2 +2 +2 -2 X* = X4, không thấy đường mở VisitedX = {X1, X2, X3, X4} VisitedY = {Y1, Y2, Y3} Giá trị xoay ∆ = (=c[3,4]) Trừ trọng số cạnh liên thuộc với {X1, X2, X3, X4} Cộng trọng số cạnh liên thuộc với {Y1, Y2, Y3} lên 2 3 3 4 8 4 2=∆ -2 -2 X* = X4, Tìm thấy đường mở X4→Y3→X3→Y2→X1→Y1→X2 →Y4 Tăng cặp Xong 2 3 Hình 85: Thuật tốn Hungari Để ý khơng tìm thấy đường mở xuất phát x* trình tìm kiếm đồ thị cho ta pha gốc x* Giá trị xoay ∆ thực chất trọng số nhỏ cạnh nối X_đỉnh pha với Y_đỉnh pha (cạnh ngoài) Việc trừ ∆ vào cạnh liên thuộc với X_đỉnh pha cộng ∆ vào cạnh liên thuộc với Y_đỉnh pha làm cho cạnh ngồi nói trở thành 0_cạnh, cạnh khác có trọng số ≥ Nhưng quan trọng tất cạnh pha 0_cạnh Điều đảm bảo cho q trình tìm kiếm đồ thị lần sau xây dựng pha lớn pha cũ (Thể chỗ: tập VisitedY rộng trước phần tử) Vì tập Y_ đỉnh ghép hữu hạn nên sau khơng q k bước, có Y_đỉnh chưa ghép ∈ VisitedY, tức tìm đường mở Trên thực tế, để chương trình hoạt động nhanh hơn, bước khởi tạo, người ta thêm thao tác: Với đỉnh x ∈ X, xác định trọng số nhỏ cạnh liên thuộc với x, sau trừ tất trọng số cạnh liên thuộc với x trọng số nhỏ Làm tương tự với Y_đỉnh Lê Minh Hoàng 278 Chuyên đề Điều tương đương với việc trừ tất phần tử hàng ma trận C giá trị nhỏ hàng đó, lại trừ tất phần tử cột ma trận C phần tử nhỏ cột Khi số 0_cạnh đồ thị nhiều, chứa ghép đầy đủ cần qua bước biến đổi chứa ghép đầy đủ k cạnh Để tưởng nhớ hai nhà tốn học Kưnig Egervary, người đặt sở lý thuyết cho phương pháp, người ta lấy tên đất nước sinh hai nhà toán học để đặt tên cho thuật tốn Mặc dù sau có số cải tiến tên gọi Thuật toán Hungari (Hungarian Algorithm) dùng phổ biến 12.4 CÀI ĐẶT 12.4.1 Phương pháp đối ngẫu Kuhn-Munkres (Không làm biến đổi ma trận C ban đầu) Phương pháp Kuhn-Munkres tìm hai dãy số Fx[1 k] Fy[1 k] thoả mãn: c[i, j] - Fx[i] - Fy[j] ≥ Tập cạnh (X[i], Y[j]) thoả mãn c[i, j] - Fx[i] - Fy[j] = chứa trọn ghép đầy đủ k cạnh, ghép cần tìm Chứng minh: Nếu tìm hai dãy số thoả mãn ta việc thực hai thao tác: Với đỉnh X[i], trừ tất trọng số cạnh liên thuộc với X[i] Fx[i] Với đỉnh Y[j], trừ tất trọng số cạnh liên thuộc với Y[j] Fy[j] (Hai thao tác tương đương với việc trừ tất trọng số cạnh (X[i], Y[j]) lượng Fx[i] + Fy[j] tức c[i, j] := c[i, j] - Fx[i] - Fy[j]) Thì dễ thấy đồ thị tạo thành gồm có cạnh trọng số không âm 0_cạnh đồ thị chứa trọn ghép đầy đủ 0 M M Fx[1] = 2 M M Fx[2] = M M Fx[3] = M M Fx[4] = Fy[1] = -2 Fy[2] = -2 Fy[3] = -3 Fy[4] = (Có nhiều phương án khác: Fx = (0, 0, 1, 1); Fy = (0, 0, -1, 2) đúng) Vậy phương pháp Kuhn-Munkres đưa việc biến đổi đồ thị G (biến đổi ma trận C) việc biến đổi hay dãy số Fx Fy Việc trừ ∆ vào trọng số tất cạnh liên thuộc với X[i] tương đương với việc tăng Fx[i] lên ∆ Việc cộng ∆ vào trọng số tất cạnh liên thuộc với Y[j] tương đương Đại học Sư phạm Hà Nội, 1999-2002 Các thuật toán đồ thị 279 với giảm Fy[j] ∆ Khi cần biết trọng số cạnh (X[i], Y[j]) sau bước biến đổi, thay viết c[i, j], ta viết c[i, j] - Fx[i] - Fy[j] Ví dụ: Thủ tục tìm đường mở thuật tốn Hungari địi hỏi phải xác định cạnh 0_cạnh, cài đặt phương pháp Kuhn-Munkres, việc xác định cạnh 0_cạnh kiểm tra đẳng thức: c[i, j] - Fx[i] - Fy[j] = hay c[i, j] = Fx[i] + Fy[j] Sơ đồ cài đặt phương pháp Kuhn-Munkres viết sau: Bước 1: Khởi tạo: M := ∅; Việc khởi tạo Fx, Fy có nhiều cách chẳng hạn Fx[i] := 0; Fy[j] := với ∀i, j Hoặc: Fx[i] := (c[i, j]) với ∀i Sau đặt Fy[j] := (c[i, j] − Fx[i]) với ∀j 1≤ j≤ k 1≤i ≤ k (Miễn c[i, j] - Fx[i] - Fy[j] ≥ 0) Bước 2: Với đỉnh x*∈X, ta tìm cách ghép x* sau: Bắt đầu từ đỉnh x*, thử tìm đường mở bắt đầu x* thuật tốn tìm kiếm đồ thị (BFS DFS) Lưu ý 0_cạnh cạnh thoả mãn c[i, j] = Fx[i] + Fy[j] Có hai khả xảy ra: Hoặc tìm đường mở dọc theo đường mở, ta loại bỏ cạnh ghép khỏi M thêm vào M cạnh chưa ghép Hoặc khơng tìm đường mở xác định hai tập: VisitedX = {Tập X_đỉnh đến từ x* đường pha} VisitedY = {Tập Y_đỉnh đến từ x* đường pha} Đặt ∆ := min{c[i, j] - Fx[i] - Fy[j] ⏐ ∀X[i] ∈ VisitedX; ∀Y[j] ∉ VisitedY} Với ∀X[i] ∈ VisitedX: Fx[i] := Fx[i] + ∆; Với ∀Y[j] ∈ VisitedY: Fy[j] := Fy[j] - ∆; Lặp lại thủ tục tìm đường mở xuất phát x* tìm đường mở Đáng lưu ý phương pháp Kuhn-Munkres khơng làm thay đổi ma trận C ban đầu Điều thực hữu ích trường hợp trọng số cạnh (X[i], Y[j]) không cho cách tường minh giá trị C[i, j] mà lại cho hàm c(i, j): trường hợp này, việc trừ hàng/cộng cột trực tiếp ma trận chi phí C khơng thể thực 12.4.2 Cài đặt a) Biểu diễn ghép Để biểu diễn ghép, ta sử dụng hai mảng: matchX[1 k] matchY[1 k] matchX[i] đỉnh thuộc tập Y ghép với đỉnh X[i] matchY[j] đỉnh thuộc tập X ghép với đỉnh Y[j] Lê Minh Hoàng 280 Chuyên đề Tức cạnh (X[i], Y[j]) thuộc ghép matchX[i] = j matchY[j] = i Quy ước rằng: Nếu X[i] chưa ghép với đỉnh tập Y matchX[i] = Nếu Y[j] chưa ghép với đỉnh tập X matchY[j] = Để thêm cạnh (X[i], Y[j]) vào ghép việc đặt matchX[i] := j matchY[j] := i; Để loại cạnh (X[i], Y[j]) khỏi ghép việc đặt matchX[i] := matchY[j] := 0; b) Tìm đường mở Ta tìm đường mở xây dựng hai tập VisitedX VisitedY thuật tốn tìm kiếm theo chiều rộng xét tới đỉnh 0_cạnh định hướng nói phần đầu: Khởi tạo hàng đợi (Queue) ban đầu có đỉnh x* Thuật tốn tìm kiếm theo chiều rộng làm việc theo nguyên tắc lấy đỉnh v khỏi Queue lại đẩy Queue nối từ v chưa thăm Như thăm tới Y_đỉnh chưa ghép tức ta tìm đường mở kết thúc Y_đỉnh chưa ghép đó, q trình tìm kiếm dừng Cịn ta thăm tới đỉnh y ∈ Y ghép, dựa vào kiện: từ y tới matchY[y] theo 0_cạnh định hướng, nên ta đánh dấu thăm y, thăm ln matchY[y], đẩy vào Queue phần tử matchY[y] ∈ X Input: file văn ASSIGN.INP • Dịng 1: Ghi hai số m, n theo thứ tự số thợ số việc cách dấu cách (m, n ≤ 100) • Các dịng tiếp theo, dịng ghi ba số i, j, c[i, j] cách dấu cách thể thợ i làm việc j chi phí để làm c[i, j] (1 ≤ i ≤ m; ≤ j ≤ n; ≤ c[i, j] ≤ 100) Output: file văn ASSIGN.OUT, mô tả phép phân cơng tối ưu tìm 1 2 3 ASSIGN.INP 56 110 120 210 242 321 330 430 449 19 ASSIGN.OUT Optimal assignment: 1) X[1] - Y[1] 2) X[2] - Y[4] 3) X[3] - Y[2] 4) X[4] - Y[3] Cost: 19 X Y P_4_12_1.PAS * Thuật toán Hungari program AssignmentProblemSolve; const InputFile = 'ASSIGN.INP'; OutputFile = 'ASSIGN.OUT'; max = 100; maxC = 10001; var Đại học Sư phạm Hà Nội, 1999-2002 Các thuật toán đồ thị 281 c: array[1 max, max] of Integer; Fx, Fy, matchX, matchY, Trace: array[1 max] of Integer; m, n, k, start, finish: Integer; {đường mở start∈X kết thúc finish∈Y} procedure Enter; var i, j: Integer; f: Text; begin Assign(f, InputFile); Reset(f); ReadLn(f, m, n); if m > n then k := m else k := n; for i := to k for j := to k c[i, j] := maxC; while not SeekEof(f) ReadLn(f, i, j, c[i, j]); Close(f); end; procedure Init; {Khởi tạo} var i, j: Integer; begin {Bộ ghép rỗng} FillChar(matchX, SizeOf(matchX), 0); FillChar(matchY, SizeOf(matchY), 0); {Fx[i] := Trọng số nhỏ cạnh liên thuộc với X[i]} for i := to k begin Fx[i] := maxC; for j := to k if c[i, j] < Fx[i] then Fx[i] := c[i, j]; end; {Fy[j] := Trọng số nhỏ cạnh liên thuộc với Y[j]} for j := to k begin Fy[j] := maxC; for i := to k {Lưu ý trọng số cạnh (x[i], y[j]) c[i, j] - Fx[i] khơng cịn c[i, j] nữa} if c[i, j] - Fx[i] < Fy[j] then Fy[j] := c[i, j] - Fx[i]; end; {Việc khởi tạo Fx Fy đơn giản số 0_cạnh trở nên nhiều tốt mà thơi} {Ta hồn tồn khởi gán Fx Fy giá trị 0} end; {Hàm cho biết trọng số cạnh (X[i], Y[j]) } function GetC(i, j: Integer): Integer; begin GetC := c[i, j] - Fx[i] - Fy[j]; end; procedure FindAugmentingPath; {Tìm đường mở bắt đầu start} var Queue: array[1 max] of Integer; i, j, first, last: Integer; begin FillChar(Trace, SizeOf(Trace), 0); {Trace[j] = X_đỉnh liền trước Y[j] đường mở} {Thuật toán BFS} Queue[1] := start; {Đẩy start vào hàng đợi} first := 1; last := 1; repeat i := Queue[first]; Inc(first); {Lấy đỉnh X[i] khỏi hàng đợi} for j := to k {Duyệt Y_đỉnh chưa thăm kề với X[i] qua 0_cạnh chưa ghép} if (Trace[j] = 0) and (GetC(i, j) = 0) then begin Trace[j] := i; {Lưu vết đường đi, với việc đánh dấu (≠0) luôn} if matchY[j] = then {Nếu j chưa ghép ghi nhận nơi kết thúc đường mở ln} Lê Minh Hoàng 282 Chuyên đề begin finish := j; Exit; end; Inc(last); Queue[last] := matchY[j]; {Đẩy matchY[j] vào Queue} end; until first > last; {Hàng đợi rỗng} end; procedure SubX_AddY; {Xoay trọng số cạnh} var i, j, t, Delta: Integer; VisitedX, VisitedY: set of Byte; begin (* Chú ý: VisitedY = {y | Trace[y] ≠ 0} VisitedX = {start} ∪ match(VisitedY) = {start} ∪ {matchY[y] | Trace[y] ≠ 0} *) VisitedX := [start]; VisitedY := []; for j := to k if Trace[j] then begin Include(VisitedX, matchY[j]); Include(VisitedY, j); end; {Sau xác định VisitedX VisitedY, ta tìm ∆ trọng số nhỏ cạnh nối từ VisitedX Y\VisitedY} Delta := maxC; for i := to k if i in VisitedX then for j := to k if not (j in VisitedY) and (GetC(i, j) < Delta) then Delta := GetC(i, j); {Xoay trọng số cạnh} for t := to k begin {Trừ trọng số cạnh liên thuộc với VisitedX Delta} if t in VisitedX then Fx[t] := Fx[t] + Delta; {Cộng trọng số cạnh liên thuộc với VisitedY lên Delta} if t in VisitedY then Fy[t] := Fy[t] - Delta; end; end; {Nới rộng ghép đường mở tìm được} procedure Enlarge; var x x f f x, next: Integer; begin next next repeat x := Trace[finish]; next := matchX[x]; matchX[x] := finish; matchY[finish] := x; finish := Next; start start until finish = 0; end; procedure Solve; {Thuật toán Hungari} var x, y: Integer; begin for x := to k begin start := x; finish := 0; {Khởi gán nơi xuất phát đường mở, finish = nghĩa chưa tìm thấy đường mở} Đại học Sư phạm Hà Nội, 1999-2002 Các thuật toán đồ thị 283 repeat FindAugmentingPath; {Thử tìm đường mở} if finish = then SubX_AddY; {Nếu khơng thấy xoay trọng số cạnh lặp lại} until finish 0; {Cho tới tìm thấy đường mở} Enlarge; {Tăng cặp dựa đường mở tìm được} end; end; procedure Result; var x, y, Count, W: Integer; f: Text; begin Assign(f, OutputFile); Rewrite(f); WriteLn(f, 'Optimal assignment:'); W := 0; Count := 0; for x := to m {In phép phân cơng cần xét đến m, khơng cần xét đến k} begin y := matchX[x]; {Những cạnh có trọng số maxC tương ứng với thợ không giao việc việc không phân công} if c[x, y] < maxC then begin Inc(Count); WriteLn(f, Count:5, ') X[', x, '] - Y[', y, '] ', c[x, y]); W := W + c[x, y]; end; end; WriteLn(f, 'Cost: ', W); Close(f); end; begin Enter; Init; Solve; Result; end Nhận xét: Nếu cài đặt cho dù đồ thị có cạnh mang trọng số âm, chương trình tìm ghép cực đại với trọng số cực tiểu Lý do: Ban đầu, ta trừ tất phần tử hàng ma trận C giá trị nhỏ hàng đó, lại trừ tất phần tử cột ma trận C giá trị nhỏ cột (Phép trừ làm gián tiếp qua Fx, Fy trừ trực tiếp ma trận C) Nên sau bước này, tất cạnh đồ thị có trọng số khơng âm phần tử nhỏ cột C chắn Sau kết thúc thuật toán, tổng tất phần tử hai dãy Fx, Fy trọng số cực tiểu ghép đầy đủ tìm đồ thị ban đầu Một vấn đề phải cẩn thận việc ước lượng độ lớn phần tử Fx Fy Nếu giả thiết cho trọng số không 500 ta khơng thể dựa vào bất đẳng thức Fx(x) + Fy(y) ≤ c(x, y) mà khẳng định phần tử Fx Fy ≤ 500 Hãy tự tìm ví dụ để hiểu rõ chất thuật tốn Lê Minh Hồng 284 Chun đề 12.5 BÀI TỐN TÌM BỘ GHÉP CỰC ĐẠI VỚI TRỌNG SỐ CỰC ĐẠI TRÊN ĐỒ THỊ HAI PHÍA Bài tốn tìm ghép cực đại với trọng số cực đại giải nhờ phương pháp Hungari cách đổi dấu tất phần tử ma trận chi phí (Nhờ nhận xét 1) Khi cài đặt, ta sửa lại đơi chút chương trình để giải tốn tìm ghép cực đại với trọng số cực đại mà không cần đổi dấu trọng số Cụ thể sau: Bước 1: Khởi tạo: M := ∅; Khởi tạo hai dãy Fx Fy thoả mãn: ∀i, j: Fx[i] + Fy[j] ≥ c[i, j]; Chẳng hạn ta đặt Fx[i] := Phần tử lớn dòng i ma trận C đặt Fy[j] := Bước 2: Với đỉnh x*∈X, ta tìm cách ghép x* sau: Với cách hiểu 0_cạnh cạnh thoả mãn c[i, j] = Fx[i] + Fy[j] Bắt đầu từ đỉnh x*, thử tìm đường mở bắt đầu x* Có hai khả xảy ra: Hoặc tìm đường mở dọc theo đường mở, ta loại bỏ cạnh ghép khỏi M thêm vào M cạnh chưa ghép Hoặc khơng tìm đường mở xác định hai tập: VisitedX = {Tập X_đỉnh đến từ x* đường pha} VisitedY = {Tập Y_đỉnh đến từ x* đường pha} Đặt ∆ := min{Fx[i] + Fy[j] - c[i, j] ⏐ ∀X[i] ∈ VisitedX; ∀Y[j] ∉ VisitedY} Với ∀X[i] ∈ VisitedX: Fx[i] := Fx[i] - ∆; Với ∀Y[j] ∈ VisitedY: Fy[j] := Fy[j] + ∆; Lặp lại thủ tục tìm đường mở xuất phát x* tìm đường mở Bước 3: Sau bước X_đỉnh ghép, ta ghép đầy đủ k cạnh với trọng số lớn Dễ dàng chứng minh tính đắn phương pháp, ta đặt: c'[i, j] = - c[i, j]; F'x[i] := - Fx[i]; F'y[j] = - Fy[j] Thì tốn trở thành tìm cặp ghép đầy đủ trọng số cực tiểu đồ thị hai phía với ma trận trọng số c'[1 k, k] Bài tốn giải cách tính hai dãy đối ngẫu F'x F'y Từ biến đổi đại số bản, ta kiểm chứng tính tương đương bước phương pháp nêu với bước phương pháp Kuhn-Munkres mục trước 12.6 NÂNG CẤP Dựa vào mơ hình cài đặt thuật tốn Kuhn-Munkres trên, ta đánh giá độ phức tạp tính tốn lý thuyết cách cài đặt này: Đại học Sư phạm Hà Nội, 1999-2002 288 Chuyên đề repeat i := Pop; {Rút đỉnh X[i] khỏi hàng đợi} for j := to k {Quét Y_đỉnh chưa thăm} if Trace[j] = then begin w := GetC(i, j); {xét cạnh (X[i], Y[j])} if w = then {Nếu 0_cạnh} begin Trace[j] := i; {Lưu vết đường đi} if matchY[j] = then {Nếu j chưa ghép ghi nhận nơi kết thúc đường mở thoát} begin finish := j; Exit; end; Push(matchY[j]); {Nếu j ghép đẩy tiếp matchY[j] vào hàng đợi} end; if d[j] > w then {Cập nhật lại khoảng cách d[j] thấy cạnh (X[i], Y[j]) ngắn khoảng cách này} begin d[j] := w; arg[j] := i; end; end; until first > last; end; {Xoay trọng số cạnh} procedure SubX_AddY; var Delta: Integer; x, y: Integer; begin {Trước hết tính ∆ = giá trị nhỏ trọng số d[y], với y∈Y chưa thăm (y không thuộc pha)} Delta := maxC; for y := to k if (Trace[y] = 0) and (d[y] < Delta) then Delta := d[y]; {Trừ trọng số cạnh liên thuộc với start∈X ∆} Fx[start] := Fx[start] + Delta; for y := to k {Xét đỉnh y∈Y} if Trace[y] then {Nếu y thuộc pha} begin x := matchY[y]; {Thì x = matchY[y] phải thuộc pha} Fy[y] := Fy[y] - Delta; {Cộng trọng số cạnh liên thuộc với y lên ∆} Fx[x] := Fx[x] + Delta; {Trừ trọng số cạnh liên thuộc với x ∆} end else d[y] := d[y] - Delta; {Nếu y ∉ pha sau bước xoay, khoảng cách từ y đến pha giảm ∆} {Chuẩn bị tiếp tụcBFS} for y := to k if (Trace[y] = 0) and (d[y] = 0) then {Thăm đỉnh y∈Y tạo với pha 0_cạnh} begin Trace[y] := arg[y]; {Lưu vết đường đi} if matchY[y] = then {Nếu y chưa ghép ghi nhận đỉnh kết thúc đường mở thoát ngay} begin finish := y; Exit; end; Push(matchY[y]); {Nếu y ghép đẩy ln matchY[y] vào hàng đợi để chờ loang tiếp} end; end; procedure Enlarge; {Nới rộng ghép đường mở kết thúc finish} var x, next: Integer; Đại học Sư phạm Hà Nội, 1999-2002 Các thuật toán đồ thị begin repeat x := Trace[finish]; next := matchX[x]; matchX[x] := finish; matchY[finish] := x; finish := Next; until finish = 0; end; 289 x f next start procedure Solve; var x, y: Integer; begin for x := to k {Với X_đỉnh: } begin start := x; {Đặt nơi khởi đầu đường mở} InitBFS; {Khởi tạo pha} repeat FindAugmentingPath; {Tìm đường mở} if finish = then SubX_AddY; {Nếu khơng thấy xoay trọng số cạnh …} until finish 0; {Cho tới tìm đường mở} Enlarge; {Nới rộng ghép đường mở tìm được} end; end; procedure Result; var x, y, Count, W: Integer; f: Text; begin Assign(f, OutputFile); Rewrite(f); WriteLn(f, 'Optimal assignment:'); W := 0; Count := 0; for x := to m {Với X_đỉnh, xét cặp ghép tương ứng} begin y := matchX[x]; if c[x, y] < maxC then {Chỉ quan tâm đến cặp ghép có trọng số < maxC} begin Inc(Count); WriteLn(f, Count:5, ') X[', x, '] - Y[', y, '] ', c[x, y]); W := W + c[x, y]; end; end; WriteLn(f, 'Cost: ', W); Close(f); end; begin Enter; Init; Solve; Result; end Lê Minh Hồng x f next start 290 Chun đề §13 BÀI TỐN TÌM BỘ GHÉP CỰC ĐẠI TRÊN ĐỒ THỊ 13.1 CÁC KHÁI NIỆM Xét đồ thị G = (V, E), ghép đồ thị G tập cạnh đơi khơng có đỉnh chung Bài tốn tìm ghép cực đại đồ thị tổng quát phát biểu sau: Cho đồ thị G, phải tìm ghép cực đại G (bộ ghép có nhiều cạnh nhất) Với ghép M đồ thị G, ta gọi: Những cạnh thuộc M gọi cạnh ghép hay cạnh đậm Những cạnh không thuộc M gọi cạnh chưa ghép hay cạnh nhạt Những đỉnh đầu mút cạnh đậm gọi đỉnh ghép, đỉnh lại gọi đỉnh chưa ghép Một đường (đường khơng có đỉnh lặp lại) gọi đường pha bắt đầu cạnh nhạt cạnh đậm, nhạt nằm nối tiếp xen kẽ Một chu trình (chu trình khơng có đỉnh lặp lại) gọi Blossom qua đỉnh, bắt đầu kết thúc cạnh nhạt dọc chu trình, cạnh đậm, nhạt nằm nối tiếp xen kẽ Đỉnh xuất phát chu trình (cũng đỉnh kết thúc) gọi đỉnh sở (base) Blossom Đường mở đường pha bắt đầu đỉnh chưa ghép kết thúc đỉnh chưa ghép Ví dụ: Với đồ thị G ghép M Hình 87: Đường (8, 1, 2, 5, 6, 4) đường pha Chu trình (2, 3, 4, 6, 5, 2) Blossom Đường (8, 1, 2, 3, 4, 6, 5, 7) đường mở Đường (8, 1, 2, 3, 4, 6, 5, 2, 1, 9) có cạnh đậm/nhạt xen kẽ đường pha (và tất nhiên đường mở) khơng phải đường Đã ghép Chưa ghép Hình 87: Đồ thị G ghép M Ta dễ dàng suy tính chất sau Đường mở Blossom đường độ dài lẻ với số cạnh nhạt nhiều số cạnh đậm cạnh Đại học Sư phạm Hà Nội, 1999-2002 Các thuật toán đồ thị 291 Trong Blossom, đỉnh đỉnh sở đỉnh ghép đỉnh ghép với đỉnh phải thuộc Blossom Vì Blossom chu trình nên Blossom, đỉnh khơng phải đỉnh sở tồn hai đường pha từ đỉnh sở đến nó, đường kết thúc cạnh đậm đường kết thúc cạnh nhạt, hai đường pha hình thành cách dọc theo chu trình theo hai hướng ngược Như ví dụ Hình 87, đỉnh có hai đường pha đỉnh sở tới: (2, 3, 4) đường pha kết thúc cạnh đậm (2, 5, 6, 4) đường pha kết thúc cạnh nhạt 13.2 THUẬT TOÁN EDMONDS (1965) Cơ sở thuật toán định lý (C.Berge): Một ghép M đồ thị G cực đại không tồn đường mở M Thuật toán Edmonds: M := ∅; for (∀ đỉnh u chưa ghép) if then < Dọc đường mở: Loại bỏ cạnh đậm khỏi M; Thêm vào M cạnh nhạt; > Result: M ghép cực đại G Điều khó thuật tốn Edmonds phải xây dựng thuật tốn tìm đường mở xuất phát từ đỉnh chưa ghép Thuật tốn xây dựng cách kết hợp thuật tốn tìm kiếm đồ thị với phép chập Blossom Xét đường pha xuất phát từ đỉnh x chưa ghép Những đỉnh đến từ x đường pha kết thúc cạnh nhạt gán nhãn "nhạt", đỉnh đến từ x đường pha kết thúc cạnh đậm gán nhãn "đậm" Với Blossom, ta định nghĩa phép chập (shrink) phép thay đỉnh Blossom đỉnh Những cạnh nối đỉnh thuộc Blossom tới đỉnh v khơng thuộc Blossom thay cạnh nối đỉnh chập với v giữ nguyên tính đậm/nhạt Dễ thấy sau phép chập, cạnh đậm đảm bảo ghép đồ thị mới: Lê Minh Hoàng 292 Chuyên đề Shrink Shrink Blossom Blossom = đỉnh sở blossom = đỉnh chập từ blossom Hình 88: Phép chập Blossom Thuật tốn tìm đường mở phát biểu sau Trước hết đỉnh xuất phát x gán nhãn đậm Tiếp theo thuật tốn tìm kiếm đồ thị x, theo nguyên tắc: từ đỉnh đậm phép cạnh nhạt từ đỉnh nhạt cạnh đậm Mỗi thăm tới đỉnh, ta gán nhãn đậm/nhạt cho đỉnh tiếp tục thao tác tìm kiếm đồ thị bình thường Cũng trình tìm kiếm, phát thấy cạnh nhạt nối hai đỉnh đậm, ta dừng lại gán nhãn tiếp gặp tình trạng đỉnh có hai nhãn đậm/nhạt, trường hợp này, Blossom phát (xem tính chất Blossom) bị chập thành đỉnh, thuật toán bắt đầu lại với đồ thị trả lời câu hỏi: "có tồn đường mở xuất phát từ x hay khơng?" Nếu đường mở tìm khơng qua đỉnh chập ta việc tăng cặp dọc theo đường mở Nếu đường mở có qua đỉnh chập ta lại nở đỉnh chập thành Blossom để thay đỉnh chập đường mở đoạn đường xuyên qua Blossom: Expand Expand Hình 89: Nở Blossom để dị đường xun qua Blossom Đại học Sư phạm Hà Nội, 1999-2002 Các thuật tốn đồ thị 293 Lưu ý khơng phải Blossom bị chập, Blossom ảnh hưởng tới trình tìm đường mở phải chập để đảm bảo đường mở tìm đường Tuy nhiên việc cài đặt trực tiếp phép chập Blossom nở đỉnh rắc rối, địi hỏi chương trình với độ phức tạp O(n4) Dưới ta trình bày phương pháp cài đặt hiệu với độ phức tạp O(n3), phương pháp cài đặt không phức tạp, yêu cầu phải hiểu rõ chất thuật toán 13.3 PHƯƠNG PHÁP LAWLER (1973) Trong phương pháp Edmonds, sau chập Blossom thành đỉnh đỉnh hồn tồn lại nằm Blossom bị chập tiếp Phương pháp Lawler quan tâm đến đỉnh chập cuối cùng, đại diện cho Blossom (Outermost Blossom), đỉnh chập cuối định danh (đánh số) đỉnh sở Blossom Cũng thao tác chập/nở nói mà ta cần mở rộng khái niệm Blossom, coi Blossom tập đỉnh nở từ đỉnh chập khơng đơn chu trình pha Xét Blossom B có đỉnh sở đỉnh r Với ∀v∈B, v ≠ r, ta lưu lại hai đường pha từ r tới v, đường kết thúc cạnh đậm đường kết thúc cạnh nhạt, có hai loại vết gãn cho đỉnh v (hai vết cập nhật trình tìm đường): S[v] đỉnh liền trước v đường pha kết thúc cạnh đậm, khơng tồn đường pha loại S[v] = T[v] đỉnh liền trước v đường pha kết thúc cạnh nhạt, không tồn đường pha loại T[v] = Bên cạnh hai nhãn S T, đỉnh v cịn có thêm Nhãn b[v] đỉnh sở Blossom chứa v Hai đỉnh u v thuộc Blossom ⇔ b[u] = b[v] Nhãn match[v] đỉnh ghép với đỉnh v Nếu v chưa ghép match[v] = Khi thuật tốn tìm đường mở đỉnh x chưa ghép phát biểu sau: Bước 1: (Init) Hàng đợi Queue dùng để chứa đỉnh đậm chờ duyệt, ban đầu gồm đỉnh đậm x Với đỉnh u, khởi gán b[u] = u match[u] = với ∀u Gán S[x] ≠ 0; Với ∀u≠x, gán S[u] = 0;Với ∀v: gán T[v] = Bước 2: (BFS) Lặp lại bước sau hàng đợi rỗng: Với đỉnh đậm u lấy từ Queue, xét cạnh nhạt (u, v): Nếu v chưa thăm: Lê Minh Hoàng 294 Chuyên đề Nếu v đỉnh chưa ghép ⇒ Tìm thấy đường mở kết thúc v, dừng Nếu v đỉnh ghép ⇒ thăm v ⇒ thăm match[v] đẩy match[v] vào Queue Sau lần thăm, ý việc lưu vết (hai nhãn S T) Nếu v thăm Nếu v đỉnh nhạt b[v] = b[u] ⇒ bỏ qua Nếu v đỉnh đậm b[v] ≠ b[u] ta phát blossom chứa u v, đó: Phát đỉnh sở: Truy vết đường ngược từ hai đỉnh đậm u v theo hai đường pha nút gốc, chọn lấy đỉnh a đỉnh đậm chung gặp q trình truy vết ngược Khi Blossom phát có đỉnh sở a Gán lại vết: Gọi (a = i1, i2, …, ip = u) (a = j1, j2, …, jq = v) hai đường pha dẫn từ a tới u v Khi (a = i1, i2, …, ip = u, jq = v, jq-1, …, j1 = a) chu trình pha từ a tới u v quay trở a Bằng cách dọc theo chu trình theo hai hướng ngược nhau, ta gán lại tất nhãn S T đỉnh chu trình Lưu ý không gán lại nhãn S T cho đỉnh k mà b[k] = a, với đỉnh k có b[k] ≠ a bắt buộc phải gán lại nhãn S T theo chu trình S[k] T[k] trước có hay chưa Chập Blossom: Xét đỉnh v mà b[v]∈{b[i1], b[i2], …, b[ip], b[j1], b[j2], …, b[jq]}, gán lại b[v] = a Nếu v đỉnh đậm (có nhãn S[v] ≠ 0) mà chưa duyệt tới (chưa đẩy vào Queue) đẩy v vào Queue chờ duyệt tiếp bước sau Nếu trình hàng đợi rỗng tức không tồn đường mở x Sau số ví dụ trường hợp từ đỉnh đậm u xét cạnh nhạt (u, v): Trường hợp 1: v chưa thăm chưa ghép: S:2 S:2 v u x T:3 u x T:1 v T:1 ⇒ Tìm thấy đường mở Trường hợp 2: v chưa thăm ghép Đại học Sư phạm Hà Nội, 1999-2002 Các thuật toán đồ thị 295 S:2 S:2 S:4 v u x T:3 v u x T:1 T:1 ⇒ Thăm v lẫn match[v], gán nhãn T[v] S[match[v]] Trường hợp 3: v thăm, đỉnh đậm thuộc blossom với u T:3 S:5 T:7 S:4 u x T:1 S:2 T:3 S:7 v T:5 S:6 b[.] = ⇒ Không xét, bỏ qua Trường hợp 4: v thăm, đỉnh đậm b[u] ≠ b[v] T:3 T:3 S:5 T:7 S:4 S:4 a x T:1 S:2 x S:2 T:3 T:1 S:6 8 T:3 S:7 T:5 S:6 ⇒ Phát Blossom, tìm đỉnh sở a = 3, gán lại nhãn S T dọc chu trình pha Đẩy hai đỉnh đậm 4, vào hàng đợi, Tại bước sau, duyệt tới đỉnh 6, tìm thấy đường mở kết thúc 8, truy vết theo nhãn S T tìm đường (1, 2, 3, 4, 5, 7, 6, 8) Tư tưởng phương pháp Lawler dùng nhãn b[v] thay cho thao tác chập trực tiếp Blossom, dùng nhãn S T để truy vết tìm đường mở, tránh thao tác nở Blossom Phương pháp dựa nhận xét: Mỗi tìm đường mở, đường mở xun qua Blossom ngồi chắn phải vào Blossom từ nút sở thoát khỏi Blossom cạnh nhạt 13.4 CÀI ĐẶT Ta cài đặt phương pháp Lawler với khuôn dạng Input/Output sau: Lê Minh Hoàng 296 Chuyên đề Input: file văn GMATCH.INP • Dịng 1: Chứa hai số n, m số cạnh số đỉnh đồ thị cách dấu cách (n ≤ 100) • m dịng tiếp theo, dòng chứa hai số u, v tượng trưng cho cạnh (u, v) đồ thị Output: file văn GMATCH.OUT, ghi ghép cực đại tìm 10 GMATCH.INP 10 11 12 16 24 28 34 36 56 59 10 78 79 GMATCH.OUT 1) 2) 3) 4) 10 5) Chương trình sửa đổi chút mơ hình cài đặt dựa vào nhận xét: v đỉnh đậm ⇔ v = x match[v] đỉnh nhạt Nếu v đỉnh đậm S[v] = match[v] Vậy ta khơng cần phải sử dụng riêng mảng nhãn S[v], bước sửa vết, ta cần sửa nhãn vết T[v] mà Để kiểm tra đỉnh v ≠ x có phải đỉnh đậm hay khơng, ta kiểm tra điều kiện: match[v] có đỉnh nhạt hay khơng, hay T[match[v]] có khác hay khơng Chương trình sử dụng biến với vai trò sau: match[v] đỉnh ghép với đỉnh v b[v] đỉnh sở Blossom chứa v T[v] đỉnh liền trước v đường pha từ đỉnh xuất phát tới v kết thúc cạnh nhạt, T[v] = trình BFS chưa xét tới đỉnh nhạt v InQueue[v] biến Boolean, InQueue[v] = True ⇔ v đỉnh đậm đẩy vào Queue để chờ duyệt start finish: Nơi bắt đầu kết thúc đường mở P_4_13_1.PAS * Phương pháp Lawler áp dụng cho thuật toán Edmonds program MatchingInGeneralGraph; const InputFile = 'GMATCH.INP'; OutputFile = 'GMATCH.OUT'; max = 100; var a: array[1 max, max] of Boolean; match, Queue, b, T: array[1 max] of Integer; InQueue: array[1 max] of Boolean; n, first, last, start, finish: Integer; procedure Enter; var Đại học Sư phạm Hà Nội, 1999-2002 Các thuật toán đồ thị i, m, u, v: Integer; f: Text; begin Assign(f, InputFile); Reset(f); FillChar(a, SizeOf(a), False); ReadLn(f, n, m); for i := to m begin ReadLn(f, u, v); a[u, v] := True; a[v, u] := True; end; Close(f); end; procedure Init; {Khởi tạo ghép rỗng} begin FillChar(match, SizeOf(match), 0); end; procedure InitBFS; {Thủ tục gọi để khởi tạo trước tìm đường mở xuất phát từ start} var i: Integer; begin {Hàng đợi gồm đỉnh đậm start} first := 1; last := 1; Queue[1] := start; FillChar(InQueue, SizeOf(InQueue), False); InQueue[start] := True; {Các nhãn T khởi gán = 0} FillChar(T, SizeOF(T), 0); {Nút sở outermost blossom chứa i i} for i := to n b[i] := i; finish := 0; {finish = nghĩa chưa tìm thấy đường mở} end; procedure Push(v: Integer); {Đẩy đỉnh đậm v vào hàng đơi} begin Inc(last); Queue[last] := v; InQueue[v] := True; end; function Pop: Integer; {Lấy đỉnh đậm khỏi hàng đợi, trả kết hàm} begin Pop := Queue[first]; Inc(first); end; {Khó phương pháp Lawler thủ tục này: Thủ tục xử lý gặp cạnh nhạt nối hai đỉnh đậm p, q} procedure BlossomShrink(p, q: Integer); var i, NewBase: Integer; Mark: array[1 max] of Boolean; {Thủ tục tìm nút sở cách truy vết ngược theo đường pha từ p q} function FindCommonAncestor(p, q: Integer): Integer; var InPath: array[1 max] of Boolean; begin FillChar(InPath, SizeOf(Inpath), False); repeat {Truy vết từ p} p := b[p]; {Nhảy tới nút sở Blossom chứa p, phép nhảy để tăng tốc độ truy vết} Inpath[p] := True; {Đánh dấu nút đó} Lê Minh Hồng 297 298 Chuyên đề if p = start then Break; {Nếu truy đến nơi xuất phát dừng} p := T[match[p]]; {Nếu chưa đến start truy lùi tiếp hai bước, theo cạnh đậm theo cạnh nhạt} until False; repeat {Truy vết từ q, tương tự p} q := b[q]; if InPath[q] then Break; {Tuy nhiên chạm vào đường pha p dừng ngay} q := T[match[q]]; until False; FindCommonAncestor := q; {Ghi nhận đỉnh sở mới} end; procedure ResetTrace(x: Integer); {Gán lại nhãn vết dọc đường pha từ start tới x} var u, v: Integer; begin v := x; while b[v] NewBase {Truy vết đường pha từ start tới đỉnh đậm x} begin u := match[v]; Mark[b[v]] := True; {Đánh dấu nhãn blossom đỉnh đường đi} Mark[b[u]] := True; v := T[u]; if b[v] NewBase then T[v] := u; {Chỉ đặt lại vết T[v] b[v] nút sở mới} end; end; begin {BlossomShrink} FillChar(Mark, SizeOf(Mark), False); {Tất nhãn b[v] chưa bị đánh dấu} NewBase := FindCommonAncestor(p, q); {xác định nút sở} {Gán lại nhãn} ResetTrace(p); ResetTrace(q); if b[p] NewBase then T[p] := q; if b[q] NewBase then T[q] := p; {Chập blossom ⇔ gán lại nhãn b[i] blossom b[i] bị đánh dấu} for i := to n if Mark[b[i]] then b[i] := NewBase; {Xét đỉnh đậm i chưa đưa vào Queue nằm Blossom mới, đẩy i Queue để chờ duyệt tiếp bước sau} for i := to n if not InQueue[i] and (b[i] = NewBase) then Push(i); end; {Thủ tục tìm đường mở} procedure FindAugmentingPath; var u, v: Integer; begin InitBFS; {Khởi tạo} repeat {BFS} u := Pop; {Xét đỉnh v chưa duyệt, kề với u, không nằm Blossom với u, dĩ nhiên T[v] = (u, v) cạnh nhạt rồi} for v := to n if (T[v] = 0) and (a[u, v]) and (b[u] b[v]) then begin if match[v] = then {Nếu v chưa ghép ghi nhận đỉnh kết thúc đường mở thoát ngay} begin T[v] := u; finish := v; Exit; end; {Nếu v đỉnh đậm gán lại vết, chập Blossom …} if (v = start) or (T[match[v]] 0) then BlossomShrink(u, v) else {Nếu khơng ghi vết đường đi, thăm v, thăm match[v] đẩy tiếp match[v] vào Queue} Đại học Sư phạm Hà Nội, 1999-2002 Các thuật toán đồ thị 299 begin T[v] := u; Push(match[v]); end; end; until first > last; end; procedure Enlarge; {Nới rộng ghép đường mở start, kết thúc finish} var v, next: Integer; begin repeat v := T[finish]; next := match[v]; match[v] := finish; match[finish] := v; finish := next; until finish = 0; end; procedure Solve; {Thuật toán Edmonds} var u: Integer; begin for u := to n if match[u] = then begin start := u; {Với đỉnh chưa ghép start} FindAugmentingPath; {Tìm đường mở start} if finish then Enlarge; {Nếu thấy nới rộng ghép theo đường mở này} end; end; procedure Result; {In ghép tìm được} var u, count: Integer; f: Text; begin Assign(f, OutputFile); Rewrite(f); count := 0; for u := to n if match[u] > u then {Vừa tránh trùng lặp (u, v) (v, u), vừa loại đỉnh không ghép (match=0)} begin Inc(count); WriteLn(f, count, ') ', u, ' ', match[u]); end; Close(f); end; begin Enter; Init; Solve; Result; end 13.5 ĐỘ PHỨC TẠP TÍNH TỐN Thủ tục BlossomShrink có độ phức tạp O(n) Thủ tục FindAugmentingPath cần không n lần gọi thủ tục BlossomShrink, cộng thêm chi phí thuật tốn tìm kiếm theo chiều rộng, có độ phức tạp Lê Minh Hồng 300 Chun đề O(n2) Phương pháp Lawler cần không n lần gọi thủ tục FindAugmentingPath nên có độ phức tạp tính tốn O(n3) Cho đến nay, phương pháp tốt để giải tốn tìm ghép tổng qt đồ thị biết đến Micali Varizani (1980), có độ phức tạp tính tốn O( n m) Bạn tham khảo tài liệu khác Đại học Sư phạm Hà Nội, 1999-2002 TÀI LIỆU ĐỌC THÊM [1] Christian Charras, Thierry Lecroq Handbook of Exact String-Matching Algorithms Gần 20 thuật tốn tìm kiếm chuỗi, có diễn giải đầy đủ [2] Reinhard Diestel Graph Theory Một sách chuyên Lý thuyết đồ thị [3] Johan Håstad Advanced Algorithms [4] Andrew J Manson Speaker Matching Bài báo nói thuật tốn tìm ghép đồ thị tổng quát, trường hợp đồ thị có trọng số [5] Eva Milková Graph Theory and Information Technology Một số thuật toán toán bao trùm tối tiểu [6] Dave Mount Design and Analysis of Computer Algorithms [7] Nguyễn Xuân My, Trần Đỗ Hùng, Lê Sĩ Quang Một số vấn đề chọn lọc tin học Cuốn sách phù hợp cho học sinh phổ thơng trung học u thích việc giải tốn tin học [8] Nguyễn Đức Nghĩa, Nguyễn Tơ Thành Toán rời rạc Một sách dành cho sinh viên ngành tin học [9] Kenneth H Rosen Discrete Mathematics and its Applications (Bản dịch tiếng Việt: Toán học rời rạc ứng dụng tin học) Cuốn sách viết dạng giáo trình dễ hiểu, có hệ thống tập xếp khoa học [10] Robert Sedgewick Algorithms (Bản dịch tiếng Việt: Cẩm Nang Thuật Toán) Một sách tiện lợi cho tra cứu, đầy đủ thuật toán kinh điển In memory of committed teachers and excellent students Le Minh Hoang ... Kuhn-Munkres mục trước 12.6 NÂNG CẤP Dựa vào mơ hình cài đặt thuật tốn Kuhn-Munkres trên, ta đánh giá độ phức tạp tính tốn lý thuyết cách cài đặt này: Đại học Sư phạm Hà Nội, 199 9- 2 002 Các thuật. .. 56 110 120 210 242 321 330 430 4 49 19 ASSIGN.OUT Optimal assignment: 1) X[1] - Y[1] 2) X[2] - Y[4] 3) X[3] - Y[2] 4) X[4] - Y[3] Cost: 19 X Y P_4_12_1.PAS * Thuật toán Hungari program AssignmentProblemSolve;... trừ ∆ vào trọng số tất cạnh liên thuộc với X[i] tương đương với việc tăng Fx[i] lên ∆ Việc cộng ∆ vào trọng số tất cạnh liên thuộc với Y[j] tương đương Đại học Sư phạm Hà Nội, 199 9- 2 002 Các thuật