Breitensuche in Java

Gepostet: , Zuletzt aktualisiert:

package bfs.java.simple;

import java.util.Arrays;
import java.util.PriorityQueue;
import java.util.Queue;

/**
 * Used to perform Breadth-First-Search (BFS) using adjacency matrices.
 * For a faster implementation, see @see ../fast/BFS.java (using adjacency Lists)
 */
public class BFS {
    /**
     * Implementation of Breadth-First-Search using adjacency matrix.
     * This returns nothing (yet), it is meant to be a template for whatever you want to do with it,
     * e.g. finding the shortest path in a unweighted graph.
     * This has a runtime of O(|V|^2) (|V| = number of Nodes), for a faster implementation see @see ../fast/BFS.java (using adjacency Lists)
     *
     * @param graph an adjacency-matrix-representation of the graph where (x,y) is true if the the there is an edge between nodes x and y.
     * @param start the node to start from.
     * @return an array containing the shortest distances from the given start node to each other node
     */
    public static int[] bfs(boolean[][] graph, int start) {
        //A Queue to manage the nodes that have yet to be visited
        Queue<Integer> queue = new PriorityQueue<>();
        //Adding the node to start from
        queue.add(start);
        //A boolean array indicating whether we have already visited a node
        boolean[] visited = new boolean[graph.length];
        //(The start node is already visited)
        visited[start] = true;
        // Keeping the distances (might not be necessary depending on your use case)
        int[] distances = new int[graph.length]; // No need to set initial values since every node is visted exactly once
        //While there are nodes left to visit...
        while (!queue.isEmpty()) {
            System.out.println("Visited nodes: " + Arrays.toString(visited));
            System.out.println("Distances: " + Arrays.toString(distances));
            int node = queue.remove();
            System.out.println("Removing node " + node + " from the queue...");
            //...for all neighboring nodes that haven't been visited yet....
            for (int i = 1; i < graph[node].length; i++) {
                if (graph[node][i] && !visited[i]) {
                    // Do whatever you want to do with the node here.
                    // Visit it, set the distance and add it to the queue
                    visited[i] = true;
                    distances[i] = distances[node] + 1;
                    queue.add(i);
                    System.out.println("Visiting node " + i + ", setting its distance to " + distances[i] + " and adding it to the queue");

                }
            }
        }
        System.out.println("No more nodes in the queue. Distances: " + Arrays.toString(distances));
        return distances;
    }
}

Über den Algorithmus und die Programmiersprache in diesem Snippet:

Breitensuche Algorithmus

Der Breitensuchalgorithmus (Breadth-first-search, BFS) ist ein Algorithmus, der verwendet wird, um das Problem des kürzesten Pfades in einem Graphen ohne Kantengewichte zu lösen (d.h. ein Diagramm, in dem alle Knoten den gleichen “Abstand” voneinander haben und entweder verbunden sind oder nicht). Dies bedeutet, dass bei einer Anzahl von Knoten und den Kanten zwischen ihnen der Breitensuchalgorithmus den kürzesten Weg vom angegebenen Startknoten zu allen anderen Knoten findet.

Beschreibung des Algorithmus

Das Grundprinzip des Breitensuchalgorithmus besteht darin, den aktuellen Knoten (den Startknoten am Anfang) zu nehmen und dann alle seine Nachbarn, die wir noch nicht besucht haben, zu einer Warteschlange hinzuzufügen. Fahren Sie mit dem nächsten Knoten in der Warteschlange fort (in einer Warteschlange, die der “älteste” Knoten ist). Bevor wir der Warteschlange einen Knoten hinzufügen, setzen wir seinen Abstand auf den Abstand des aktuellen Knotens plus 1 (da alle Kanten gleich gewichtet sind), wobei der Abstand zum Startknoten 0 ist. Dies wird wiederholt, bis sich keine Knoten mehr in der Warteschlange befinden (alle Knoten werden besucht).

