Si tiene muy pocos ciclos, aquí hay un algoritmo que usará menos espacio, pero tomará mucho más tiempo terminarlo.
[Editar.] Mi análisis de tiempo de ejecución anterior omitió el costo crucial de determinar si los nodos que visitamos se encuentran entre los previamente muestreados; Esta respuesta ha sido revisada para corregir esto.
Volvemos a iterar a través de todos los elementos de S . A medida que exploramos las órbitas de los elementos s ∈ S , tomamos muestras de los nodos que hemos visitado, para poder verificar si los encontramos nuevamente. También mantenemos una lista de muestras de 'componentes' --uniones de órbitas que terminan en un ciclo común (y que, por lo tanto, son equivalentes a ciclos) - que han sido visitadas previamente.
Inicializar una lista vacía de componentes, complist. Cada componente está representado por una colección de muestras de ese componente; También mantenemos un árbol de búsqueda samplesque almacena todos los elementos que han sido seleccionados como muestras para algún componente u otro. Supongamos que G es una secuencia de enteros hasta n , para la cual la membresía se puede determinar de manera eficiente al calcular algún predicado booleano; por ejemplo, las potencias de 2 o perfectos p th poderes para algún entero p . Para cada s ∈ S , haga lo siguiente:
- Si s está dentro
samples, salte al paso 5.
- Inicialice una lista vacía
cursample, un iterador j ← f ( s ) y un contador t ← 1.
- Mientras j no está en
samples:
- Si t ∈ G , inserte j en ambos cursampley samples.
- Incremente t y establezca j ← f (j) .
- Verifique si j está adentro
cursample. Si no, hemos encontrado un componente explorado previamente: verificamos a qué componente pertenece j e insertamos todos los elementos de cursampleen el elemento apropiado complistpara aumentarlo. De lo contrario, hemos reencontrado un elemento de la órbita actual, lo que significa que hemos atravesado un ciclo al menos una vez sin encontrar ningún representante de ciclos descubiertos previamente: insertamos cursample, como una colección de muestras de un componente recién encontrado, en complist.
- Proceder al siguiente elemento s ∈ S .
Para n = | S |, sea X (n) una función monótona creciente que describa el número esperado de ciclos ( por ejemplo, X (n) = n 1/3 ), y sea Y (n) = y (n) log ( n ) ∈ Ω ( X (n) log ( n )) es una función de aumento monótono que determina un objetivo para el uso de la memoria ( por ejemplo, y (n) = n 1/2 ). Requerimos y (n) ∈ Ω ( X (n) ) porque tomará al menos espacio X (n) log ( n ) para almacenar una muestra de cada componente.
Cuantos más elementos de una órbita muestreemos, más probabilidades tenemos de seleccionar rápidamente una muestra en el ciclo al final de una órbita y, por lo tanto, detectar rápidamente ese ciclo. Desde un punto de vista asintótico, entonces tiene sentido obtener tantas muestras como lo permitan nuestros límites de memoria: también podemos establecer que G tenga elementos y (n) esperados que son menores que n .
- Si se espera que la longitud máxima de una órbita en S sea L , podemos dejar que G sea el múltiplo entero de L / y (n) .
- Si no hay una longitud esperada, simplemente podemos muestrear una vez cada n / a (n)elementos; En cualquier caso, este es un límite superior en los intervalos entre muestras.
Si, al buscar un nuevo componente, comenzamos a atravesar elementos de S que hemos visitado anteriormente (ya sea de un nuevo componente que se está descubriendo o de uno antiguo cuyo ciclo terminal ya se ha encontrado), tomará como máximo n / a ( n) iteraciones para encontrar un elemento muestreado previamente; este es un límite superior en el número de veces, por cada intento de encontrar un nuevo componente, atravesamos nodos redundantes. Debido a que hacemos n intentos de este tipo, visitaremos de manera redundante elementos de S como máximo n 2 / a (n) veces en total.
El trabajo requerido para evaluar la membresía sampleses O ( y (n) log y (n) ), que repetimos en cada visita: el costo acumulado de esta verificación es O ( n 2 log y (n) ). También existe el costo de agregar las muestras a sus colecciones respectivas, que acumulativamente es O ( y (n) log y (n) ). Finalmente, cada vez que volvemos a encontrar un componente descubierto previamente, debemos gastar hasta X (n) log * y (n) tiempo para determinar qué componente redescubrimos; Como esto puede suceder hasta n veces, el trabajo acumulado implicado está limitado por n X (n) log y (n) .
Por lo tanto, el trabajo acumulado realizado para verificar si los nodos que visitamos están entre las muestras dominan el tiempo de ejecución: esto cuesta O ( n 2 log y (n) ). Entonces deberíamos hacer y (n) lo más pequeño posible, es decir O ( X (n) ).
Por lo tanto, uno puede enumerar el número de ciclos (que es el mismo que el número de componentes que terminan en esos ciclos) en el espacio O ( X (n) log ( n )), tomando O ( n 2 log X (n) ) tiempo para hacerlo, donde X (n) es el número esperado de ciclos.