1.5 Eksempel: Forén og find


Dynamic connectivity example

Dynamisk sammenhæng

Betragt følgende problem: Indput er en sekvens af heltalspar. Hvert heltal repræsenterer et objekt af en eller anden slags, fx en person i et socialt netværk, et kontaktpunkt i et elektrisk kredsløb, et variabelnavn i et program eller en position i en krystal. Den ønskede fortolkning af parret p q er, at objektet p forbindes med objektet q. Vi antager, at «er forbundet med» opfylder kravene til en ækvivalensrelation:

En ækvivalensrelation opdeler objekterne i ækvivalensklasser, også kaldet sammenhængskomponenter.

Vores mål er at skrive et program som fjerner overflødige par fra sekvensen: Det indlæste heltalspar p q skal kun rapporteres, hvis p og qs sammenhæng ikke allerede er givet af tidligere indlæste par. Ellers skal programmet ignorere parret p q og fortsætte med at indlæse det næste par af heltal.


APG forén-og-find.

Følgende programmeringsgrænseflade indkapsler de grundlæggende opererationer for den abstrakte datype forén-og-find. Den opretholder en unikt komponentnavn for hver komponent. I begyndelsen tilhører hvert element sin egen komponent.

public class UF
UF(int A) \(N\) elementer med heltalsnavne fra \(\{0,\ldots,N-1\}\) uden forbindelser
voidunion(int p, int q)tilføj forbindelse mellem \(p\) og \(q\)
intfind(int p)komponentnavn for \(p\) (en værdi i \(\{0,\ldots, N-1\}\))
booleanconnected(int p, int q)tilhører \(p\) og \(q\) samme komponent?
intcount()antal komponenter


For at teste den abstrakte datatype løser metoden main() i UF.java det dynamiske sammenhængsproblem. Der er tre filer med testdata: Filen tinyUF.txt indeholder de 11 forbindelser fra det lille eksempel øverst til højre, filen mediumUF.txt indeholder 900 forbindelser og filen largeUF.txt indeholder millioner af forbindelser.

Implementationer.

Vi betragter nu flere forskellige implementationer af forén-og-find. Alle er baseret på en række id[] af heltalsværdier, indekseret af elementerne. Således bruger vi værdierne id[p] og id[q] for at afgøre, elementerne \(p\) og \(q\) tilhører samme komponent.

Omkostningsmodel for forén-og-find.

For at vurdere algoritmer for forén og find, tæller vi antallet af rækkeadgange, dvs. antallet af læse- og skriveoperationer til indgange i rækken id[].

Definitioner.

Størrelsen af et træ er antallet af træets knuder. Dybden af en knude i træet er antallet af henvisninger på vejen fra knuden til træet rod. Træets højde er den største dybde af dets knuder.

Påstand.

Hurtigt fund anvender \(1\) rækkeadgang for hvert kald af find() og mellem \(N+3\) og \(2N+1\) rækkeadgange for hvert kald af union(), der forener to komponenter.

Påstand.

Antallet af rækkeadgange for et kald af find(p) i hurtig forening er \(1+2d(p)\), hvor \(d(p)\) er dybden af den knude, der svarer til elementet \(p\). Antallet af rækkeadgange for et kald af union() og connected() er omkostningen for de to kald af find() (plus 1 for union() hvis de to givne elementer er i forskellige træer).

Påstand.

Dybden af hver knude i skoven konstrueret af vægtet, hurtig forening af \(N\) elementer er højst \(\operatorname{lg} N\).

Korollar.

Værstefaldstilvæksten af omkostningen per operation for find(), connected() og union() ved vægtet, hurtig forening af \(N\) elementer er højst \(\operatorname{lg} N\).
Algoritme konstruktion forening fund
hurtigt fund \(N\) \(N\) \(1\)
hurtig forening \(N\) træhøjde træhøjde
vægtet, hurtig forening \(N\) \(\operatorname{lg} N\) \(\operatorname{lg} N\)
vægtet, hurtig forening med vejforkortning \(N\) meget tæt på 1 (amortiseret) se opgave 1.5.13 i [SW]
umulig \(N\) \(1\) \(1\)
Køretider for forskellige implementationer af forén-og-find. Tabellen angiver vækstraten af tidsforbruget per operation for \(N\) elementer.