Phương thức push-relabel cho phép chúng ta áp dụng các thao tác cơ bản trong một số thứ tự thông dụng bằng cách lựa chọn cẩn thận thứ tự và quản lý cấu trúc dữ liệu mạng một cách hiệu quả. Tuy nhiên, chúng ta có thể giải quyết vấn đề luồng cực đại nhanh hơn O(V2E) (xem hệ quả 26.26). Bây giờ chúng ta sẽ xem xét
thuật toán relabel-to-front, một thuật toán push-relabel chạy với độ phức tạp tính toán là O(V3), với đường tiệm cận tại điểm tối ưu nhất là O(V2E), và tốt hơn cho một mạng dày đặt.
Thuật toán relabel-to-front chứa đựng một danh sách các đỉnh trong mạng (trừ đỉnh phát và đỉnh thu). Bắt đầu từ đỉnh trước tiên, thuật toán duyệt qua danh sách các đỉnh, lập lại việc chọn một đỉnh tràn u và sau đó “xoá bỏ” nó. Việc xóa bỏ một đỉnh tràn u tương ứng với việc thực hiện các thao tác push và relabel cho đến khi đỉnh u không còn tràn nữa. Khi một đỉnh được gán lại nhãn (relabel), nó được chuyển tới trước danh sách và thuật toán bắt đầu duyệt lại lần nữa.
Việc phân tích tính đúng đắn của thuật toán relabel-to-front phụ thuộc vào khái niệm các cạnh có thể chấp nhận được (admissible edges): đó là những cạnh trong mạng thặng dư mà thông qua nó luồng có thể được đẩy xuống. Sau khi chứng minh một vài thuộc tính về các cạch có thể chấp nhận được của mạng, chúng ta sẽ kiểm tra nhanh thao tác xoá một đỉnh, trình bày và phân tích chính thuật toán relabel-to-front.
Những cạnh và mạng có thể chấp nhận được.
Nếu G=(V,E) là một mạng vận tải với đỉnh đầu s và đỉnh cuối t, f là một luồng trước đó trong G, và h là một hàm chiều cao (height function), chúng ta nói rằng (u,v) là một cạnh có thể chấp nhận được nếu cf(u,v)>0 và h(u) = h(v)+1. Ngược lại, (u,v) là cạnh không thể chấp nhận được. Mạng có thể chấp nhận được là Gf,h = (V, Ef,h) trong đó Ef,h được lập từ các cạnh có thể chấp nhận được.
Mạng có thể chấp nhận được gồm những cạnh mà thông qua nó luồng có thể được đẩy xuống. Bổ đề sau chỉ ra rằng mạng này là một đồ thị có hướng không có chu trình.
Bổ đề 26.27 (mạng có thể chấp nhận được không có chu trình)
Nếu G=(V,E) là một mạng vận tải, f là một luồng trước trong G, và h là hàm chiều cao trong G, khi đó mạng có thể chấp nhận được Gf,h = (V, Ef,h) không có chu trình.
Chứng minh: Kiểm chứng bằng phép phản chứng, giả sử Gf,h chứa chu trình p=(v0,
v1,…vk), với v0 = vk và k>0. vì mỗi cạnh trong p được chấp nhận, ta có h(vi-1) = h(vi) + 1 với i=1, 2,…, k. Từ đó ta có: ∑= k i 1 h(vi-1) = ∑= k i 1 (h(vi) +1) =∑= k i 1 h(vi) +k
Bởi vì mỗi cạnh trong chu trình p xuất hiện một lần cho mỗi lần tính tổng suy ra k=0, mâu thuẩn với giả thiết k>0. vậy Gf,h không có chu trình.
Hai bổ đề tiếp theo cho thấy các thao tác đẩy và gán lại nhãn làm thay đổi mạng có thể chấp nhận được như thế nào.
Bổ đề 26.28
Cho G = (V,E) là một mạng vận tải, cho f là một luồng trước trong G, và giả sử rằng h là một hàm chiều cao, u là đỉnh tràn và (u,v) là cạnh có thể chấp nhận được, sau đó áp dụng PUSH(u,v). Thao tác không tạo ra các cạnh mới có thể chấp
nhận được, nhưng nó có thể là nguyên nhân khiến (u,v) trở nên không chấp nhận được.
Chứng minh:
Do (u,v) là cạnh có thể chấp nhận được, và u là đỉnh tràn nên có thể thực hiện đẩy luồng từ u đến v. Khi thực hiện PUSH(u,v) thì cạnh thặng dư mới có thể được tạo ra chỉ là (v,u). Bởi vì h(v)=h(u)-1 vì vậy (v,u) không thể trở nên chấp nhận được. Nếu thao tác là một lực đẩy bảo hoà thì cf (u,v) =0 và cạnh (u,v) trở nên không thể chấp nhận được.
Bổ đề 26.29
Cho G = (V,E) là một mạng vận tải, f là một luồng trước đó trong G, giả sử rằng thuộc tính h là một hàm chiều cao. Nếu u là một đỉnh tràn và không có các cạnh chấp nhận được xuất phát từ u thì áp dụng RELABEL(u). Sau khi thực hiện thao tác gán lại nhãn, thì có ít nhất một cạnh có thể chấp nhận được xuất phát từ u, nhưng cũng không có các cạnh có thể chấp nhận đi vào u.
Chứng minh:
Nếu u là đỉnh tràn thì theo Bổ đề 26.15, hoặc là thao tác push hoặc là thao tác relabel sẽ được áp dụng cho nó. Nếu không có những cạnh có thể chấp nhận được xuất phát từ u, thì luồng không thể được đẩy ra từ u và vì thế RELABEL(u) được áp dụng. Sau khi thực hiện thao tác gán lại nhãn, h[u] = 1 + min{h[v]: (u,v) ∈ Ef}. Như vậy, nếu v là một đỉnh mà có giá trị nhỏ nhất trong tập này, thì cạnh (u,v) trở thành cạnh có thể chấp nhận được. Do đó sau khi gán lại nhãn, có ít nhất một cạnh có thể chấp nhận được xuất phát từ u.
Để chỉ ra rằng, không có cạnh có thể chấp nhận được đi vào u sau khi thực hiện thao tác gán lại nhãn, giả sử rằng có một đỉnh v mà (v,u) là cạnh có thể chấp nhận được, khi đó h[v] = h[u] + 1 sau khi gán lại nhãn, và như vậy thì trước khi gán lại nhãn h[v] > h[u] +1. Nhưng theo bổ đề 26.13, không có những cạnh thặng dư tồn tại giữa các đỉnh mà độ cao chênh lệch nhau hơn 1 đơn vị. Ngoài ra, việc gán lại nhãn cho một đỉnh không làm thay đổi mạng thặng thừa. Vì vậy, (v,u) không thuộc mạng thặng dư, và do đó nó không thể thuộc mạng có thể chấp nhận được.
Danh sách kề (Neighbor list)
Các đỉnh trong thuật toán relabel-to-front được tổ chức thành “danh sách kề”. Cho một mạng vận tải G = (V,E), Danh sách kề N[u] với u ∈ V là một danh sách liên kết đơn của các láng giềng của u trong G. Vì vậy, đỉnh v xuất hiện trong danh sách N[u] nếu (u,v) ∈ E hoặc (v,u) ∈ E. Danh sách kề N[u] chứa chính xác những đỉnh v mà trong đó (u,v) có thể là cạnh thặng dư. Đỉnh đầu tiên trong N[u] được trỏ bởi head[N[u]]. Đỉnh v tiếp theo trong danh sách kề được trỏ bởi next- neighbor[v], con trỏ này là NIL nếu v là đỉnh cuối trong danh sách kề.
Xoá bỏ một đỉnh tràn
Một đỉnh tràn u được xoá bằng cách đẩy tất cả luồng tràn qua những cạnh có thể chấp nhận được đến những đỉnh láng giềng. Việc gán lại nhãn cho u là cần thiết để các cạnh xuất phát từ u trở thành có thể chấp nhận được. Giải thuật như sau:
DISCHARGE(u) 1 While e[u]>0 do 2 v:=current[u] 3 If v=nil then 4 RELABEL(u) 5 current[u]:= head[N[u]]
6 else If cf(u,v)>0 and h[u] = h[v] +1 then
7 PUSH(u,v)
8 Else Current[u] next-neighbor[v]
Hình 26.9 mô tả sự hoạt động của vòng lặp While trong thủ tục
DISCHARGE. Mỗi vòng lặp thực hiện chính xác một trong ba thao tác, phụ thuộc vào đỉnh v hiện thời trong danh sách kề N[u].
1. Nếu v=Nil thì chúng ta thoát và kết thúc của N[u]. Dòng 4 gán lại nhãn cho đỉnh u, và sau đó dòng 5 khởi tạo lại cạnh kề hiện thời của u là đỉnh đầu tiên trong N[u].
2. Nếu v khác NIL và (u,v) là cạnh có thể chấp nhận được (xác định bởi dòng kiểm tra số 6), sau đó dòng 7 thực hiện đẩy một vài (hoặc có thể tất cả) luồng tràn của u cho đỉnh v.
3. Nếu v khác NIL, nhưng (u,v) là không thể chấp nhận, khi đó dòng 8 tăng current[u] lên một vị trí nữa trong danh sách kề N[u].
Hình 26.9 Thực hiện việc loại bỏ một đỉnh y. Nó thực hiện 15 lần lặp vòng while trong thủ tục DISCHARGE để đẩy tất cả luồng tràn từ y, Chỉ những đỉnh kề của y và những cạnh đi vào hoặc đi ra từ y được cho thấy. Trong mỗi phần, con số bên trong mỗi đỉnh (ngoại trừ đỉnh s) chính là giá trị luồng tràn tại đỉnh đó; chiều cao của các đỉnh được biểu thị bằng các số từ 0 đến 6 ở phía bên trái của các hình; các đỉnh được tô sáng trong các vòn lặp khi nó được duyệt qua.
(a) Trước hết, có 19 đơn vị dư thừa được đẩy từ y (e[y]=19), và current[y] = s. Ở các vòng lặp 1, 2, và 3 không thỏa mãn điều kiện để đẩy luồng tràn từ y; đến vòng lặp thứ 4 thì current[y] = NIL (phần mờ bên duới danh sách kề), vì thế y được gán lại nhãn và current[y] được khởi tạo lại cho trỏ đến đầu của danh sách kề N[y].
(b) Sau khi thực hiện gán lại nhãn, đỉnh y có độ cao là 1. Trong vòng lặp 5, 6, các cạnh (y,s) và (y,x) được nhận thấy là không thể chấp nhận được, nhưng 8 đơn vị của luồng tràn được đẩy từ y đến z trong vòng lặp 7. Mặt dù được đẩy, current[y] không được tăng lên trong vòng lặp.
(c) Bởi vì việc đẩy trong vòng lặp 7 làm bảo hoà cạnh (y,z), nó được nhận ra là không thể chấp nhận trong vòng lặp 8. Trong vòng lặp 9, current[y]= NIL, và cũng vì thế đỉnh y được gán lại nhãn thêm lần nữa và current[y] được khởi tạo lại.
(d) Trong vòng lặp 10, (y,s) là không thể chấp nhận được, nhưng 5 đơn vị của luồng tràn được đẩy từ y đến x trong vòng lặp 11.
(e) Bởi vì current[y] không được tăng thêm trong vòng lặp 11, vòng lặp 12 cho thấy (y,x) trở nên không chấp nhận được. Vòng lặp 13 cho thấy (y,z) trở nên không chấp nhận được, và vòng lặp 14 gán lại nhãn cho đỉnh y và khởi tạo lại current[y].
(f) Vòng lặp 15 đẩy 6 đơn vị của luồng quá mức từ y đến s.
(g) Đỉnh y bây giờ không còn tràn luồng, và DISCHARGE hoàn thành. Trong ví dụ này, DISCHARGE cả khi bắt đầu cũng như khi kết thúc con trỏ current luôn trỏ vào đầu danh sách kề, nhueng trong trường hợp tổng quát thì điều này không đúng.
Nhận xét rằng, nếu DISCHARGE được gọi trên một đỉnh tràn u, thì hành động cuối cùng được thực hiện bởi DISCHARGE phải được đẩy từ u. Tại sao? Thủ tục kết thúc chỉ khi e[u] bằng 0, và cả thao tác gán lại nhãn lẫn thao tác tăng con trỏ current[u] đều chịu sự tác động của giá trị e[u].
Chúng ta phải chắc chắn rằng khi PUSH hoặc RELABEL được gọi bởi DISCHARGE, thao tác được áp dụng. Bổ đề tiếp sẽ chứng tỏ thực tế này.
Bổ đề 26.30
Nếu DISCHARGE gọi PUSH(u, v) ở dòng 7, thì thao tác đẩy được áp dụng cho (u,v).
Nếu DISCHARGE gọi RELABEL(u) ở dòng 4, thì thao tác gán lại nhãn được áp dụng cho u.
Chứng minh:
Kiểm tra dòng 1 và dòng 6 để chắc chắn rằng một thao tác đẩy chỉ xảy ra nếu thao tác đó được áp dụng, điều này được chứng minh ở đoạn đầu tiên trong bổ đề.
Để chứng minh đoạn 2, Thông qua việc kiểm dòng 1 và Bổ đề 26.29, chúng ta chỉ cần chỉ ra rằng tất cả các cạnh xuất phát từ u là không thể chấp nhận. thủ tục DISCHARGR được gọi lặp đi lặp lại, con trỏ current[u] di chuyển xuống cuối danh sách N[u]. Mỗi “chu kỳ” duyệt qua danh sách N[u] thì current[u] bắt đầu từ vị trí đầu tiên trong N[u] và kết thúc với current[u]=Nil. Khi u được gán lại nhãn thì một chu trình mới lại bắt đầu. Khi con trỏ current[u] chuyển đến đỉnh v thuộc N[u] thì cạnh (u,v) phải được tin rằng là không thể chấp nhận được bằng lệnh kiểm tra ở dòng 6. Vì vậy theo thời gian mọi cạnh xuất phát từ u đã được xác định trở thành không thể chấp nhận ở một số thời điểm. Khi thủ tục DISCHARGE kết thúc thì mọi cạnh xuất phát từ u đều không thể chấp nhận. Tại sao? Theo bổ đề 26.28 thì việc đẩy không thể tạo ra một số cạnh có thể chấp nhận được. Vì vậy, một số cạnh có thể chấp nhận được phải được tạo ra bởi thao tác relable. Nhưng trong suốt quá trình duyệt qua các đỉnh trong danh sách kề N[u] đỉnh u không được gán lại nhãn,
theo bổ đề 26.29 thì sau thao tác gán lại nhãn không có một số cạnh có thể chấp nhận được đi vào u. Vì vậy, khi kết thúc tất cả các cạnh xuất phát từ u lưu trạng thái không chấp nhận được và Bổ đề đã được chứng minh.
Thuật toán RELABEL-TO-FRONT
Trong thuật toán relabel-to-front, chúng ta lưu trữ một danh sách liên kết L gồm tất cả các đỉnh trong V – {s, t}. Một thuộc tính khoá là các đỉnh trong L được sắp sếp Topo theo mạng có thể chấp nhận được, như chúng ta sẽ thấy trong vòng lặp bất biến dưới đây (Gọi lại từ Bổ đề 26.27 ta có mạng có thể chấp nhận được là một mạng yếu – tức là mạng không có chu trình).
Giả như thuật toán relabel-to-front thừa nhận rằng, danh sách kề N[u] đã được tạo ra cho mỗi đỉnh u. Nó cũng thừa nhận rằng con trỏ next[u] trỏ đến đỉnh sau u trong danh sách L và next[u] = NIL nếu u là đỉnh cuối cùng trong danh sách.
RELABEL-TO-FRONT(G, s, t) 1 INITIALIZE-PREFLOW(G, s) 2 L := V[G] - {s, t}, theo thứ tự 3 for each dinh u ∈ V[G] - {s, t} 4 do current[u] := head[N[u]] 5 u := head[L]
7 do begin old-height := h[u] 8 DISCHARGE(u)
9 if h[u] > old-height
10 then chuyển u đế đầu danh sách L 11 u := next[u]
Thuật toán relabel-to-front làm việc như sau:
- Dòng 1 khởi tạo luồng đầu tiên và độ cao các đỉnh như trong thuật toán push-relabel tổng quát.
- Dòng 2 khởi tạo danh sách L chứa tất cả các đỉnh có khả năng tràn luồng theo thứ tự bất kỳ. Dòng 3-4 khởi tạo con trỏ hiện thời – current của mỗi đỉnh u trỏ đến đỉnh đầu tiên trong danh sách kề của N[u].
Như trình bày trong Hình 26.10, vòng lặp while thuộc dòng 6 đến dòng 11 chạy duyệt qua danh sách L, xoá bỏ các đỉnh. Dòng 5 được tạo ra để bắt đầu làm việc với đỉnh đầu tiên trong danh sách L. Mỗi lần thực hiện qua vòng lặp, một đỉnh u bị xoá bỏ trong dòng 8. Nếu u được gán lại nhãn bằng thủ tục DISCHARGE, dòng 10 di chuyển nó đến trước danh sách L. Điều này được xác định bằng việc lưu giữ chiều cao của u trong biến old-height trước khi thao tác xoá bỏ (dòng 7) và sự so sánh chiều cao được lưu giữ này với chiêu cao của u về sau (dòng 9). Dòng 11 thực hiện lần lặp tiếp theo của vòng lặp while, sử dụng đỉnh sau đỉnh u trong danh sách L. Nếu u đã được di chuyển đến trước danh sách, đỉnh sử dụng trong lần lặp tiếp theo là một đỉnh sau u với vị trí mới của nó trong danh sách.
(a) Một mạng vận tải trước khi lặp trong vòng while, khởi đầu, 26 đơn vị của luồng rời điểm phát s. Danh sách khởi tạo L = (x, y, z), giá trị khởi tạo u=x. Dưới mỗi đỉnh u trong danh sách L là danh sách kề của nó N[u], đỉnh u được đánh mờ trong danh sách L là đỉnh hiện tại đang xét còn đỉnh mờ trong danh sách N là đỉnh mà current[u] đang trỏ tới. Đỉnh x đã được xoá. Nó được gán lại nhãn độ cao 1; 5 đơn vị của luồng tràn được đẩy đến y, và 7 đơn vị được còn lại của luồng tràn được đẩy đến đỉnh cuối t. Bởi vị x đã được gán lại nhãn, nó được di chuyển đến vị trí đầu của L, mà trong trường hợp này không có sự thay đổi cấu trúc của L
(b) Sau x, đỉnh tiếp theo được loại bỏ trong L là y. Hình 26.9 trình bày chi tiết thao tác loại bỏ y tại vị trí này. Bởi vị y được gán lại nhãn, nó được di chuyển đến vị trí đầu của L.
(c) Đỉnh x bây giờ theo sau y trong L, và vì thế nó lại được loại bỏ lần nữa, đẩy tất cả 5 đơn vị của luồng tràn đến t. Bởi vị đỉnh x không được gán lại nhãn trong thao tác loại bỏ này. Nó được duy trì vị trí trong danh sách L.
(d) Vì đỉnh z theo sau đỉnh x trong L, nó là bị loại bỏ. Nó được gán lại nhãn chiều cao là 1 và tất cả 8 đơn vị của luồng tràn được đẩy đến t. Bởi vì z được gán lại nhãn, nó được di chuyển đến trước của L.
(e) Đỉnh y bây giờ theo sau đỉnh z trong L và sẽ được loại bỏ. Nhưng bởi vì y không tràn luồng, DISCHARGE ngay lập tức thoát ra, và y duy trì vị trí trong L. Đỉnh x sẽ được loại bỏ. Bởi vì nó cũng không tràn luồng nên DISCHARGE lại thoát ra, và x duy trì vị trí trong L. RELABEL-TO-FRONT đạt đến cuối danh sách L và kết thúc. Không có đỉnh tràn luồng và luồng trước là luồng cực đại.
Để chỉ ra rằng RELABEL-TO-FRONT tính được một luồng cực đại, chúng ta sẽ chỉ ra rằng đó là một sự thực hiện đầy đủ của thuật toán push-relabel tổng quát. Trước hết, nhận xét rằng nó thực hiện các thao tác đẩy và gán lại nhãn chỉ khi