Wie finde ich alle gleichwertigen Pfade im entarteten Baum, die auf einem bestimmten Knoten beginnen?

9

Ich habe degenerate tree (sieht wie ein Array oder eine doppelt verkettete Liste aus). Zum Beispiel ist es dieser Baum:

Jede Kante hat ein gewisses Gewicht. Ich möchte alle gleichen Pfade finden, die in jedem Eckpunkt beginnen.

Mit anderen Worten, ich möchte alle Tupel (v1, v, v2) erhalten, wobei v1 und v2 ein beliebiger Vor- und Nachfahren sind, so dass c(v1, v) = c(v, v2) .

Lassen Sie die Kanten die folgenden Gewichte haben (es ist nur ein Beispiel):

a-b = 3

b-c = 1

c-d = 1

d-e = 1

Dann:

  1. Der Vertex A hat keinen gleichen Pfad (es gibt keinen Vertex von der linken Seite).
  2. Der Vertex B hat ein gleiches Paar. Der Pfad B-A entspricht dem Pfad B-E (3 == 3) .
  3. Der Vertex C hat ein gleiches Paar. Der Pfad B-C entspricht dem Pfad C-D (1 == 1) .
  4. Der Vertex D hat ein gleiches Paar. Der Pfad C-D entspricht dem Pfad D-E (1 == 1) .
  5. Der Vertex E hat keinen gleichen Pfad (es gibt keinen Vertex von der rechten Seite).

Ich implementiere einen einfachen Algorithmus, der in O(n^2) funktioniert. Aber es ist zu langsam für mich.

    
David 15.05.2015, 22:39
quelle

1 Antwort

4

Sie schreiben in Kommentaren, dass Ihr aktueller Ansatz

ist
  

Es scheint, ich suche nach einer Möglichkeit, die Konstante in O (n ^ 2) zu verringern. ich wähle   irgendein Eckpunkt. Dann erstelle ich zwei Sets. Dann fülle ich diese Sets mit   Partialsummen, während von diesem Eckpunkt zum Start von Baum und zu iterieren   Ende des Baumes. Dann finde ich einen Schnittpunkt und erhalte die Anzahl der Pfade   von diesem Eckpunkt. Dann wiederhole ich den Algorithmus für alle anderen Knoten.

Es gibt einen einfacheren und, ich denke, schnelleren Ansatz von O(n^2) , basierend auf der sogenannten Zwei-Zeiger-Methode.

Für jedes vertix v gehen Sie gleichzeitig in zwei mögliche Richtungen. Bewegen Sie einen "Zeiger" zu einem Scheitelpunkt ( vl ) in eine Richtung und einen anderen ( vr ) in eine andere Richtung und versuchen Sie, den Abstand von v zu vl so nahe wie möglich von% co_de zu halten % bis v wie möglich. Jedes Mal, wenn diese Abstände gleich sind, haben Sie gleiche Pfade.

%Vor%

(Durch Vorberechnung der Präfixsummen finden Sie vr in O (1).)

Es ist leicht zu sehen, dass kein gleiches Paar verpasst wird, vorausgesetzt, dass Sie keine Kanten mit der Länge 0 haben.

In Bezug auf eine schnellere Lösung, wenn Sie alle Paare listen wollen, können Sie es nicht schneller machen, weil die Anzahl der Paare im schlimmsten Fall 0 (n ^ 2) ist. Aber wenn Sie nur die Menge dieser Paare benötigen, können schnellere Algorithmen existieren.

UPD : Ich habe einen anderen Algorithmus für die Berechnung der Menge entwickelt, der schneller sein könnte, wenn Ihre Kanten eher kurz sind. Wenn Sie die Gesamtlänge Ihrer Kette (Summe aller Kantengewichte) als dist angeben, wird der Algorithmus in L ausgeführt. Es ist jedoch konzeptionell viel fortschrittlicher und fortgeschrittener in der Codierung auch.

Zunächst einige theoretische Überlegungen. Betrachten Sie einen Vertex O(L log L) . Lassen Sie uns zwei Arrays, v und a , nicht die null-indizierten Arrays im C-Stil, sondern Arrays mit Indexierung von b bis -L .

Definieren wir

  • für L , i>0 wenn zu rechts von a[i]=1 für die Entfernung genau v dort ist ein Eckpunkt, sonst i
  • für a[i]=0 , i=0
  • für a[i]≡a[0]=1 , i<0 wenn zu links von a[i]=1 für die Entfernung genau v es gibt einen Eckpunkt, sonst -i

