Backtracking ist eine mit der Rekursion verwandte Problemlösungsstrategie.

Beim Backtracking probiert man jede mögliche Lösung aus, überprüft allerdings mit jedem Schritt die Abbruchbedingungen des Problems. Ist eine der Abbruchbedingungen gegeben, wird dieser “Pfad” nicht weiter verfolgt. Dabei ist der Algorithmus immer sehr ähnlich aufgebaut:

Pseudocode

backtrack()
  WENN Problem gelöst
    GIB Lösung ZURÜCK
  
  Finde Element im Ursprungszustand
  DURCHLAUFE jeden Zustand
    WENN Zustand zulässig
      Setze Zustand um

      WENN backtrack() Lösung findet
        GIB Lösung ZURÜCK
      
      Mache Zustand Rückgängig
  
  GIB keine Lösung ZURÜCK

Für jedes Element des Problems (Spalten im Damenproblem, Länder im Vierfarbenproblem…) werden alle möglichen Zustände ausprobiert (Z. 5f). Ist ein Zustand zulässig (Z. 7), so wird diese Möglichkeit umgesetzt (Z. 8) und rekursiv überprüft, ob es eine Lösung für den aktuellen Zustand gibt (Z. 10). Gibt es diesen, wird er zurückgegeben (Z. 11). Gibt es diesen nicht, wird der vorherige Schritt rückgängig gemacht (Z. 13) und der nächste Zustand überprüft. Sollten alle möglichen Zustände überprüft worden sein, ohne dass eine Lösung gefunden wurde, dann gibt es für den aktuellen Zustand keine Lösung und das wird dementsprechend zurückgegeben (Z. 15).

Die Rekursionsbasis stellt der Fall ein, dass das Problem gelöst ist: Dann wird einfach die Lösung zurückgegeben (Z. 2f).

Bedingungen an das Problem

Damit ein Problem effizient per Backtracking gelöst werden kann, muss es folgende Eigenschaften besitzen:

  1. Das Problem ist in endlich vielen Teilschritten lösbar
  2. Jeder der Teilschritte besitzt Abbruchbedingungen
  3. Jeder der Schritte hat eine endliche Anzahl Lösungsmöglichkeiten

Ein Problem muss nicht zwingend eine Lösung besitzen, um effizient mit Backtracking gelöst zu werden. Ein unlösbares Problems wird als solches erkannt.

Am Beispiel des Vierfarbenproblems

Beim Vierfarbenproblem geht es darum, eine Karte mit nn Ländern unter Verwendung von kk Farben einzufärben.

Dabei müssen für jedes Feld zwei Bedingungen erfüllt sein:

Das neu gefärbte Land grenzt an kein bereits früher gefärbtes Land mit der gleichen Farbe.

Beispiel Bedingung 1

Durch die Färbung des neuen Landes wird kein angrenzendes, noch nicht gefärbtes Land unfärbbar.

Beispiel Bedingung 2

Die Bilder zeigen jeweils Fälle, in denen die Bedingungen nicht erfüllt sind.

Die Zweite Bedingung ist nicht zwingend notwendig, macht die Problemlösung allerdings deutlich einfacher.

Pseudocode

Zu Demonstrationszwecken ist der folgende Code auf k=4k = 4 Felder ausgelegt. Wir stellen erstmal den Pseudocode für unsere beiden Bedingungen auf:

bedingung1(i, farbe)
  FÜR alle angrenzenden
    WENN Farbe des angrenzenden gleich farbe
      GIB false ZURÜCK
  GIB true ZURÜCK

Um herauszufinden, ob man eine Farbe setzen kann, überprüft man jedes der angrenzenden Länder.

bedingung2(i)
  ERZEUGE Liste
  FÜR alle angrenzenden
    HÄNGE Farbe AN Liste AN
  
    FALLS Liste enthält Blau UND
        Liste enthält Gelb UND
        Liste enthält Grün UND
        Liste enthält Rot
      GIB false ZURÜCK
    SONST
      GIBT true ZURÜCK

