L R (a) (b)
5. Một đồ thị kép G=(V,E) với tập đỉnh V= R là đầy đủ bậc d (d-regular)
26.5 Thuật toán Relabel-to-front
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 vài loại. Bởi việc lựa chọn cẩn thận các loại và quản lý cấu trúc dữ liệu mạng có hiệu nghiệm. Tuy nhiên, chúng ta có thể giải quyết vấn đề bằng luồng cực đại nhanh hơn O(V2E), xem hệ quả (corollary) 26.26. Chúng ta bây giờ phải xem xét thuật toán
relabel-to-front , một thuật toán push-relabel chạy với mức độ phức tạp 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. Bắt đầu đỉnh trước tiên, thuật toán duyệt qua danh sách, lập lại việc chọn một đỉnh đầy u và sau đó “xoá bỏ” nó, đó là biểu diễn các thao tác push và relabel đến khi u chắc chắn không còn nữa. Mỗi lần một đỉnh được đánh nhãn lại (relabel), nó được di chuyển tới trước danh sách (kể từ nhãn có tên “relabel-to-front”) và thuật toán bắt đầu duyệt lại lần nữa.
Sự đúng đắn và phân tích 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): những cạnh còn lại trong mạng đi qua mà luồng có thể được chen vào. Sau khi chứng minh một vài thuộc tính về mạng của các cạch có thể chấp nhận được, chúng ta sẽ kiểm tra nhanh thao tác xoá bỏ hiện tại 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 luồng 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), sau đó chúng ta nói rằng (u,v) là một cạnh có thể chấp nhận được nếu cf(v,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ậ được là Gf,h = (V, Ef,h) ở chổ 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 kết nối mà luồng có thể được đẩy ra. Bổ đề sau cho rằng mạng này là một đồ thị có hướng không có chu trình (dag).
Bổ đề 26.27 (mạng có thể chấp nhận được không chu trình)
Nếu G=(V,E) là một mạng luồng, 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, cho Gf,h chứa chu trình p=(v0,
v1,…vk), với v0 = vk và k>0. Từ 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. Chu trình tổng như sau:
∑=k 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, ta lấy được trái ngược rằng 0 = k.
Bước tiếp theo của bổ đề cho thấy cách thao tác đẩy và gán lại nhãn làm thay đổi mạng có thể chấp nhận được.
Bổ đề 26.28
Cho G = (V,E) là một mạng luồng, cho f là một luồng trước trong G, và giả sử rằng thuộc tính h là một hàm chiều cao, nếu một đỉnh u được chứa và (u,v) là cạnh có thể chấp nhận được, sau đó áp dụng PUSH(v,v). Thao tác không tạo một vài cạnh mới
có thể chấp nhận được, nhưng nó có lẽ là nguyên nhân (u,v) trở nên không chấp nhận được.
Chứng minh:
Bằng định nghĩa một cạnh có thể chấp nhận được, luồng có thể đẩy từ u đến v. Từ u là đầy, áp dụng thao tác PUSH(u,v). Chỉ cạnh mới còn lại mà có thể tạo bởi luồng đang đẩy từ u đến v được cạnh (u,v). Từ h[v] = h[u] – 1, cạnh (u,v) không thể trở thành cạnh có thể chấp nhận được. Nếu thao tác là một lực đẩy bảo hoà, cf (u,v) =0 sau đó 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 luồng, 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 một đỉnh u là một đỉnh đầy đủ và có các cạnh không chấp nhận tách khỏi u, sau đó á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 tách khỏi u, nhưng cũng không có các cạnh có thể chấp nhận nối vào u.
Chứng minh:
Nếu u là đầy đủ, bằng Bổ đề 26.15, mỗi thao tác push hoặc relabel áp dụng cho nó. Nếu không có những cạnh có thể chấp nhận được tách khỏi u, thì không có luồng nào có thể được đẩy ra từ u và cũng áp dụng RELABEL(u). 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à thu được giá trị nhỏ nhất trong giả thiết 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 tách khỏi u.
Cũng cho biết thêm rằng, không có cạnh chấp nhận được đưa vào 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à (u,v) là cạnh chấp nhận được. Sau đó, h[v] = h[u] + 1 sau khi gán lại nhãn, và vì thế h[v] > h[u] +1 trước khi gán lại nhãn. Nhưng theo bổ đề 26.13, không có cạnh còn lại tồn tại giữa các đỉnh mà độ cao không bằng như 1. Ngoài ra, gán lại nhãn một đỉnh không làm thay đổi mạng dư thừa. Theo cách đó, (u,v) không thuộc mạng dư thừa, và kể từ đó nó không thuộc trong mạng có thể chấp nhận được.
Danh sách kề (neighbor lists)
Các cạnh trong thuật toán relabel-to-front được tổ chức trong “danh sách kề”. Cho một mạng luồng G = (V,E), Danh sách kề N[u] thuộc đỉnh 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. Do đó, đỉnh v xuất hiện trong danh sách N[v] 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 đó có thể là một cạnh thừa (u,v). Đỉnh đầu tiên trong N[u] là trỏ đầu bởi head[N[u]]. Đỉnh theo sau v trong một 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ề.
Chu trình thuật toán relabel-to-front đi qua mỗi danh sách kề trong một cách sắp xếp tuỳ ý, mà được bố trí từ đầu đến cuối phép thực hiện trong thuật toán. Ở mỗi đỉnh
u, trường hiện tại current[u] trỏ đến đỉnh hiện tại dưới sự suy xét trong N[u]. Ban đầu, current[u] được thiết lập bởi head[N[u]].
Xoá bỏ một đỉnh đầy đủ
Một đỉnh đầy đủ u được xoá bằng tất cả số dôi thừa của chính nó qua những cạnh có thể chấp nhận được với những đỉnh bên cạnh. Gán lại nhãn u là lý do cần thiết các cạnh tách khỏi u trở thành có thể chấp nhận được. Giả thuật như sau:
DISCHARGE(u) 1 While e[u]>0 do 2 v ← current[u] 3 If v=nil then 4 RELABELl(u) 5 current[u] ← head[N[u]]
6 else If cf(u,v)>0 anh h[u] = h[v] +1 then
7 PUSH(u,v)
8 Else Current[u] next-neighbor[v]
Hình 26.9 từng bước lập lại vài lần của vòng lặp while loop của dòng 1-8, mà thực hiện càng lâu đỉnh u vượt mức rỏ ràng. Mỗi lần lặp trình diễn chính xác một lần của ba thao tác, phụ thuộc vào đỉnh v hiện thời trong danh sách kề N[u].
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 để được một cạnh đầu tiên trong N[u]. (Bổ đề 26.30 trạng thái dưới đây mà thao tác gán lại nhãn áp dụng trong trường hợp này).
Nếu v khác NIL và (u,v) là cạnh có thể chấp nhận được (quyết định bởi việc chạy thử trong dòng 6), sau đó dòng 7 đẩy một vài (hoặc có thể tất cả) các đỉnh dư thừa của u cho đỉnh v.
<Hình trang 684>
Hình 26.9 Loại bỏ một đỉnh y, Nó thực hiện 15 lần lặp vòng while của lệnh DISCHARGE để đẩy tất cả những luồng dư thừa 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 được thừa chính nó ngay tại bắt đầu của vòng lặp đầu tiên hiển thị trong mỗi phần. và mỗi đỉnh được hiển thị ở trong suốt phần độ cao của nó. Đúng ra được hiển thị danh sách kề N[y] tại vị trí bắt đầu mỗi lần lặp, với số lặp trên cùng. Đỉnh kề được làm mở là current[y].
(a) Trước hết, có 19 đơn vị dư thừa được đẩy từ y, và current[y] = s. Lặp 1, 2, và 3 nên chuyển đến current[y], từ khi không có cạnh chấp nhận rời khỏi y. Trong lần lặp 4, current[y] = NIL (hiển thị phần mờ bên duới danh sách kề).và vì thế, y được gán lại nhãn và current[y] được khởi tạo lại cho trỏ đầu của danh sách kề.
(b) Sau khi thực hiện gán lại nhãn, đỉnh y có độ cao là 1. Trong vong 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 dư thừa đượ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 dư thừa đượ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 luồng quá mức, và DISCHARGE hoàn thành. Trong ví dụ này, DISCHARGE đã cả bắt đầu và kết thúc với trỏ đầu hiện thời của danh sách kề, nhưng tổng quát trường hợp này là không cần thiết.
<Hình trang 685>
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].
Nhận xét rằng, nếu DISCHARGE được gọi trong một cạnh u đầy đủ, thì thao tác cuối cùng thực hiện bởi DISCHARGE phải lại một sự đẩ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 và cả tăng thêm của con trỏ current[u] tác động đến giá trị của e[u].
Chúng ta phải chắc chắn rằng khi PUSH hoặc RELABEL được gọi bởi DISCHARGE, áp dụng thao tác. Bổ đề tiếp cung cấp thực tế này.
Bổ đề 26.30
Nếu DISCHARGE gọi PUSH(u, v) trong dòng 7, sau đó thao tác đẩy được áp dụng cho (u,v).
Nếu DISCHARGE gọi RELABEL(u) trong dòng 4, thì sau đó thao tác gán lại nhãn áp dụng cho u.
Chứng minh:
Kiểm tra thử trong dòng 1 và dòng 6 chắc chắn rằng một thao tác đẩy xuất hiện chỉ nếu thao tác áp dụng mà xác nhận đoạn đầu tiên trong bổ đề.
Chứng minh đoạn 2, Theo kiểm tra tại dòng 1 và Bổ đề 26.29, chúng ta chỉ cần xem tất cả các cạnh tách khỏi u là cạnh không thể chấp nhận. Nhận xét rằng DISCHARGE(u) được gọi lại nhiều lần, con trỏ current[u] chuyển xuống danh sách N[u]. Mỗi “chu kỳ” tại điểm đầu của N[u] và kết thúc với current[u] = NIL, mà tại đó con trỏ u được gán lại nhãn và chu kỳ mới bắt đầu. Mỗi lần con trỏ current[u] tăng lên qua một đỉnh v ∈N[u] suốt một chu kỳ, cạnh (u,v) phải là không thể chấp nhận trong
kiểm tra dòng tại dòng 6. Do đó, mỗi lần chu kỳ được kết thúc, mọi cạnh tách khỏi u được xác định là không thể chấp nhận được ở một số lần trong suốt chu kỳ. Nhận thấy khoá được rằng, vào lúc cuối của chu kỳ, mọi cạnh tách khỏi u vẫn không thể chấp nhận được. Tại sao? Bằng Bổ đề 26.28, việc đẩy không thể tạo nên một vài cạnh có thể chấp nhận được, để có một cạnh đơn tách khỏi u. Do đó, một vài cạnh có thể chấp nhận được tại bằng một thao tác gán lại nhãn. Nhưng đỉnh u không được gán lại nhãn suốt chu kỳ, bằng Bổ đề 26.29, một vài đỉnh khác v mà được gán lại nhãn suốt chu trình không đưa vào các cạnh không thể chấp nhận được sau khi gán lại nhãn. Do đó, tại lần cuối của chi kỳ, tất cả các cạnh tách khỏi u vẫn không thể 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 duy 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á mà 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 mà mạng không được chấp nhận là một mạng yếu - dag).
Giả như thuật toán relabel-to-front thừa nhận rằng, danh sách kề N[u] thực sự được tạo ra cho mỗi đỉnh u, nó cũng thừa nhận rằng co trỏ next[u] trỏ đến đỉnh sau u trong danh sách L và do đó, như thường lệ 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} trong vài thứ tự 3 trong mỗi đỉnh u ∈V(G) - {s,t} 4 do current[u] ← head[N[u]] 5 u ← head[L] 6 while u <> NIL 7 do old-heght ← h[u] 8 DISCHARGE(u) 9 If h[u] > old-height
10 then di chuyển u đến trước của 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à đặt độ cao các giá trị như nhau 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 đầy đủ mạnh, trong vài thứ tự. Dòng 3-4 khởi tạo con trỏ hiện thời – current của mỗi đỉnh u đến đỉnh đầu tiên trong danh sách kề của u.
Như trình bày trong Hình 26.10, vòng lặp while của dòng 6-11 chạy qua danh sách L, xoá bỏ các đỉnh. Dòng 5 tạo nên nó bắt đầu bằng đỉnh đầu tiên trong danh sách. Mỗi lần thực hiện qua vòng lặp, mỗi đỉ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. Xác định này được tạo ra bởi 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 việc lưu giữ chiều cao này đến chiêu cao của u về sau (dòng 9). Dòng 11 tạo nên lặp lại tiếp theo của vòng lặp while sử dụng các đỉnh sau u trong danh sách L. Nếu u đã được di chuyển đến trước danh sách, đỉnh sử dụng trong vòng lặp tiếp theo là một đỉnh sau u trong vị trí mới của nó trong danh sách.
Để RELABEL-TO-FRONT tính một luồng cực đại, chúng ta cần trình bày 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 thao tác đẩy và gán lại nhãn chỉ khi chúng được áp dụng, từ Bổ đề 26.30 cam đoan rằng DISCHARGE chỉ thực hiện chúng khi chúng áp dụng. Nó vẫn trình bày rằng khi RELABEL-TO-FRONT kết thúc, không có thao tác cơ bản nào được áp dụng. Phần còn lại của tham số đúng đắn dựa theo vòng lặp bất biến.
Tại mỗi lần kiểm tra trong dòng 6 của RELABEL-TO-FRONT, danh sách L là một hình topo sắp xếp các cạnh trong mạng có thể chấp nhận được Gf,h =(V, Ef,h) và không có đỉnh trước u trong danh sách có luồng vượt quá giới hạn.
Khởi tạo: Ngay lặp tức sau khi INITIALIZE-PROFLOW được chạy, h[s] = |V| và h[v] = 0 cho tất cả v ∈V – {S}. Từ khi |V| >= 2 ( bởi vị V chứa tổi thiểu s và t), không có cạnh có thể được chấp nhận . Do đó, Ef,h = φ, và một số trình tự trong V-{s,
t} là một sắp xếp topo của Gf,h.
<Hình trang 688>
Hình 26.10 Việc làm của RELABEL-TO-FRONT
(a) Một mạng luồng trước khi lặp trong vòng while, khởi đầu, 26 đơn vị của luồng rời điểm đầu s. Đúng ra là hiển thị danh sách khởi tạo L = (x, y, z), nơi mà khởi tạo u=x. Dưới mỗi đỉnh trong danh sách L là danh sách kề của nó, bằng đỉnh mờ bên cạnh đỉnh hiện tại. Đỉnh x đã được xoá. Nó được gán lại nhãn độ cao 1, 5 đơn vị của luồng quá mức được đẩy đến y, và 7 đơn vị được duy trì của phần vượt trội đượ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ói được di chuyển đến vị trí đầu của L.