Wenn Sie nur sehr wenige Zyklen haben, finden Sie hier einen Algorithmus, der weniger Speicherplatz benötigt, dessen Beendigung jedoch wesentlich länger dauert.
[Bearbeiten.] meiner vorherigen Laufzeitanalyse wurden die entscheidenden Kosten für die Feststellung, ob die von uns besuchten Knoten zu den zuvor untersuchten gehören, übersehen. Diese Antwort wurde etwas überarbeitet, um dies zu korrigieren.
Wir wieder durchlaufen alle Elemente von S . Während wir die Umlaufbahnen der Elemente s ∈ S untersuchen , nehmen wir eine Stichprobe von den Knoten, die wir besucht haben, um zu überprüfen, ob wir wieder auf sie stoßen. Wir führen auch eine Liste von Proben von "Komponenten" - Vereinigungen von Umlaufbahnen, die in einem gemeinsamen Zyklus enden (und daher den Zyklen entsprechen) -, die zuvor besucht wurden.
Initialisieren Sie eine leere Liste von Komponenten complist
. Jede Komponente wird durch eine Sammlung von Proben dieser Komponente dargestellt. Wir pflegen auch einen Suchbaum, in samples
dem alle Elemente gespeichert sind, die als Beispiele für die eine oder andere Komponente ausgewählt wurden. Sei G eine Folge von ganzen Zahlen bis zu n , für die die Zugehörigkeit durch Berechnung eines booleschen Prädikats effizient bestimmbar ist; Zum Beispiel Potenzen von 2 oder perfekte p- te Potenzen für eine ganze Zahl p . Gehen Sie für jedes s ∈ S wie folgt vor:
- Wenn s in ist
samples
aktiviert , fahren Sie mit Schritt 5 fort.
- Initialisieren Sie eine leere Liste
cursample
, einen Iterator j ← f ( s ) und einen Zähler t ← 1.
- Während j nicht in ist
samples
:
- Wenn t ∈ G ist , füge j in beide cursample
und ein samples
.
- Inkrementiere t und setze j ← f (j) .
- Überprüfen Sie, ob j in ist
cursample
. Wenn nicht, sind wir auf eine zuvor untersuchte Komponente gestoßen: Wir überprüfen, zu welcher Komponente j gehört, und fügen alle Elemente von cursample
in das entsprechende Element von ein complist
, um sie zu erweitern. Andernfalls sind wir erneut auf ein Element aus der aktuellen Umlaufbahn gestoßen, was bedeutet, dass wir mindestens einmal einen Zyklus durchlaufen haben, ohne auf Vertreter zuvor entdeckter Zyklen zu stoßen: Wir fügen cursample
als Sammlung von Proben einer neu gefundenen Komponente in eincomplist
.
- Fahren Sie mit dem nächsten Element s ∈ S fort .
Für n = | S |, sei X (n) eine monoton ansteigende Funktion, die die erwartete Anzahl von Zyklen beschreibt ( z. B. X (n) = n 1/3 ), und sei Y (n) = y (n) log ( n ) ∈ Ω ( X (n) log ( n )) ist eine monoton ansteigende Funktion, die ein Ziel für die Speichernutzung bestimmt ( z. B. y (n) = n 1/2 ). Wir benötigen y (n) ∈ Ω ( X (n) ), da mindestens X (n) log ( n ) Speicherplatz benötigt wird, um eine Probe von jeder Komponente zu speichern.
Je mehr Elemente einer Umlaufbahn wir abtasten, desto wahrscheinlicher ist es, dass wir am Ende einer Umlaufbahn schnell eine Probe im Zyklus auswählen und dadurch diesen Zyklus schnell erkennen. Aus asymptotischer Sicht ist es dann sinnvoll, so viele Abtastwerte zu erhalten, wie unsere Speichergrenzen zulassen: Wir können G genauso gut so einstellen , dass ein erwartetes y (n) -Element vorliegt, das kleiner als n ist .
- Wenn erwartet wird, dass die maximale Länge einer Umlaufbahn in S L ist , können wir G die ganzzahligen Vielfachen von L / y (n) sein lassen .
- Wenn es keine erwartete Länge gibt, können wir einfach alle n / y (n) einmal probieren.Elemente; Dies ist in jedem Fall eine Obergrenze für die Intervalle zwischen den Proben.
Wenn wir bei der Suche nach einer neuen Komponente beginnen, Elemente von S zu durchlaufen, die wir zuvor besucht haben (entweder von einer neuen Komponente, die entdeckt wurde, oder von einer alten, deren Endzyklus bereits gefunden wurde), dauert es höchstens n / y ( n) Iterationen, um auf ein zuvor abgetastetes Element zu stoßen; Dies ist dann eine Obergrenze für die Häufigkeit, mit der wir bei jedem Versuch, eine neue Komponente zu finden, redundante Knoten durchlaufen. Da wir n solche Versuche machen, werden wir dann Elemente von S insgesamt höchstens n 2 / y (n) Mal redundant besuchen .
Die zum Testen der Mitgliedschaft erforderliche Arbeit samples
ist O ( y (n) log y (n) ), die wir bei jedem Besuch wiederholen: Die kumulierten Kosten dieser Überprüfung betragen O ( n 2 log y (n) ). Es gibt auch die Kosten für das Hinzufügen der Proben zu ihren jeweiligen Sammlungen, die kumulativ O ( y (n) log y (n) ) sind. Schließlich müssen wir jedes Mal, wenn wir auf eine zuvor entdeckte Komponente stoßen, bis zu X (n) log * y (n) Zeit aufwenden, um zu bestimmen, welche Komponente wir wiederentdeckt haben. Da dies bis zu n Mal vorkommen kann, ist die kumulative Arbeit durch n X (n) log y (n) begrenzt .
Somit dominiert die kumulative Arbeit bei der Überprüfung, ob die von uns besuchten Knoten zu den Stichproben gehören, die Laufzeit: Dies kostet O ( n 2 log y (n) ). Dann sollten wir y (n) so klein wie möglich machen, dh O ( X (n)) ).
Somit kann man die Anzahl der Zyklen (die der Anzahl der Komponenten entspricht, die in diesen Zyklen enden) im Raum O ( X (n) log ( n )) aufzählen, wobei O ( n 2 log X (n) ) genommen wird. Zeit dafür, wobei X (n) die erwartete Anzahl von Zyklen ist.