Union Find Datenstruktur

Kirby.exe

Top Contributor
Also wir sollen einen Minimum Spanning Tree erstellen mittels Kruskal Algorithmus. Hierzu soll ebenfalls die Union-Find Datenstruktur verwendet werden. Bei dieser Datenstruktur gibt es noch ein paar Unklarheiten.

Was genau passiert in dem MakeSet() und was passiert in Union() ?
 
Ehrlich gesagt nicht wirklich 🙁 Ich verstehe nicht was der Repräsentant sein soll. Ist das einfach das andere Ende der Kante oder was ist das?

Außerdem verstehe ich nicht wirklich was nun bei Union passiert, wird das Array erweitert oder wie? Wird der Eintrag einfach ersetzt? Oder wie wird dieser "neue" Repräsentant bestimmt?
 
Der Repräsentant ist einfach ein Element der Menge, was diese Menge identifiziert (Bei einer Menge mit einem Element ist es genau dieses eine Element)
Beispiel: Die Menge sind die Klassen einer Schule. Die Elemente der Menge sind die Schüler in der Klasse. Dann ist der Repräsentant einfach der "Klassensprecher" der Menge. Und wenn zwei Mengen M1 und M2 zusammengelegt werden, dann ist der neue Repräsentant einfach der Klassensprecher von M1.
 
Also ich bin immer noch etwas verwirrt...xD Scheinbar ist das also eine Unterteilung in Sub Mengen welche per Referenz immer auf einen Kindes Knoten zeigen richtig? Was ich jedoch noch nicht verstanden habe ist, wie ich den einen gegebenen Knoten in diese Sub Mengen unterteile? Also nach welchen Kriterien?
 
Kritieren: Egal. Initial nimmt man einfach irgendein Element.
Wenn man mit komplett disjunkten Mengen startet (wie z.B. bei Kruskal, wo am Anfang jeder Knoten eine eigene Menge ist), dann ist der Knoten automatisch der Repräsentant.

Kruskal:
Angenommen du hast 10 Knoten v1 bis v10. Dann ist du initial folgende 10 Mengen (R = Repräsentant)

{ v1 } (R: v1)
{ v2 } (R: v2)
{ v3 } (R: v3)
{ v4 } (R: v4)
{ v5 } (R: v5)
{ v6 } (R: v6)
{ v7 } (R: v7)
{ v8 } (R: v8)
{ v9 } (R: v9)
{ v10 } (R: v10)

Dann läuft der Algorithmus los und will die Kante zwischen v1 und v5 aufnehmen.
find(v1) = v1 (Repräsentant von v1)
find(v5) = v5 (Repräsentant von v5)
=> Unterschiedlich, also unterschiedliche Teilbäume
Union(v1, v5) => { v1, v5} (R: v1}
Neue Mengen:
{ v1, v5 } (R: v1)
{ v2 } (R: v2)
{ v3 } (R: v3)
{ v4 } (R: v4)
{ v6 } (R: v6)
{ v7 } (R: v7)
{ v8 } (R: v8)
{ v9 } (R: v9)
{ v10 } (R: v10)
usw.

Als Datenstruktur wählt man da gerne Bäume, weil ein Union(M1, M2) einfach heißt "Hänge die Menge M2 unter einen Knoten der Menge M1.
 
Ahhh ich verstehe 🙂 Also ich nehme meine Kantenmenge und würde diese aufsteigend nach Gewicht sortieren. Danach würde ich dann mit der Datenstruktur anfangen und die Kanten der Reihenfolge nach linken oder? Als ergebnis sollte dann der Spanning Tree rauskommen?
 
Bei Kruskal ist die Union-Find Datenstruktur die Knoten-Menge. Die Kanten werden in einer simplen Datenstruktur gehalten, die einfach nach Kantengewicht sortiert ist.

Edit: Also ja, deine Aussage ist richtig (hatte sie zuerst falsch gelesen).
 
Also ich habe jetzt meine grobe Union Find Datenstruktur, jedoch habe ich das Gefühl, dass ich falsch initialisiere... ich dachte, dass ich mit ein Elementigen Mengen anfange also ist der Repräsentant an Index i = i oder?

Hier ist meine Implementation:

Java:
public class UnionFind {
  
    int [] parents;
    int [] ranks;
  
    public UnionFind(int size) {
        parents = new int[size+1];
        ranks = new int[size+1];
    }
  
    public void union(int index1, int index2) {
        int temp1 = find(index1);
        int temp2 = find(index2);
      
        if(temp1 != temp2) link(temp1, temp2);
    }
  
    public void makeSet() {
        for(int i = 0; i < parents.length; i++) {
            parents[i] = i;
            ranks[i] = 0;
        }
    }
  
