Chu-Liu-Edmondsův algoritmus (Chu-Liu-Edmond's algorithm), byl nezávisle objeven v roce 1965 (Chu a Liu), 1967 (Edmonds) a 1971 (Bock). Tento algoritmus slouží k nalezení minimální (maximální) kostry orientovaného grafu, což je výrazně složitější problém, než je hledání kostry neorientovaného grafu, pro které existuje řada jednoduchých a efektivních algoritmů - například Borůvkův, Jarník-Primův nebo Kruskalův algoritmus.
Princip
Chu-Liu-Edmondsův se dá rozdělit na dvě fáze. V té první dochází k eliminaci prokazatelně přebytečných hran a postupné kontrakci cyklů do pseudouzlů reprezentujících cykly a přehodnocení hran vedoucích do těchto pseudouzlů. V druhé fázi jsou cykly postupně zpětně rozevírány a jsou vybrány pouze hrany, které patří do minimální kostry.
Tarjanova implementace
Tento článek se dále bude zabývat pouze Tarjanovou implementací Edmondsova algoritmu (pro minimální kostru) se zahrnutými Cameriniho připomínkami. Původní verze algoritmu uveřejněná v roce 1977 Robertem Tarjanem obsahovala chybu, díky které nezaručoval optimalitu nalezené kostry. Tyto nedostatky byly opraveny v roce 1979 úpravou druhé fáze Tarjanova algoritmu.
Datové struktury
Tarjanova implementace využívá několika datových struktur, jejichž účel zde bude pro přehlednost popsán.
- roots - Obsahuje komponenty, do kterých dosud nevede žádná hrana.
- queues[i] - Obsahuje prioritní fronty hran vedoucích do komponenty i.
- enter[i] - Pro každou komponentu i obsahuje hranu, která do ní aktuálně vede.
- h - Obsahuje hrany, které mohou být v kostře (přecházejí do druhé fáze).
- rset - Obsahuje kořenové komponenty výsledného lesa.
- s - Obsahuje reprezentanty silných komponent (všechny uzly z dané silné komponenty mají společného reprezentanta).
- w - Obsahuje reprezentanty slabých komponent (všechny uzly z dané slabé komponenty mají společného reprezentanta).
- min - Označuje uzly, které mohou být kořeny výsledného minimálního lesa (tzn. min[rset[i]] je kořenem v minimálním lese).
- fRoots - Kořeny lesa F (Cameriniho připomínky). V lese F je uzlem každá hrana z h. Pokud hrana v vede do kontrahovaného cyklu c, pak jsou všechny hrany z c potomky hrany v v lese F.
- lambda[i] - Listy lesa F (tj. hrany, které nevedou do žádného cyklu). Platí, že lambda[i] obsahuje hranu v(k, i).
- cycles[i] - Obsahuje hrany cyklu silné komponenty reprezentované uzlem i
Disjoint sety s a w mají tu vlastnost, že slučují vždy do prvního reprezentanta. Prioritní fronty queues řadí hrany dle jejich váhy vzestupně. Pokud ve frontě již není žádná hrana, tak vrátí dummy hranu.
První fáze algoritmu
Na začátku jsou všechny uzly v roots, protože do nich nevede ještě žádná hrana. Prvním krokem algoritmu je smyčka - dokud roots obsahuje uzly, tak algoritmus vybere náhodně jeden uzel k z roots a vybere pro něj z odpovídající fronty nejkratší hranu v(i, k). Pokud je tato hrana dummy, pak je daný uzel zřejmě root komponentou optimálního lesa, protože do něj nevedou žádné hrany.
Pokud tato hrana není dummy, tak mohou nastat následující situace. Buď jsou oba uzly ve stejné silné komponentě a byla tedy nalezena hrana v rámci silné komponenty - je třeba proto k vrátit do roots a pokračovat smyčkou v prvním kroku. Nebo se tak nestalo, a v tom případě musí algoritmus tuto hranu zpracovat.
Pokud jsou uzly i a k v různých slabých komponentách, pak dojde ke spojení těchto slabých komponent algoritmus se vrací na výběr uzlu z roots. Pokud jsou ve stejné slabé komponentě, tak vznikla silná komponenta (cyklus), protože k bylo v roots (neobsahovalo žádnou vstupní hranu).
V rámci nově vzniklé komponenty, reprezentované uzlem k, je zapotřebí učinit následující úkony. Nejprve je zapotřebí snížit všem vstupním hranám uzlů un daného cyklu prioritu o , kde currWeight je váha hrany, která vstupuje do uzlu un v rámci cyklu a maxWeight je váha maximální hrany cyklu. Tímto se docílí penalizace hran, které by v případě svého ponechání v kostře poškodily optimalitu. Po přehodnocení těchto hran je třeba sloučit haldy hran všech uzlů v cyklu do haldy uzlu k. Protože k nyní vystupuje v roli pseudouzlu, do nějž zatím nevstupují žádné hrany, tak se k přidá do roots. Algoritmus se opět vrací na výběr uzlu z roots.
Druhá fáze algoritmu
Nyní již seznam h obsahuje pouze hrany, které mohou být v kostře. V originální Tarjanově implementaci se provedl průchod do hloubky z jednotlivých kořenů minimálního lesa, avšak tento postup nefunguje, protože druhé z Tarjanových lemmat bylo vyvráceno (o jednoznačnosti cesty mezi vrcholem a uzlem komponenty). Z tohoto důvodu je zapotřebí ziskat minimální les jiným způsobem.
Camerini ve svém článku navrhuje následující postup.
Nechť R je množina kořenových uzlů minimálního lesa, čili platí . Nechť B je prázná množina hran minimálního lesa. Pak opakujme následující algoritmus, dokud množina R a množina kořenových uzlů lesa F (fRoots) nejsou prázdné.
- Pokud je množina R neprázdná, pak odstraň jeden z jejích prvků (v). V opačném případě odstraň jeden z vrcholů lesa F a přidej jej do množiny B.
- Identifikuj cestu mezi listem
a příslušným kořenem lesa F.
- Odstraň z lesa F všechny uzly (a hrany z nich vedoucí) na cestě identifikované v předchozím kroku. Tato operace mění obsah množiny fRoots.
Časová složitost
Tarjanova implementace má časovou složitost v nejhorším případě a v průměrném
za předpokladu, že dílčí operace mají následující složitost.
Prioritní fronta se inicializuje alespoň v čase , operace min -
, spojení dvou front -
, operace add -
. Čili celkem všechny operace proritních front zaberou celkem
jednotek času.
Požadované složitosti operací disjoint-setů jsou: find - , union -
.
Modifikace algoritmu
Modifikace pro husté grafy
Zatímco v řídkých grafech tento algoritmus funguje se složitostí , tak pro husté grafy lze dosáhnout složitosti
. Modifikace spočívá v nahrazení prioritních front běžnými seznamy. Seznam pro každou silnou komponentu S obsahuje pouze jednu minimální hranu
takovou, že
(pozn. z implementačního hlediska jsou uzly i a j obvykle reprezentanty silných komponent). Tento seznam je řazen dle i.
Modifikace pro kořenové kostry
Kořenovou kostru lze získat jednoduchou úpravou vstupního grafu - stačí vymazat hrany, které vstupují do požadovaného kořenu.
Maximální kostra
Pro získání maximální orientované kostry daného grafu stačí v algoritmu změnit prioritu front a pozměnit část algoritmu, která kontrahuje uzly do pseudouzlů (otočit znaménka, nalézt hranu s minimální vahou a upravit příslušným způsobem vzorec).
Kód
001.
/**
002.
* Tarjanova implementace Edmondsova algoritmu se zapracovanymi Cameriniho
003.
* pripominkami. Hleda minimalni orientovanou kostru
004.
* Jedna se o implementaci pro ridke grafy, jejiz operace nejsou optimalni
005.
* ve smyslu clanku Roberta Tarjana "Finding optimum branchings", ale
006.
* k optimalite nemaji daleko.
007.
* @author Pavel Micka
008.
*/
009.
public
class
EdmondsAlgorithm {
010.
private
Queue<Integer> roots;
//silne komponenty, ktere jsou koreny v aktualnim grafu (nevede do nich zadny uzel)
011.
private
BinomialHeap[] queues;
//vstupni hrany do danych uzlu (razene dle priority)
012.
private
Edge[] enter;
//vstupni hrany do silnych komponent
013.
private
List<Edge>[] h;
//hrany, ktere mohou byt v kostre
014.
private
List<Integer> rset;
//rooti vyslednych stromu Tarjanovy implementace
015.
private
DisjointSet s;
//silne komponenty (reprezentanti)
016.
private
DisjointSet w;
//slabe komponenty (reprezentanti)
017.
private
int
[] min;
//urcuje finalni korenove uzly orientovanych koster
018.
private
int
nodeCount;
//pocet uzlu zadaneho grafu
019.
private
List<Edge> fRoots;
//koreny lesa F
020.
private
List<Edge>[] cycles;
//nalezene cykly
021.
private
Edge[] lambda;
//listy lesa F
022.
023.
public
EdmondsAlgorithm(
int
nodeCount) {
024.
roots =
new
LinkedList<Integer>();
025.
queues =
new
BinomialHeap[nodeCount];
026.
enter =
new
Edge[nodeCount];
//vstupni hrany silnych komponent
027.
h =
new
ArrayList[nodeCount];
//mnozina hran (optimalni kostra)
028.
rset =
new
ArrayList<Integer>();
//finalni koreny
029.
w =
new
DisjointSet(nodeCount);
030.
s =
new
DisjointSet(nodeCount);
031.
min =
new
int
[nodeCount];
032.
033.
this
.fRoots =
new
ArrayList<Edge>();
034.
cycles =
new
ArrayList[nodeCount];
035.
lambda =
new
Edge[nodeCount];
036.
037.
for
(
int
i =
0
; i < nodeCount; i++) {
038.
roots.add(i);
039.
queues[i] =
new
BinomialHeap(
1400000
);
//kapacita, ktera by mela stacit pro dane pripady
040.
h[i] =
new
ArrayList<Edge>();
041.
enter[i] =
new
Edge(
0
,
0
, Integer.MIN_VALUE);
042.
min[i] = i;
043.
}
044.
}
045.
046.
/**
047.
* Demonstrace Edmondsova algorimu
048.
*
049.
* Na vstupu (standard in) jsou data ve tvaru
050.
*
051.
* 6
052.
* 0 1 50
053.
* 0 2 60
054.
* 1 3 50
055.
* 2 3 1
056.
* 3 5 1000
057.
* 0 4 2
058.
* 3 0 1
059.
* 0 3 30
060.
* 0 3 300
061.
* 0 0 0
062.
*
063.
* Kde prvni radek je pocet uzlu, nasledujici radky reprezentuji hrany
064.
* ve tvaru (z do vaha) a posledni radek (0 0 0) znaci konec vstupu
065.
*
066.
* Algoritmus vypise koren a hrany minimalni kostry
067.
* @param args the command line arguments
068.
*/
069.
public
static
void
main(String[] args)
throws
FileNotFoundException, IOException {
070.
BufferedReader reader =
new
BufferedReader(
new
FileReader(
new
File(
"edmondsData/soubor.txt"
)),
9192
);
071.
072.
String line = reader.readLine();
073.
int
nodeCount = Integer.valueOf(line);
074.
075.
EdmondsAlgorithm o =
new
EdmondsAlgorithm(nodeCount);
076.
o.readEdges(reader);
077.
ResultWrapper r = o.edmondsAlgorithm();
078.
079.
for
(Integer i : r.getRoots()){
080.
System.out.println(i);
081.
}
082.
for
(Edge e : r.getEdges()){
083.
System.out.println(e);
084.
}
085.
}
086.
087.
/**
088.
* Precte hrany ve tvaru
089.
* E1 E1 WEIGHT
090.
* ze zadaneho readeru
091.
* @param reader
092.
* @param nodeCount pocet uzlu
093.
* @throws IOException
094.
*/
095.
private
void
readEdges(BufferedReader reader)
throws
IOException {
096.
String line =
null
;
097.
while
((line = reader.readLine()) !=
null
) {
098.
StringTokenizer tokenizer =
new
StringTokenizer(line,
" "
);
099.
int
from = Integer.valueOf(tokenizer.nextToken());
100.
int
to = Integer.valueOf(tokenizer.nextToken());
101.
int
weight = Integer.valueOf(tokenizer.nextToken());
102.
103.
if
(from == to)
continue
;
104.
105.
Edge e =
new
Edge(from, to, weight);
106.
queues[to].insert(e);
107.
}
108.
}
109.
/**
110.
* Tarjanova implementace Edmonsova algoritmu
111.
* @return minimalni kostra orientovaneho grafu
112.
*/
113.
private
ResultWrapper edmondsAlgorithm() {
114.
while
(roots.size() !=
0
) {
115.
int
k = roots.poll();
116.
Edge edge = min(k);
117.
if
(!(edge.getFrom() ==
0
&& edge.getTo() ==
0
)) {
118.
if
(cycles[s.find(k)] ==
null
|| cycles[s.find(k)].size() ==
1
) {
119.
lambda[k] = edge;
120.
}
else
if
(s.find(edge.getFrom()) != s.find(edge.getTo())) {
121.
for
(Edge e : cycles[s.find(k)]) {
122.
fRoots.remove(e);
//je v cyklu, nemuze byt rootem
123.
edge.getfChildren().add(e);
124.
e.setfParent(edge);
125.
}
126.
}
127.
}
128.
if
(edge.getFrom() ==
0
&& edge.getTo() ==
0
) {
//pokud je to dummy hrana
129.
rset.add(k);
//pak uzel nema vstupni hranu (je root)
130.
}
else
if
(s.find(edge.getFrom()) == k) {
//nalezli jsme hranu v ramci silne komponenty
131.
roots.add(k);
//uzel vratimem do fronty
132.
}
else
{
133.
h[edge.getFrom()].add(edge);
//tato hrana je adeptem na hranu MST
134.
fRoots.add(edge);
//tato hrana je korenem v lese F
135.
if
(w.find(edge.getFrom()) != w.find(edge.getTo())) {
//pocatecni a koncovy uzel jsou v ruznych slabych komponentach
136.
w.union(w.find(edge.getFrom()), w.find(edge.getTo()));
137.
enter[k] = edge;
//edge je vstupni hranou do k
138.
}
else
{
//pocatecni a koncovy uzel jsou ve stejne slabe komponente
139.
Edge cycleEdge = edge;
140.
int
maxVal = Integer.MIN_VALUE;
141.
int
vertex = Integer.MIN_VALUE;
142.
143.
List<Edge> cycle =
new
ArrayList<Edge>();
144.
while
(!(cycleEdge.getFrom() ==
0
&& cycleEdge.getTo() ==
0
)) {
//hledame hranu v cyklu s maximalni vahou (ktera nejvice poskodi cyklus)
145.
cycle.add(cycleEdge);
146.
if
(cycleEdge.getWeight() > maxVal) {
147.
maxVal = cycleEdge.getWeight();
148.
vertex = s.find(cycleEdge.getTo());
149.
}
150.
cycleEdge = enter[s.find(cycleEdge.getFrom())];
151.
}
152.
cycles[s.find(k)] = cycle;
153.
queues[k].decreaseAllKeys(edge.getWeight() - maxVal);
154.
min[k] = min[vertex];
155.
156.
//collapse cycle
157.
cycleEdge = enter[s.find(edge.getFrom())];
158.
while
(!(cycleEdge.getFrom() ==
0
&& cycleEdge.getTo() ==
0
)) {
159.
queues[s.find(cycleEdge.getTo())].decreaseAllKeys(cycleEdge.getWeight() - maxVal);
//pridame prichozim hranam hodnotu, ktera znaci, o kolik se vypustenim dane cyklove hrany a pridanim dane necyklove zhorsi reseni
160.
queues[k].mergeHeaps(queues[s.find(cycleEdge.getTo())]);
161.
s.union(k, s.find(cycleEdge.getTo()));
162.
cycleEdge = enter[s.find(cycleEdge.getFrom())];
163.
}
164.
roots.add(k);
165.
}
166.
}
167.
}
168.
return
leaf();
169.
}
170.
171.
/**
172.
* Slouzi k identifikaci minimalni kostry dle Cameriniho pripominek
173.
* @return minimalni kostra grafu
174.
*/
175.
private
ResultWrapper leaf() {
176.
List<Edge> b =
new
ArrayList<Edge>();
177.
List<Integer> r =
new
ArrayList<Integer>();
178.
int
fRootIndex =
0
;
179.
OUTER:
180.
while
(!rset.isEmpty() || fRootIndex < fRoots.size()) {
181.
int
v =
0
;
182.
if
(!rset.isEmpty()) {
183.
v = min[rset.remove(
0
)];
184.
r.add(v);
185.
}
else
{
186.
Edge e = fRoots.get(fRootIndex);
187.
if
(e.isDeleted()) {
188.
fRootIndex++;
189.
continue
OUTER;
190.
}
191.
v = e.getTo();
192.
b.add(e);
193.
fRootIndex++;
194.
}
195.
Edge curr = lambda[v];
196.
while
(curr !=
null
&& curr.isDeleted() ==
false
) {
197.
curr.setDeleted(
true
);
198.
fRoots.addAll(curr.getfChildren());
199.
curr = curr.getfParent();
200.
}
201.
}
202.
return
new
ResultWrapper(r, b);
203.
}
204.
205.
/**
206.
* Odstrani a vrati minimalni hranu vstupujici do daneho uzlu
207.
* @param k cislo uzlu
208.
* @return minimalni vstupni hrana, pokud hrana neexistuje - dummy hrana
209.
* z uzlu 0 do uzlu 0 s vahou Integer.MIN_VALUE
210.
*/
211.
private
Edge min(
int
k) {
212.
Edge e = queues[k].returnTop();
213.
if
(e ==
null
) e =
new
Edge(
0
,
0
, Integer.MIN_VALUE);
214.
return
e;
215.
}
216.
}
217.
218.
/**
219.
* Binomialni halda
220.
* Radi prvky dle priority (mensi == vyssi priorita)
221.
* @author Pavel Micka
222.
*/
223.
class
BinomialHeap {
224.
private
Node<Edge>[] nodes;
225.
private
Node<Edge> min;
226.
private
int
offset =
0
;
227.
228.
public
BinomialHeap(
double
capacity) {
229.
min =
null
;
230.
nodes =
new
Node[((
int
) (Math.log(capacity) / Math.log(
2
))) +
2
];
231.
}
232.
233.
public
void
decreaseAllKeys(
int
ammout) {
234.
this
.offset -= ammout;
235.
}
236.
237.
/**
238.
* Vlozi prvek do haldy, pokud je mensi nez aktualni minimalni, tak
239.
* jej ulozi jako nejmensi prvek
240.
* @param e
241.
*/
242.
public
void
insert(Edge e) {
243.
Node<Edge> n =
new
Node<Edge>(e);
244.
if
(nodes[
0
] !=
null
) merge(n, nodes[
0
]);
245.
else
nodes[
0
] = n;
246.
247.
if
(min ==
null
) min = n;
248.
else
if
(min.value.compareTo(n.value) ==
1
) min = n;
249.
}
250.
251.
/**
252.
* Smaze a vrati uzel s nejvyssi prioritou
253.
* @return
254.
*/
255.
public
Edge returnTop() {
256.
if
(min ==
null
)
return
null
;
257.
258.
Edge tmp = (Edge) min.value;
259.
tmp.setWeight(tmp.getWeight() +
this
.offset);
260.
nodes[min.order] =
null
;
//strom vyjmeme
261.
for
(Node n : min.children) {
//a z potomku udelame nove stromu
262.
n.parent =
null
;
263.
if
(nodes[n.order] !=
null
) merge(n, nodes[n.order]);
264.
else
nodes[n.order] = n;
265.
}
266.
min.children.clear();
267.
Edge minVal =
null
;
268.
Node minNode =
null
;
269.
for
(
int
i =
0
; i < nodes.length; i++) {
270.
if
(nodes[i] !=
null
) {
271.
if
(minVal ==
null
) {
272.
minVal = nodes[i].value;
273.
minNode = nodes[i];
274.
}
else
if
(minVal.compareTo(nodes[i].value) ==
1
) {
275.
minVal = nodes[i].value;
276.
minNode = nodes[i];
277.
}
278.
}
279.
}
280.
min = minNode;
281.
return
(Edge) tmp;
282.
}
283.
284.
/**
285.
* Provede slouceni binomialnich stromu, je-li nutno provede slucovani
286.
* do vyssich radu
287.
* alespon jedna z hald musi byt bezpodminecne jiz soucasti haldy, obe haldy
288.
* musi byt stejneho radu
289.
* @param a halda 1
290.
* @param b halda 2
291.
* @return sloucena halda
292.
*/
293.
private
void
merge(Node a, Node b) {
294.
if
(a.order != b.order)
295.
throw
new
IllegalArgumentException(
"Haldy nejsou stejneho radu"
);
296.
int
tmpOrder = a.order;
297.
nodes[tmpOrder] =
null
;
298.
Node newRoot =
null
;
299.
if
(a.value.compareTo(b.value) <
0
) {
300.
b.parent = a;
301.
a.children.add(b);
302.
a.order++;
303.
newRoot = a;
304.
}
else
{
305.
a.parent = b;
306.
b.children.add(a);
307.
b.order++;
308.
newRoot = b;
309.
}
310.
if
(nodes[tmpOrder +
1
] ==
null
) nodes[tmpOrder +
1
] = newRoot;
311.
else
merge(newRoot, nodes[tmpOrder +
1
]);
312.
}
313.
314.
/**
315.
* Slouci haldy, pokud mergovana halda obsahuje mensi prvek, nez halda, na
316.
* ktere je volana operace, pak je tento prvek minimalni i v nove halde
317.
* (Toto lze napsat lepe)
318.
* @param heap
319.
*/
320.
public
void
mergeHeaps(BinomialHeap heap) {
321.
for
(
int
i =
0
; i < heap.nodes.length; i++) {
322.
if
(heap.nodes[i] !=
null
) {
323.
decreaseWeights(
this
.offset - heap.offset, heap.nodes[i]);
324.
if
(nodes[i] ==
null
) nodes[i] = heap.nodes[i];
325.
else
merge(nodes[i], heap.nodes[i]);
326.
}
327.
}
328.
if
(
this
.min ==
null
)
this
.min = heap.min;
329.
else
if
(
this
.min !=
null
&& heap.min !=
null
&& heap.min.value.getWeight() < min.value.getWeight())
330.
min = heap.min;
331.
}
332.
333.
@Override
334.
public
String toString() {
335.
StringBuilder builder =
new
StringBuilder();
336.
for
(
int
i =
0
; i < nodes.length; i++) {
337.
if
(nodes[i] ==
null
) builder.append(i +
": null\\n"
);
338.
else
builder.append(i +
":\\n"
+ nodes[i].toString() +
"\\n"
);
339.
}
340.
return
builder.toString();
341.
}
342.
343.
private
void
decreaseWeights(
int
ammount, Node<Edge> node) {
344.
node.value.setWeight(node.value.getWeight() - ammount);
345.
for
(Node n : node.children) {
346.
decreaseWeights(ammount, n);
347.
}
348.
}
349.
350.
/**
351.
* Reprezentuje uzel binomialni haldy
352.
* @param <ENTITY>
353.
*/
354.
private
class
Node<ENTITY
extends
Comparable> {
355.
356.
Node<ENTITY> parent;
357.
ENTITY value;
358.
List<Node> children;
359.
int
order;
//rad binomialni haldy
360.
361.
public
Node(ENTITY value) {
362.
this
.value = value;
363.
children =
new
ArrayList<Node>();
364.
order =
0
;
365.
}
366.
367.
@Override
368.
public
String toString() {
369.
String s =
"Node value: "
+ value +
", order: "
+ order +
"\\n"
;
370.
for
(Node n : children) {
371.
s += n.toString();
372.
}
373.
return
s;
374.
}
375.
}
376.
}
377.
378.
/**
379.
* Hrana orientovaneho grafu
380.
* @author malejpavouk
381.
*/
382.
class
Edge
implements
Comparable<Edge> {
383.
private
Edge fParent;
//rodic ve stromu F
384.
private
List<Edge> fChildren;
//deti ve stromu F
385.
private
int
from;
//uzel z
386.
private
int
to;
//uzel do
387.
private
int
weight;
//vaha
388.
private
int
originalWeight;
//puvodni vaha hrany
389.
private
boolean
deleted;
//priznak smazani hrany
390.
391.
public
Edge(
int
from,
int
to,
int
weight) {
392.
this
.from = from;
393.
this
.to = to;
394.
this
.weight = weight;
395.
this
.originalWeight = weight;
396.
this
.deleted =
false
;
397.
398.
this
.fChildren =
new
ArrayList<Edge>();
399.
}
400.
401.
public
int
compareTo(Edge o) {
402.
if
(
this
.getWeight() < o.getWeight())
return
-
1
;
403.
else
if
(
this
.getWeight() > o.getWeight())
return
1
;
404.
return
0
;
405.
}
406.
407.
@Override
408.
public
String toString() {
409.
return
getFrom() +
" "
+ getTo() +
" "
+ getWeight();
410.
}
411.
412.
/**
413.
* @return the fParent
414.
*/
415.
public
Edge getfParent() {
416.
return
fParent;
417.
}
418.
419.
/**
420.
* @param fParent the fParent to set
421.
*/
422.
public
void
setfParent(Edge fParent) {
423.
this
.fParent = fParent;
424.
}
425.
426.
/**
427.
* @return the fChildren
428.
*/
429.
public
List<Edge> getfChildren() {
430.
return
fChildren;
431.
}
432.
433.
/**
434.
* @param fChildren the fChildren to set
435.
*/
436.
public
void
setfChildren(List<Edge> fChildren) {
437.
this
.fChildren = fChildren;
438.
}
439.
440.
/**
441.
* @return the from
442.
*/
443.
public
int
getFrom() {
444.
return
from;
445.
}
446.
447.
/**
448.
* @param from the from to set
449.
*/
450.
public
void
setFrom(
int
from) {
451.
this
.from = from;
452.
}
453.
454.
/**
455.
* @return the to
456.
*/
457.
public
int
getTo() {
458.
return
to;
459.
}
460.
461.
/**
462.
* @param to the to to set
463.
*/
464.
public
void
setTo(
int
to) {
465.
this
.to = to;
466.
}
467.
468.
/**
469.
* @return the weight
470.
*/
471.
public
int
getWeight() {
472.
return
weight;
473.
}
474.
475.
/**
476.
* @param weight the weight to set
477.
*/
478.
public
void
setWeight(
int
weight) {
479.
this
.weight = weight;
480.
}
481.
482.
/**
483.
* @return the originalWeight
484.
*/
485.
public
int
getOriginalWeight() {
486.
return
originalWeight;
487.
}
488.
489.
/**
490.
* @param originalWeight the originalWeight to set
491.
*/
492.
public
void
setOriginalWeight(
int
originalWeight) {
493.
this
.originalWeight = originalWeight;
494.
}
495.
496.
/**
497.
* @return the deleted
498.
*/
499.
public
boolean
isDeleted() {
500.
return
deleted;
501.
}
502.
503.
/**
504.
* @param deleted the deleted to set
505.
*/
506.
public
void
setDeleted(
boolean
deleted) {
507.
this
.deleted = deleted;
508.
}
509.
}
510.
511.
/**
512.
* Implementace Union-Find problemu pro Edmondsuv algoritmus
513.
* @author malejpavouk
514.
*/
515.
class
DisjointSet {
516.
private
Node[] nodes;
517.
518.
public
DisjointSet(
int
nodeCount) {
519.
nodes =
new
Node[nodeCount];
520.
for
(
int
i =
0
; i < nodeCount; i++) {
521.
nodes[i] =
new
Node();
522.
nodes[i].id = i;
523.
}
524.
}
525.
526.
/**
527.
* Provede sjednoceni komponent, ve kterych jsou uzly "a" a "b"
528.
* Union provadi vzdy do A
529.
* @param a cislo uzlu a
530.
* @param b cislo uzlu b
531.
* @return cislo reprezentanta sjednocene komponenty
532.
*/
533.
public
int
union(
int
a,
int
b) {
534.
Node repA = nodes[find(a)];
535.
Node repB = nodes[find(b)];
536.
537.
repB.parent = repA;
538.
return
repA.id;
539.
}
540.
541.
/**
542.
* Vrati reprezentanta zadaneho uzlu
543.
* @param a cislo uzlu, jehoz reprezentanta hledame
544.
* @return cislo reprezentanta uzlu
545.
*/
546.
public
int
find(
int
a) {
547.
Node n = nodes[a];
548.
int
jumps =
0
;
549.
while
(n.parent !=
null
) {
550.
n = n.parent;
551.
jumps++;
552.
}
553.
if
(jumps >
1
) repair(a, n.id);
554.
return
n.id;
555.
}
556.
/**
557.
* Provede kontrakci cesty do korene
558.
* @param a list, ze ktereho se zacina kontrahovat
559.
* @param rootId id rootu
560.
*/
561.
private
void
repair(
int
a,
int
rootId) {
562.
Node curr = nodes[a];
563.
while
(curr.id != rootId){
564.
Node tmp = curr.parent;
565.
curr.parent = nodes[rootId];
566.
curr = tmp;
567.
}
568.
}
569.
@Override
570.
public
String toString() {
571.
StringBuilder builder =
new
StringBuilder();
572.
for
(
int
i =
0
; i < nodes.length; i++) {
573.
builder.append(find(i) +
" "
);
574.
}
575.
return
builder.toString();
576.
}
577.
578.
/**
579.
* Uzel n-arniho stromu
580.
*/
581.
private
static
class
Node {
582.
/**
583.
* Rodic
584.
*/
585.
Node parent;
586.
/**
587.
* Identifikator uzlu
588.
*/
589.
int
id;
590.
}
591.
}
592.
593.
/**
594.
* Prepravka na vysledek Edmondsova algoritmu
595.
* @author malejpavouk
596.
*/
597.
class
ResultWrapper {
598.
private
List<Integer> roots;
//hrany minimalniho lesa
599.
private
List<Edge> edges;
//hrany minimalniho lesa
600.
601.
public
ResultWrapper(List<Integer> roots, List<Edge> edges) {
602.
this
.roots = roots;
603.
this
.edges = edges;
604.
}
605.
/**
606.
* @return the roots
607.
*/
608.
public
List<Integer> getRoots() {
609.
return
roots;
610.
}
611.
612.
/**
613.
* @return the edges
614.
*/
615.
public
List<Edge> getEdges() {
616.
return
edges;
617.
}
618.
}
Literatura
- TARJAN, Robert. Finding Optimum Branchings. In Networks : Vol. 7. [s.l.] : [s.n.], 1977. s. 25-35.
- CAMERINI, P. M., FRATTA, L., MAFFIOLI, F. A Note on Finding Optimum Branchings. In Networks : Vol. 9. [s.l.] : [s.n.], 1979. s. 309-312.
- TOFIGH, Ali. Optimum Branchings and Spanning Aborescences. [s.l.] : [s.n.], 2009. 11 s.