Se você tiver muito poucos ciclos, aqui está um algoritmo que usará menos espaço, mas levará muito mais tempo para terminar.
[Editar.] Minha análise anterior em tempo de execução perdeu o custo crucial de determinar se os nós que visitamos estão entre os amostrados anteriormente; essa resposta foi revisada para corrigir isso.
Voltamos a percorrer todos os elementos de S . À medida que exploramos as órbitas dos elementos s ∈ S , coletamos amostras dos nós que visitamos, para poder verificar se os encontramos novamente. Também mantemos uma lista de amostras de 'componentes' - uniões de órbitas que terminam em um ciclo comum (e que são, portanto, equivalentes a ciclos) - que foram visitadas anteriormente.
Inicialize uma lista vazia de componentes complist
,. Cada componente é representado por uma coleção de amostras desse componente; também mantemos uma árvore de pesquisa samples
que armazena todos os elementos que foram selecionados como amostras para um componente ou outro. Seja G uma sequência de números inteiros até n , para os quais a associação é eficientemente determinável calculando algum predicado booleano; por exemplo, poderes de 2 ou perfeitas p th poderes para algum inteiro p . Para cada s ∈ S , faça o seguinte:
- Se s estiver dentro
samples
, pule para a etapa 5.
- Inicialize uma lista vazia
cursample
, um iterador j ← f ( s ) e um contador t ← 1.
- Enquanto j não estiver em
samples
:
- Se t ∈ G , insira j em ambos cursample
e samples
.
- Incremente te ajuste j ← f (j) .
- Verifique se j está em
cursample
. Caso contrário, encontramos um componente explorado anteriormente: verificamos a qual componente j pertence e inserimos todos os elementos de cursample
no elemento apropriado complist
para aumentá-lo. Caso contrário, reencontramos um elemento da órbita atual, o que significa que percorremos um ciclo pelo menos uma vez sem encontrar representantes de ciclos descobertos anteriormente: inserimos cursample
como uma coleção de amostras de um componente recém-encontrado complist
.
- Avance para o próximo elemento s ∈ S .
Para n = | S |, seja X (n) uma função crescente monótona que descreve o número esperado de ciclos ( por exemplo, X (n) = n 1/3 ) e deixe Y (n) = y (n) log ( n ) ∈ ∈ ( X (n) log ( n )) é uma função crescente monótona que determina um alvo para o uso da memória ( por exemplo, y (n) = n 1/2 ). Exigimos y (n) ∈ Ω ( X (n) ) porque será necessário pelo menos X (n) log ( n ) para armazenar uma amostra de cada componente.
Quanto mais elementos de uma órbita amostramos, maior a probabilidade de selecionarmos rapidamente uma amostra no ciclo no final de uma órbita e, assim, detectar rapidamente esse ciclo. Do ponto de vista assintótico, faz sentido obter tantas amostras quanto nossos limites de memória permitirem: é melhor definirmos G para ter um esperado y (n) elementos que são menores que n .
- Se se espera que o comprimento máximo de uma órbita em S seja L , podemos permitir que G sejam os múltiplos inteiros de L / y (n) .
- Se não houver comprimento esperado, podemos simplesmente amostrar uma vez a cada n / a (n)elementos; em qualquer caso, esse é um limite superior nos intervalos entre as amostras.
Se, ao procurar um novo componente, começarmos a atravessar elementos de S que já visitamos anteriormente (seja de um novo componente descoberto ou de um antigo cujo ciclo terminal já foi encontrado), será necessário no máximo n / a ( n) iterações para encontrar um elemento previamente amostrado; esse é um limite superior do número de vezes que, para cada tentativa de encontrar um novo componente, atravessamos nós redundantes. Como fazemos n tais tentativas, visitaremos redundantemente elementos de S no máximo n 2 / y (n) vezes no total.
O trabalho necessário para testar a associação samples
é O ( y (n) log y (n) ), que repetimos a cada visita: o custo cumulativo dessa verificação é O ( n 2 log y (n) ). Há também o custo de adicionar as amostras às suas respectivas coleções, que cumulativamente é O ( y (n) log y (n) ). Finalmente, cada vez que encontramos um componente descoberto anteriormente, precisamos gastar até X (n) log * y (n) tempo para determinar qual componente redescobrimos; como isso pode acontecer até n vezes, o trabalho cumulativo envolvido é limitado por n X (n) log y (n) .
Assim, o trabalho cumulativo realizado para verificar se os nós que visitamos estão entre as amostras domina o tempo de execução: isso custa O ( n 2 log y (n) ). Então devemos tornar y (n) o menor possível, ou seja, O ( X (n) ).
Assim, pode-se enumerar o número de ciclos (que é o mesmo que o número de componentes que terminam nesses ciclos) no espaço O ( X (n) log ( n )), ocupando O ( n 2 log X (n) ) tempo para fazê-lo, onde X (n) é o número esperado de ciclos.