Im Einzelnen führt dies zu den folgenden Schritten:

  1. Initialisieren Sie die Entfernung zum Startknoten als 0. Die Entfernungen zu allen anderen Knoten müssen nicht initialisiert werden, da jeder Knoten genau einmal besucht wird.
  2. Setzen Sie alle Knoten auf “nicht besucht”.
  3. Fügen Sie den ersten Knoten zur Warteschlange hinzu und kennzeichnen Sie ihn als besucht.
  4. Während sich Knoten in der Warteschlange befinden:

    1. Nehmen Sie einen Knoten aus der Warteschlange
    2. Fügen Sie für alle Knoten daneben, die wir noch nicht besucht haben, sie der Warteschlange hinzu, stellen Sie ihre Entfernung auf die Entfernung zum aktuellen Knoten plus 1 ein und legen Sie sie als “besucht” fest.

Am Ende sind die Abstände zu allen Knoten korrekt.

Beispiel des Algorithmus

Betrachten Sie das folgende Diagramm:

Grafik für die erste Breitensuche

Die Schritte, die der Algorithmus in diesem Diagramm ausführt, wenn der Knoten 0 als Startpunkt angegeben wird, sind:

  1. Besuche Knoten 0
  2. Besuchte Knoten: [wahr, falsch, falsch, falsch, falsch, falsch], Entfernungen: [0, 0, 0, 0, 0, 0]

    1. Knoten 0 aus der Warteschlange entfernen …
    2. Besuchen Sie Knoten 1, setzen Sie seinen Abstand auf 1 und fügen Sie ihn der Warteschlange hinzu
    3. Besuchen Sie Knoten 2, setzen Sie seinen Abstand auf 1 und fügen Sie ihn der Warteschlange hinzu
  3. Besuchte Knoten: [wahr, wahr, wahr, falsch, falsch, falsch], Entfernungen: [0, 1, 1, 0, 0, 0]

    1. Entfernen von Knoten 1 aus der Warteschlange …
    2. Besuchen Sie Knoten 3, setzen Sie seinen Abstand auf 2 und fügen Sie ihn der Warteschlange hinzu
    3. Besuchen Sie Knoten 4, stellen Sie seinen Abstand auf 2 ein und fügen Sie ihn der Warteschlange hinzu
  4. Besuchte Knoten: [wahr, wahr, wahr, wahr, wahr, falsch], Entfernungen: [0, 1, 1, 2, 2, 0]

    1. Entfernen von Knoten 2 aus der Warteschlange …
    2. Keine benachbarten, nicht besuchten Knoten
  5. Besuchte Knoten: [wahr, wahr, wahr, wahr, wahr, falsch], Entfernungen: [0, 1, 1, 2, 2, 0]

    1. Entfernen von Knoten 3 aus der Warteschlange …
    2. Besuchen Sie Knoten 5, stellen Sie seinen Abstand auf 3 ein und fügen Sie ihn der Warteschlange hinzu
  6. Besuchte Knoten: [wahr, wahr, wahr, wahr, wahr, wahr], Entfernungen: [0, 1, 1, 2, 2, 3]

    1. Entfernen von Knoten 4 aus der Warteschlange …
    2. Keine benachbarten, nicht besuchten Knoten
  7. Besuchte Knoten: [wahr, wahr, wahr, wahr, wahr, wahr], Entfernungen: [0, 1, 1, 2, 2, 3]

    1. Entfernen von Knoten 5 aus der Warteschlange …
  8. Keine Knoten mehr in der Warteschlange. Endabstände: [0, 1, 1, 2, 2, 3]

Laufzeitkomplexität des Algorithmus

Die Laufzeitkomplexität der Breitensuche beträgt O(|E| + |V|) (|V| = Anzahl der Knoten, |E| = Anzahl der Kanten), wenn Adjazenzlisten verwendet werden. Wenn wir einfach alle Knoten durchsuchen, um in jedem Schritt verbundene Knoten zu finden, und mithilfe einer Matrix nachsehen, ob zwei Knoten benachbart sind, steigt die Laufzeitkomplexität auf O(|V|^2).