Ein einfaches Verständnis dieses Arrays ist wie folgt. Strecken Sie Ihren Graphen und legen Sie ihn entlang der Koordinatenachse, so dass jede Kante die Länge hat, die ihrem Gewicht entspricht, und dass der Scheitelpunkt a[i]=0 im Ursprung liegt. Dann v wenn es einen Knoten an der Koordinate a[i]=1 gibt.

Für Ihr Beispiel und für den Scheitelpunkt "b" gewählt als i :

%Vor%

Für ein anderes Array, Array v , definieren wir die Werte in einer symmetrischen Weise in Bezug auf den Ursprung, als ob wir die Richtung der Achse invertiert hätten:

  • für b , i>0 wenn bis links von b[i]=1 für die Entfernung genau v dort ist ein Eckpunkt, sonst i
  • für b[i]=0 , i=0
  • für b[i]≡b[0]=1 , i<0 wenn zu rechts von b[i]=1 für die Entfernung genau v es gibt einen Scheitelpunkt, sonst -i

Betrachten Sie nun ein drittes Array b[i]=0 , so dass c , Sternchen hier für normale Multiplikation bleibt. Offensichtlich c[i]=a[i]*b[i] , wenn der Pfad der Länge c[i]=1 auf der linken Seite in einem Scheitelpunkt endet und der Pfad der Länge abs(i) auf der rechten Seite in einem Scheitelpunkt endet. Für abs(i) entspricht jede Position in i>0 mit c dem Pfad, den Sie benötigen. Es gibt auch negative Positionen ( c[i]=1 mit c[i]=1 ), die nur die positiven Positionen widerspiegeln, und eine weitere Position mit i<0 , nämlich Position c[i]=1 .

Berechnen Sie die Summe aller Elemente in i=0 . Diese Summe ist c , wobei sum(c)=2P+1 die Gesamtzahl der Pfade ist, die Sie benötigen, wobei P der Mittelpunkt ist. Wenn Sie also v kennen, können Sie einfach sum(c) ermitteln.

Betrachten wir nun die Arrays P und a näher und wie verändern sie sich, wenn wir den Vertex b ändern. Lassen Sie uns v den linken Eckpunkt (das Wurzelverzeichnis Ihres Baumes) und v0 und a0 die entsprechenden b0 und a Arrays für diesen Eckpunkt angeben.

Für einen beliebigen Vertex b bedeutet v . Dann ist es leicht zu sehen, dass für den Vertex d=dist(v0,v) die Arrays v und a nur die Arrays b und a0 um b0 verschoben sind:

%Vor%

Es ist offensichtlich, wenn Sie sich an das Bild mit dem entlang einer Koordinatenachse gestreckten Baum erinnern.

Betrachten wir nun ein weiteres Array, d (ein Array für alle Scheitelpunkte), und für jeden Scheitelpunkt S geben wir den Wert von v in das Element sum(c) ( S[d] und d hängt von c ab.

Genauer gesagt, lassen Sie uns das Array v für jedes S

definieren %Vor%

Sobald wir das Array d kennen, können wir über Scheitelpunkte iterieren und für jeden Scheitelpunkt S sein v einfach als sum(c) mit S[d] erhalten, weil wir für jeden Scheitelpunkt d=dist(v,v0) % co_de haben %.

Aber die Formel für v ist sehr einfach: sum(c)=sum(a0[i+d]*b0[i-d]) ist nur die Faltung des S und S Sequenzen. (Die Formel folgt nicht genau der Definition, sondern lässt sich leicht in die genaue Definitionsform ändern.)

Was wir jetzt brauchen, ist a0 und b0 (was wir in a0 time und space berechnen können), berechnet das b0 Array. Danach können wir über O(L) array iterieren und einfach die Anzahl der Pfade aus S extrahieren.

Direkte Anwendung der obigen Formel ist S . Jedoch kann die Faltung von zwei Folgen in S[d]=2P+1 berechnet werden, indem der Fast-Fourier-Transformationsalgorithmus angewendet wird. Darüber hinaus können Sie eine ähnliche Zahlentheoretische Transformation anwenden (ich weiß nicht, ob es eine bessere Verknüpfung gibt), um mit Ganzzahlen zu arbeiten nur und vermeiden Sie Präzisionsprobleme.

Also wird der allgemeine Umriss des Algorithmus

%Vor%

(Ich nenne es O(L^2) , weil O(L log L) nicht genau eine Faltung ist, sondern ein sehr ähnliches Objekt, für das ein ähnlicher Algorithmus angewendet werden kann. Außerdem können Sie corrected_convolution und dann S definieren die richtige Faltung.)

    
Petr 19.05.2015, 20:46
quelle