    private void link(int node1, int node2) {
        if (ranks[node1] > ranks[node2]) parents[node2] = node1;
        else {
            parents[node1] = node2;
            if (ranks[node1] == ranks[node2]) ranks[node2]++;
        }
    }
  
    private int find(int index) {
        return parents[index];
    }
  
    public int[] getTree() {
        return parents;
    }
}
 
Zuletzt bearbeitet:
Oder vielleicht anders:

Ich bin immernoch etwas verwirrt...Den ich erkenne an dem Ergebnis nicht so wirklich einen Spanning Tree da zwar 6 von 6 Elementen in einer Menge sind (also den gleichen Repräsentanten haben) aber ich erkenne keine Struktur oder Hierarchie 🙁
 
Naja, du musst dir auch die Kanten merken, die dazu geführt haben, das ein Knoten hinzugefügt wurde. Diese Kanten sind die Kanten des Minimum Spanning Trees
 
Naja, du musst dir auch die Kanten merken, die dazu geführt haben, das ein Knoten hinzugefügt wurde. Diese Kanten sind die Kanten des Minimum Spanning Trees
Naja das tue ich hier:

Java:
public void runOp() {
        ArrayList<GraphNode> spanningTree = new ArrayList<>();
        ArrayList<GraphNode> unusedNodes = graph.getAllNodes();
        ArrayList<Integer> usedNodes = new ArrayList<>();
        UnionFind s = new UnionFind(graph.numberOfNodes);
       
        Collections.sort(unusedNodes);
       
        while(!unusedNodes.isEmpty()) {
            GraphNode current = unusedNodes.remove(0);
            if(s.find(current.startNode) != s.find(current.endNode)) {
                spanningTree.add(current);
                s.link(s.find(current.startNode), s.find(current.endNode));
            }
           
        }
       
        this.printSpanningTree(spanningTree);
    }

Es läuft aber ewig und ein Freund von mir hat gesagt, dass wenn man es richtig man es eigentlich nur 1-2 Sec dauern sollte...xD
 
Ich verstehe den Code nicht- Wo ist die Liste der Kanten und wo der Knoten? Du musst über die Liste der Kanten iterieren und die Union-Datenstruktur über die Liste der Knoten führen. Warum hat bei dir ein GraphNode einen Start und ein Ende?
 
Oh das war eine scheiß bennenung meinerseits...GraphNode sollte GraphArc sein xD Das sind die Kanten, deswegen Start und endKnoten 🙂

UnusedNodes sollte unusedArcs heißen 🙂
 
Wenn deine Union Datenstruktur nur noch eine Menge enthält solltest du auch abbrechen. Es ist ziemlich witzlos, dann die ganzen restlichen Kanten zu prüfen.
 
ja gut xD Ich reduziere das Array in dem Sinne nicht xD Also müsste ich hier mit einer Schleife über das Array laufen und schauen ob alle den gleichen Repräsentanten haben xD zumindest fällt mir spontan nichts effizienteres ein 🙂
 
Update:

Ich habe es hinbekommen, dass die Laufzeit schnell ist (Einfach auf ArrayList verzichtet und only Arrays verwendet 🙂 )

Das Problem ist nun, das die Costen des Spanning Trees (also alle Weg Gewichte addiert) nicht korrekt berechnet werden...Ich habe alles überprüft:

Bin mir ziemlich sicher, dass die Union Find Datenstruktur richtig ist, der Kruskal Algorithmus ist exakt so vom Wikipedia Pseudo Code implementiert (außer das der Vergleich es die gleiche Submenge ist, das passiert intern in union) und sonst wüsste ich nicht woran es liegen kann.

Hier sind die Dateien:

Java:
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;

public class Kruskal {

    private Graph graph;
    private double timeForTest = 0.0;

    public long runOp() {
        GraphArc [] arcs = new GraphArc[graph.numberOfArcs];
        arcs = graph.getArray();

        UnionFind s = new UnionFind(graph.numberOfNodes);

        Arrays.sort(arcs);

        long wayCost = 0;

        for(int i = 0; i < arcs.length; i++) {

            GraphArc current = arcs[i];
           
            if(s.union(current.startNode, current.endNode)) {
                wayCost += current.weight;
            }
        }
        return wayCost;
    }