Abhängig vom Diagramm spielt dies möglicherweise keine Rolle, da die Anzahl der Kanten bis zu |V|^2 betragen kann, wenn alle Knoten miteinander verbunden sind.

Speicherplatzkomplexität des Algorithmus

Die Speicherplatzkomplexität der Breitensuche hängt auch davon ab, wie sie implementiert ist, und entspricht der Laufzeitkomplexität.

Java

The Java Logo

Java ™ ist eine kompilierte Sprache, die für viele Zwecke verwendet wird, von eingebetteten Systemen über UI-Anwendungen bis hin zu Webservern.

“Hello World” in Java

Das Wichtigste zuerst - hier erfahren Sie, wie Sie Ihre erste Codezeile in Java ausführen können.

  1. Laden Sie die neueste Version von Java von [java.com] herunter und installieren Sie sie (https://www.java.com/en/download/). Sie können auch eine frühere Version herunterladen, wenn Ihr Anwendungsfall dies erfordert.
  2. Öffnen Sie ein Terminal, stellen Sie sicher, dass die Befehle “javac” und “javac” funktionieren und dass sich der Befehl, den Sie verwenden werden, auf die Version bezieht, die Sie gerade installiert haben, indem Sie “java -version” ausführen. Wenn der Fehler “Befehl nicht gefunden” (oder ähnlich) angezeigt wird, starten Sie die Befehlszeile und, falls dies nicht hilft, Ihren Computer neu. Wenn das Problem weiterhin besteht, finden Sie hier einige hilfreiche Fragen zu StackOverflow für jede Plattform:

  3. Sobald dies funktioniert, kopieren Sie das folgende Snippet in eine Datei mit dem Namen HelloWorld.java:

    class HelloWorld {
    public static void main(String[] args) {
        // Paste any following code snippets here.
        System.out.println("Hello World");
    }
    }
  4. Ändern Sie das Verzeichnis, indem Sie “cd path / to / HelloWorld” eingeben, dann “javac HelloWorld.java” ausführen, um die Datei zu kompilieren (die den Bytecode erstellt), und dann “java HelloWorld” ausführen (* ohne die .java-Endung *). Dies sollte “Hello World” auf Ihrem Terminal drucken.

Das ist es! Beachten Sie, dass die Eintrittsbarriere bei Java etwas höher ist als bei z. Python - aber Java ist viel schneller und weist meiner Erfahrung nach aufgrund starker Typisierung und anderer Faktoren weniger Fehler in großen Projekten auf.

Fundamentals in Java

Um in Java implementierte Algorithmen und Technologien zu verstehen, muss man zunächst verstehen, wie grundlegende Programmierkonzepte in dieser bestimmten Sprache aussehen. Jedes der folgenden Snippets sollte vom Boilerplate-Code des Hello World-Beispiels umgeben sein und mit den oben genannten Befehlen kompiliert und ausgeführt werden.

Variables and Arithmetic

Variablen in Java sind statisch typisiert, dh der Inhalt einer Variablen muss beim Schreiben des Codes angegeben werden. Der Datentyp für ganze Zahlen ist beispielsweise “int”. Zahlen mit Dezimalstellen werden je nach erforderlicher Genauigkeit mit “float” oder “double” eingegeben. Der Texttyp lautet “String”.

int number = 5;
double decimalNumber = 3.25;
double result = number * decimalNumber;
String callout = "The number is ";
// In this instance, the values are concatenated rather than added because one of them is a String.
System.out.println(callout + result);

Arrays

Arrays in Java sind echte Arrays (im Gegensatz zu beispielsweise Python, wo sie als Listen implementiert sind). Dies hat zur Folge, dass die Größe beim Erstellen festgelegt werden muss und nicht geändert werden kann, aber auch, dass sie in Java effizienter sind als in Python.

int[] integers = new int[5];
integers[3] = 12; // Assigning values to positions in the array
// integers[4] is 0, integers[6] would give IndexOutOfBoundsException
String[] strings = {"Hello", "World"}; // Array initialization with initial values
System.out.println(strings[0] + integers[3]); // Prints "Hello12"

Conditions

Wie die meisten Programmiersprachen kann Java “if-else” -Anweisungen ausführen. Darüber hinaus kann Java auch “switch-case” -Anweisungen ausführen.

int value = 5;
if(value == 5){
    System.out.println("Value is 5");
} else if(value < 5){
    System.out.println("Value is less than 5");
} else {
    System.out.println("Value is something else");
}

switch (value){
    case 1:
        System.out.println("Value is 1");
        break; // Don't go further down the cases
    case 2:
        System.out.println("Value is 2");
        break; // Don't go further down the cases
    case 3:
        System.out.println("Value is 3");
        break; // Don't go further down the cases
    case 4:
        System.out.println("Value is 4");
        break; // Don't go further down the cases
    case 5:
        System.out.println("Value is 5");
        break; // Don't go further down the cases
    default:
        System.out.println("Value is something else");
}

Der obige Java-Code gibt zweimal “Wert ist 5” aus.

Schleifen

Java unterstützt “for” -, “while” - und “do while” -Schleifen. Die Anweisungen break undcontinue werden ebenfalls unterstützt. Das folgende Beispiel zeigt die Unterschiede:

int value = 2;
for (int i = 0; i < value; i++) {
    System.out.println(i);
}
while (value > 0) {
    System.out.println(value);
    value--;
}
do {
    System.out.println(value);
    value--;
} while (value > 0);

Dadurch wird Folgendes auf das Terminal gedruckt:

0
1
2
1
0

Beachten Sie die letzte “0”: Sie wird gedruckt, weil in der “do-while” -Schleife im Vergleich zur “while” -Schleife. Der Codeblock wird mindestens einmal ausgeführt, bevor die Bedingung überprüft wird.

Funktionen

Funktionen in Java können Teil einer Klasse oder eines Objekts einer Klasse sein. Für weitere Informationen zur objektorientierten Programmierung empfehle ich den w3schools-Kurs. Hier ist ein minimales Beispiel für eine Funktion als Teil einer Klasse (auch als statische Funktion bezeichnet):

class HelloWorld {
    public static void main(String[] args) {
        System.out.println(addNumbers(3, 4));
    }

    public static int addNumbers(int numberOne, int numberTwo) {
        return numberOne + numberTwo;
    }
}

Und hier ist ein Beispiel für den Aufruf einer Funktion eines Objekts einer Klasse:

class HelloWorld {
    public static void main(String[] args) {
        System.out.println(new HelloWorld().addNumbers(3, 4));
    }

    public int addNumbers(int numberOne, int numberTwo) {
        return numberOne + numberTwo;
    }
}

Beachten Sie, wie im ersten Beispiel das Schlüsselwort static verwendet wird und im zweiten Beispiel das Objekt der Klasse instanziiert werden muss, bevor in die Funktion dieses Objekts aufrufen kann. Dies sind einige der Unterschiede bei Klassenmethoden und Objektfunktionen.

Syntax

Java erfordert die Verwendung von geschweiften Klammern ({}), um Codeblöcke in Bedingungen, Schleifen, Funktionen usw.; Außerdem sind am Ende der Anweisungen Semikolons erforderlich. Dies kann zwar zu lästigen Syntaxfehlern führen, bedeutet jedoch auch, dass die Verwendung von Leerzeichen für die bevorzugte Formatierung (z. B. Einrücken von Codeteilen) den Code nicht beeinflusst.

Fortgeschrittenes Wissen in Java

Java wurde erstmals 1995 veröffentlicht und ist ein Multi-Paradigma. Das heißt, es ist in erster Linie objektorientiert, hat aber auch funktionale und reflektierende Elemente. Es ist statisch typisiert, bietet jedoch in neueren Versionen ein gewisses Maß an dynamischer Typisierung. Für weitere Informationen hat Java einen großartigen Artikel Wikipedia.