Si vous avez très peu de cycles, voici un algorithme qui utilisera moins d'espace, mais prendra beaucoup plus de temps pour se terminer.
[Edit.] Mon analyse précédente de l'exécution a manqué le coût crucial de déterminer si les nœuds que nous visitons sont parmi ceux précédemment échantillonnés; cette réponse a été quelque peu révisée pour corriger cela.
Nous avons de nouveau itérer à travers tous les éléments de S . En explorant les orbites des éléments s ∈ S , nous échantillonnons à partir des nœuds que nous avons visités, afin de pouvoir vérifier si nous les rencontrons à nouveau. Nous tenons également à jour une liste d'échantillons de «composants» - unions d'orbites qui se terminent par un cycle commun (et qui sont donc équivalents à des cycles) - qui ont déjà été visités.
Initialiser une liste vide de composants, complist
. Chaque composant est représenté par une collection d'échantillons de ce composant; nous maintenons également un arbre de recherche samples
qui stocke tous les éléments qui ont été sélectionnés comme échantillons pour certains composants ou autres. Soit G une séquence d'entiers jusqu'à n , pour laquelle l'appartenance peut être déterminée efficacement en calculant un prédicat booléen; par exemple, des puissances de 2 ou parfaits p e pouvoirs pour un entier p . Pour chaque s ∈ S , procédez comme suit:
- Si s est
samples
activé, passez à l'étape 5.
- Initialiser une liste vide
cursample
, un itérateur j ← f ( s ) et un compteur t ← 1.
- Si j n'est pas
samples
:
- Si t ∈ G , insérez j dans les deux cursample
et samples
.
- Incrémenter t et régler j ← f (j) .
- Vérifiez si j est dedans
cursample
. Sinon, nous avons rencontré un composant précédemment exploré: nous vérifions à quel composant j appartient et insérons tous les éléments de cursample
dans l'élément approprié de complist
pour l'augmenter. Sinon, nous avons retrouvé un élément de l'orbite actuelle, ce qui signifie que nous avons traversé un cycle au moins une fois sans rencontrer de représentants de cycles précédemment découverts: nous insérons cursample
, en tant que collection d'échantillons d'un composant nouvellement trouvé, dans complist
.
- Passez à l'élément suivant de ∈ S .
Pour n = | S |, soit X (n) une fonction croissante monotone décrivant le nombre de cycles attendu ( par exemple X (n) = n 1/3 ), et soit Y (n) = y (n) log ( n ) ∈ Ω ( X (n) log ( n )) est une fonction croissante monotone détermination d' une cible pour l' utilisation de la mémoire ( par exemple , y (n) = n 1/2 ). Nous avons besoin de y (n) ∈ Ω ( X (n) ) car il faudra au moins X ( n ) espace log ( n ) pour stocker un échantillon de chaque composant.
Plus nous échantillonnons d'éléments d'une orbite, plus nous sommes susceptibles de sélectionner rapidement un échantillon dans le cycle à la fin d'une orbite, et ainsi de détecter rapidement ce cycle. D'un point de vue asymptotique, il est alors logique d'obtenir autant d'échantillons que nos limites de mémoire le permettent: nous pouvons aussi bien définir G pour avoir un y (n) éléments attendus qui sont inférieurs à n .
- Si la longueur maximale d'une orbite dans S est supposée être L , nous pouvons laisser G être les multiples entiers de L / y (n) .
- S'il n'y a pas de longueur attendue, nous pouvons simplement échantillonner une fois tous les n / y (n)éléments; il s'agit en tout cas d'une borne supérieure sur les intervalles entre les échantillons.
Si, en cherchant un nouveau composant, nous commençons à parcourir des éléments de S que nous avons visités précédemment (soit à partir d'un nouveau composant en cours de découverte, soit d'un ancien dont le cycle terminal a déjà été trouvé), cela prendra au plus n / y ( n) itérations pour rencontrer un élément précédemment échantillonné; il s'agit alors d'une limite supérieure du nombre de fois, pour chaque tentative de trouver un nouveau composant, nous traversons des nœuds redondants. Parce que nous faisons n de telles tentatives, nous visiterons alors de manière redondante des éléments de S au plus n 2 / y (n) fois au total.
Le travail requis pour tester l'appartenance à samples
est O ( y (n) log y (n) ), que nous répétons à chaque visite: le coût cumulé de cette vérification est O ( n 2 log y (n) ). Il y a aussi le coût de l'ajout des échantillons à leurs collections respectives, qui est cumulativement O ( y (n) log y (n) ). Enfin, chaque fois que nous rencontrons à nouveau un composant découvert précédemment, nous devons consacrer jusqu'à X (n) log * y (n) de temps pour déterminer le composant que nous avons redécouvert; comme cela peut se produire jusqu'à n fois, le travail cumulé impliqué est limité par n X (n) log y (n) .
Ainsi, le travail cumulé effectué pour vérifier si les nœuds que nous visitons figurent parmi les échantillons dominent le temps d'exécution: cela coûte O ( n 2 log y (n) ). Il faut alors rendre y (n) le plus petit possible, c'est-à-dire O ( X (n) ).
Ainsi, on peut énumérer le nombre de cycles (qui est le même que le nombre de composants qui se terminent par ces cycles) dans l' espace O ( X (n) log ( n )), en prenant O ( n 2 log X (n) ) le temps de le faire, où X (n) est le nombre attendu de cycles.