    public void readGraph(String filename) {
        try {
            BufferedReader in = new BufferedReader(new FileReader(filename));
            String line = "";
            String[] parts = null;

            while((line = in.readLine()) != null) {
                //System.out.println("Current Line: " + line);
                if(line.charAt(0) == 'c') {
                }else if(line.charAt(0) == 'p') {
                    parts = line.split(" ");
                    graph = new Graph(convertInt(parts[2]), convertInt(parts[3]));
                }else if(line.charAt(0) == 'a') {
                    parts = line.split(" ");
                    //System.out.println(Arrays.toString(parts));
                    int start = convertInt(parts[1]);
                    int end = convertInt(parts[2]);
                    int weightNode = convertInt(parts[3]);
                    graph.add(start, end, weightNode);
                    //System.out.println(graph.toString());
                }else {
                    System.out.println("This Character is not valid!!!");
                }
            }
            //System.out.println(graph.toString());
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    private int convertInt(String input) {
        return Integer.parseInt(input);
    }

}
Java:
public class UnionFind {
   
    private int[] parents;
    private int[] rank;
   
    public UnionFind(int numberOfNodes) {
        parents = new int[numberOfNodes+1];
        rank = new int[numberOfNodes+1];
       
        this.makeSet();
    }
   
    private void makeSet() {
        for(int i = 0; i < parents.length; i++) {
            parents[i] = i;
            rank[i] = 0;
        }
    }
   
    public boolean union(int value1, int value2) {
        int temp1 = find(value1);
        int temp2 = find(value2);
       
        if(temp1 != temp2) {
            link(temp1, temp2);
            return true;
        }
        return false;
    }
   
    private void link(int value1, int value2) {

        if (rank[value1] < rank[value2])
            parents[value1] = value2;
        else if (rank[value2] < rank[value1])
            parents[value2] = value1;
        else {
            parents[value1] = value2;
            rank[value2] += 1;
        }
    }
   
    private int find(int value) {
        return parents[value];
    }
}
Java:
import java.util.ArrayList;

public class Graph {
   
    private ArrayList<ArrayList<GraphArc>> adjazenzlist;
    private GraphArc[] arcs;
    private int counter;
    public int numberOfNodes;
    public int numberOfArcs;

    public Graph(int numberOfNodes, int numberOfPaths) {
        adjazenzlist = new ArrayList<ArrayList<GraphArc>>();
        arcs = new GraphArc[numberOfPaths];
        counter = 0;
        this.numberOfNodes = numberOfNodes;
        this.numberOfArcs = numberOfPaths;
       
        for(int i = 0; i < numberOfNodes+1; i++) {
            adjazenzlist.add(new ArrayList<GraphArc>());
        }
    }
   
    public void add(int startNode, int endNode, int weight) {
        adjazenzlist.get(startNode).add(new GraphArc(startNode, endNode, weight));
        arcs[counter] = new GraphArc(startNode, endNode, weight);
        counter++;
    }
   
    public GraphArc[] getArray() {
        return arcs;
    }
   
    public String toString() {
        StringBuilder sb = new StringBuilder();
        for(int i = 0; i < adjazenzlist.size(); i++) {
            if(adjazenzlist.get(i).size() == 0) continue;
            sb.append(i + ": ");
            for(int j = 0; j < adjazenzlist.get(i).size(); j++) {
                GraphArc s = adjazenzlist.get(i).get(j);
                sb.append("(" + i + ", " + s.endNode + ", " + s.weight + ")");
                if(j != adjazenzlist.get(i).size() - 1) {
                    sb.append(", ");
                }
            }
            sb.append("\n");
        }
        return sb.toString();
    }
   
}
Java:
public class GraphArc implements Comparable<GraphArc>{
   
    public int startNode;
    public int endNode;
    public int weight;
   
    public GraphArc(int startNode, int endNode, int weight) {
        this.startNode = startNode;
        this.endNode = endNode;
        this.weight = weight;
    }

    @Override
    public int compareTo(GraphArc o) {
       
        if(o.weight > this.weight) return -1;
        else if(o.weight == this.weight) return 0;
        else return 1;
    }
   
    public String toString() {
        return "(" + this.startNode + ", " + this.endNode + ", " + this.weight + ")";
    }
}

Hier ist mein geschriebener JUnit Test:
Java:
import static org.junit.jupiter.api.Assertions.assertEquals;

import org.junit.jupiter.api.Test;

public class KruskalTest {
   
    @Test
    public void kruskalLastTest() {
       
        Kruskal s = new Kruskal();
        String directory = "/Users/userNameHere/Desktop/instances/";
       
        s.readGraph(directory + "USA-road-d.NY.gr");
        long result = s.runOp();
        assertEquals(261159288, result, "Got " + result + " but expected 261159288!");
        System.out.println("OK!");
       
        s.readGraph(directory + "USA-road-d.BAY.gr");
        result = s.runOp();
        assertEquals(435798417, result, "Got " + result + " but expected 435798417!");
        System.out.println("OK!");
       
        s.readGraph(directory + "USA-road-d.COL.gr");
        result = s.runOp();
        assertEquals(1323900090, result, "Got " + result + " but expected 1323900090!");
        System.out.println("OK!");
       
        s.readGraph(directory + "USA-road-d.FLA.gr");
        result = s.runOp();
        assertEquals(1806814846, result, "Got " + result + " but expected 1806814846!");
        System.out.println("OK!");
       
        s.readGraph(directory + "USA-road-d.NW.gr");
        result = s.runOp();
        assertEquals(3522586810L, result, "Got " + result + " but expected 3522586810!");
        System.out.println("OK!");
       
        s.readGraph(directory + "USA-road-d.NE.gr");
        result = s.runOp();
        assertEquals(2131122758L, result, "Got " + result + " but expected 2131122758!");
        System.out.println("OK!");
       
    }

}

Hier sind die Test Instanzen:
 
Aus dem Bauch heraus - ohne genauer reinzuschauen - sieht mir das link nicht korrekt aus.

Sobald die Vereinigung von zwei Mengen aus mehr als zwei Elementen steht, müsste eigentlich der parent von allen Element aktualisiert werden. Und der Rank ist komplett überflüssig in der Implementierung, der macht aktuell einfach gar nichts.

Mach mal einen Test auf die Union find Struktur mit 4 Elementen und mach dann ein union(1,2) und (2,3) und dann noch mal ein Union(1,3) und Union(1,2) und Union(2,3), die sollten ja alle false liefern. Ich wette einer der drei Aufrufe liefert true.
 
Yep du hattest recht, bei insgesamt 4 Elementen funktioniert alles richtig, aber ab 8 Elementen nicht mehr, da sind unions true obwohl diese Mengen bereits gemerged wurden 🙂 Aber wie fixe ich das....xD Soll ich bei jedem link() dann über das Array iterieren und alle mit diesem Parent gegen den neuen replacen? Dann hätte die Methode ja eine Laufzeit von O(n) oder ist das nicht so schlimm?
 
Also der JUnit Test sagt schonmal dass es das richtige Ergebnis liefert 🙂

Habe die Methode so:

Java:
private void link(int value1, int value2) {
        int searchValue = 0;
        int replaceValue = value2;
        
        if (rank[value1] < rank[value2]) {
            searchValue = value1;
            replaceValue = value2;
            parents[value1] = value2;
        }else if (rank[value2] < rank[value1]) {
            searchValue = value2;
            replaceValue = value1;
        }else {
            searchValue = value1;
            replaceValue = value2;
        }
        
        for(int i = 0; i < parents.length; i++) {
                if(parents[i] == searchValue) {
                    parents[i] = replaceValue;
                    rank[replaceValue]++;
                }
         }
    }

Aber wie die schon gesagt hast müsste ich jetzt schauen wie ich das am besten implementiere 🙂 Danke für deine Hilfe 🙂 Jetzt weiß ich zumindest wo ich ansetzen muss 🙂
 
Habe es gelöst 🙂 Habe im Skript gelesen, dass die Find Methode beim suchen automatisch eine Pfadkompression durchführen soll und da ja Array Zugriffe in O(1) passieren ist das tausend mal schneller als in O(n) über das Array zu iterieren 🙂

Mal ne Off-Topic Frage:

Werden Union-Find Datenstrukturen auch in Navigationssystemen verwendet oder funktionieren die komplett anders?
 
Ich wüsste nicht, was die da bringen, weil ein Algorithmus wie A* oder Dijsktra braucht die, soweit ich gerade weiß, nicht. Das ist ja eine Mengenbasierte Datenstruktur - bei Navigation interessieren mich Pfade und keine Mengen.
 
Ich wüsste nicht, was die da bringen, weil ein Algorithmus wie A* oder Dijsktra braucht die, soweit ich gerade weiß, nicht. Das ist ja eine Mengenbasierte Datenstruktur - bei Navigation interessieren mich Pfade und keine Mengen.
Ach stimmt xD Da habe ich mich verwirrt xD Ich meinte etwas anderes xD Mir hat mal jemand erzählt das wohl in Netwerken wenn man nicht nur static routing sondern auch einen DHCP Part hat, das irgendwie da Minimum Spanning Tree verwendet wird oder so xD Oder hat ich das auch falsch im Gedächtnis?😵
 
Da klingelt bei mir auch was, google sagt: https://de.wikipedia.org/wiki/Spanning_Tree_Protocol

Damit hört mein Wissen da aber auch schon auf. Ob da wirklich diese Datenstrukturen verwendet werden, keine Ahnung. Sehe ich aber gefühlt skeptisch, da:
* Die Graph-Größe vermutlich relativ klein sein sollte
* Da auch alternative Wege und Co betrachtet werden müssen in den Protokollen, eine reine Union-Find Struktur vermutlich nicht reicht

Aber das Teilaspekte davon in Implementierungen verwendet werden, halte ich nicht für ausgeschlossen.
 

Neue Themen


Zurück
Oben