Encontrar todos los ciclos

9

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 SSf:SS<SS

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 <sSfs<

¿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 ) .fΩ(n)

Charles
fuente
44
Una de las formas naturales de medir el espacio es considerar S como el conjunto de cadenas de n bits yf como un oráculo. Entonces el algoritmo ingenuo que describiste requiere un espacio exponencial. Uno puede buscar un algoritmo que use solo espacio polinómico, pero no parece que sea posible para mí.
Tsuyoshi Ito
Eso es lo que quise decir con "No sé cuál es la mejor manera de medir el espacio". Posiblemente debería apuntar a O (poli (n) + y) donde y es la salida, de modo que el espacio utilizado sea polinomial siempre que y sea suficientemente pequeño.
Charles el
¿Su función f tiene propiedades utilizables? Independientemente de que el algoritmo es polinómica o exponencial en su forma preferida de expresar el tamaño de la entrada será algo discutible si la respuesta práctica es que el algoritmo tomará tiempo y espacio en el orden de la cardinalidad de S .
Niel de Beaudrap
@Niel de Beaudrap: No estoy seguro de qué propiedades serían útiles. Sin embargo, espero que el número de ciclos distintos sea pequeño, probablemente ; Es por eso que sugerí una función deyyn enlugar de solon. Estoy dispuesto a usar el espacio exponencial en el número de bits de salida, si es necesario. O(n3)ynortenorte
Charles

Respuestas:

7

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:

  1. Si A [ s ] =  (fully explored), salte al paso 6.
  2. Establezca A [ s ] ←  (partially explored), y establezca un iterador j  ←  f (s) .
  3. Mientras A [ j ] =  (unexplored), establezca A [ j ] ←  (partially explored), y establezca j  ←  f (j) .
  4. Si A [ 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 .
  5. Para indicar que la órbita que comienza en s ahora se ha explorado por completo, configure j  ←  s .
    Mientras A [ j ] =  (partially explored), establezca A [ j ] ←  (fully explored)y establezca j  ←  f (j) .
  6. Proceder al siguiente elemento s  ∈  S .

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 ) .

Niel de Beaudrap
fuente
O(El |SEl |Iniciar sesiónEl |SEl |)<
Me pregunto si hay una manera de usar mucho menos espacio en el caso de que haya pocos ciclos totales sin usar más que espacio polinómico. Ah, no importa; Esto servirá para mis necesidades.
Charles
1
Me parece que esto debería estar en #L (usando alimentación de matriz). ¿Puede esto ser # L-duro?
Kaveh
@Charles: mira mi respuesta más reciente que te dará mejoras si sabes que #cycles ∈ o ( | S | ). Utiliza más que polylog | S | espacio, pero si está dispuesto a intercambiar espacio y tiempo, puede ser mejor para usted.
Niel de Beaudrap
@Niel de Beaudrap: ¡Gracias! +1 para ambos. Este algoritmo parece mejor siempre que los datos queden en la memoria; una vez que se derrame, miraré usar el otro. (Es posible que el otro sería mejor si pudiera acomodar todo en el caché, pero eso puede ser demasiado complicado.)
Charles
5

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:

  1. Si s está dentro samples, salte al paso 5.
  2. Inicialice una lista vacía cursample, un iterador j  ← f ( s ) y un contador t  ← 1.
  3. Mientras j no está en samples:
    - Si t  ∈  G , inserte j en ambos cursampley samples.
    - Incremente t y establezca j  ←  f (j) .
  4. 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.
  5. 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.

Niel de Beaudrap
fuente
1

ssF(s)

Peter Taylor
fuente