Wir tragen die Farbe aller umliegenden Felder in eine Liste ein. Wenn diese Liste alle kk Farben enthält (im Beispiel Blau, Gelb, Rot und Grün), ist die Bedingung nicht erfüllt.

Um nun das Problem zu lösen, benutzen wir folgenden Algorithmus:

vierFarbenProblem()
  FALLS alle Felder eingefärbt sind
    GIB Farben der Felder ZURÜCK

  finde das erste Land ohne Farbe
  DURCHLAUFE ALLE farben
    FALLS
      bedingung1(land, farbe) UND
      bedingung2(land)
    DANN
      SETZE farbe
      FALLS vierFarbenProblem() eine Lösung zurückgibt
        GIB die Lösung zurück
      SETZE farbe zurück
  GIB keine Lösung zurück

In Zeile 2f. ist die erste Rekursionsbasis: Sie überprüft, ob das Problem gelöst wurde. Wenn es gelöst wurde, wird die Lösung zurückgegeben.

Greift die Rekursionsbasis nicht, gehen wir in den Schritt über:
Wir finden ein Land ohne Farbe (Z. 5) und iterieren über alle Lösungsmöglichkeiten (in unserem Fall kk Farben FF). Falls diese Farbe FF beiden Bedingungen genügt, wird sie “ausprobiert”:
Die Farbe wird erst gesetzt (Z. 11).
Dann wird überprüft, ob es für diese Konstellation eine Lösung gibt (Z. 12).
Gibt es diese, wird sie zurückgegeben (Z. 13), sonst wird das Setzen der Farbe wieder rückgängig gemacht (Z. 14) und in der nächsten Iteration die nächste Farbe ausprobiert.
Falls über alle Farben iteriert wurde, ohne eine Lösung zu finden, gibt es für diesen Zustand keine Lösung und “keine Lösung” wird zurückgegeben (Z. 15).

Demonstration

In diesem Beispiel stellt jeder Knoten ein Land dar, jede Kante die gemeinsame Grenze mit einem anderen. Mit den Preset-Tasten wählt man eine Voreinstellung an, mit dem Backtrack-Taster startet man den Algorithmus. Bleiben die Felder Grau, ist keine Lösung möglich.

Das ganze ist mithilfe der Visualisierungs-Biblothek Vis.js entstanden, die zentrale Methode sieht so aus:

const backtrack = () => {
  // Basis 1: Lösung gefunden
  if (allColored()) return data;

  const betrachtet = getFirstUncolored();
  for (let color of FARBEN) {
    if (
      bedingung1(
        betrachtet,
        color
      ) &&
      bedingung2(betrachtet)
    ) {
      setColor(betrachtet, color);

      const solution = backtrack();
      if (solution !== null) return solution;

      setColor(betrachtet, FARBLOS);
    }
  }

  return null
};

Präsentation

Das Vierfarbenproblem als Präsentation

Am Beispiel von Sudoku

Sudoku ist ein beliebtes Zahlenrätsel, bei dem es um das ausfüllen eines Zahlenfeldes nach bestimmten Regeln geht.

Eine Matrix MM der Dimension 9×99 \times 9 (selten andere Größen) besteht aus 3×33 \times 3 Submatrizen QQ der Dimension 3×33 \times 3. Jede Komponente sei eine natürliche Zahl 0x100 \geq x \geq 10, sodass xx in jeder Spalte, Zeile und Submatriz einzigartig ist.

Dabei sind zu Beginn schon einige Zahlen eingetragen.

Beispiel

Zur Lösung eines Sudokus kann man sehr gut Backtracking verwenden. Dabei sind die Komponenten unsere Elemente und die Zahlen [0;9][0;9] die möglichen Zustände dieser.

Pseudocode

WENN Alle Komponenten befüllt
    GIB Feld ZURÜCK
  
  Finde leere Komponente
  DURCHLAUFE 1..9
    WENN Zahl zulässig
      Setze Zahl

      WENN backtrack() Lösung findet
        GIB Lösung ZURÜCK
      
      Entferne Zahl
  
  GIB keine Lösung ZURÜCK