Tengo un conjunto finito , una función , y una orden total en . Quiero encontrar el número de ciclos distintos en .f : S → S < S S
Para un elemento dado , puedo usar el algoritmo de Floyd (o Brent, etc.) para encontrar la duración del ciclo al que las aplicaciones repetidas de envían ; Con un poco más de esfuerzo puedo identificar este ciclo (por ejemplo, por su elemento -minimal). Un mal método para resolver el problema sería repetir esto en cada elemento, ordenar los elementos mínimos resultantes descartando duplicados y devolver el recuento. Pero esto potencialmente implica muchos pases sobre los mismos elementos y grandes requisitos de espacio.f s <
¿Qué métodos tienen mejor rendimiento en tiempo y espacio? Ni siquiera estoy seguro de cuál es la mejor manera de medir el espacio necesario: si es la función de identidad, entonces cualquier método que almacene todos los ciclos utilizará el espacio Ω ( n ) .
fuente
Respuestas:
Si todo lo que quiere hacer es contar el número de ciclos, puede hacerlo con 2 | S | bits (más cambio) de espacio. Parece poco probable que pueda hacerlo mucho mejor a menos que S o f tenga algunas propiedades particularmente convenientes.
Comience con una matriz A que almacena enteros {0,1,2} - uno por elemento de S - inicializado a cero; indicaremos como éstos
(unexplored)
,(partially explored)
y(fully explored)
. Inicialice un contador de ciclos a cero. Para cada elemento s ∈ S en orden, haga lo siguiente:(fully explored)
, salte al paso 6.(partially explored)
, y establezca un iterador j ← f (s) .(unexplored)
, establezca A [ j ] ←(partially explored)
, y establezca j ← f (j) .(partially explored)
, hemos cerrado un nuevo ciclo; incremente c en 1. (Si desea mantener un registro de algún representante de este ciclo, el valor actual de j será una elección arbitraria; por supuesto, este no será necesariamente el elemento mínimo en el ciclo bajo su preferencia orden <.) De lo contrario, tenemos A [ j ] =(fully explored)
, lo que significa que hemos descubierto una órbita explorada previamente que termina en un ciclo ya contado; no incremente c .Mientras A [ j ] =
(partially explored)
, establezca A [ j ] ←(fully explored)
y establezca j ← f (j) .Por lo tanto, cada ciclo entre las órbitas inducidas por f se contará una vez; y cualquier elemento que registre como representantes serán elementos de ciclos distintos. Los requisitos de memoria son 2 | S | para la matriz A, O (log | S |) para el recuento del ciclo y otras probabilidades y finales.
Cada elemento s ∈ S se visitará al menos dos veces: una vez cuando el valor de A [ s ] cambia de
(unexplored)
a(partially explored)
, y una vez para el cambio a(fully explored)
. El número total de veces que los nodos se vuelven a visitar después de haber sido(fully explored)
acotado está limitado anteriormente por el número de intentos de encontrar nuevos ciclos que no lo hacen, que es como máximo | S | - derivados de la iteración de bucle principal a través de todos los elementos de S . Por lo tanto, podemos esperar que este proceso implique como máximo 3 | S | recorridos de nodos, contando todas las veces que los nodos son visitados o revisitados.Si desea realizar un seguimiento de los elementos representativos de los ciclos, y realmente desea que sean los elementos mínimos, puede limitar el número de visitas a los nodos a 4 | S |, si agrega una "vuelta al ciclo" adicional en el paso 4 para encontrar un representante que sea más pequeño que el que cierra el ciclo. (Si las órbitas debajo de f consistieran solo en ciclos, este trabajo adicional podría evitarse, pero eso no es cierto para f arbitraria ) .
fuente
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úsquedasamples
que 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:samples
, salte al paso 5.cursample
, un iterador j ← f ( s ) y un contador t ← 1.samples
:- Si t ∈ G , inserte j en ambos
cursample
ysamples
.- Incremente t y establezca j ← f (j) .
cursample
. Si no, hemos encontrado un componente explorado previamente: verificamos a qué componente pertenece j e insertamos todos los elementos decursample
en el elemento apropiadocomplist
para 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: insertamoscursample
, como una colección de muestras de un componente recién encontrado, encomplist
.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
samples
es 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.
fuente
fuente