Complexidade do algoritmo de Fisher-Yates Shuffle

15

Esta questão é em relação ao algoritmo Fisher-Yates para retornar um shuffle aleatório de uma determinada matriz. A página da Wikipedia diz que sua complexidade é O (n), mas acho que é O (n log n).

Em cada iteração i, um número inteiro aleatório é escolhido entre 1 e i. Simplesmente escrever o número inteiro na memória é O (log i) e, como não há iterações, o total é

O (log 1) + O (log 2) + ... + O (log n) = O (n log n)

o que não é melhor do que o algoritmo ingênuo. Estou faltando alguma coisa aqui?

Nota: O algoritmo ingênuo é atribuir a cada elemento um número aleatório no intervalo (0,1) e, em seguida, classificar a matriz em relação aos números atribuídos.

Tomer Vromen
fonte

Respostas:

24

Suspeito que aqui, como na maioria dos algoritmos, o custo de leitura e gravação de números de bits seja considerado constante. É um pecado menor, desde que você não se empolgue e derrube P e PSPACE por acidente .O(logn)

Suresh Venkat
fonte
4
Embora esse seja realmente um "pecado menor", acho que é um pecado grave da pedagogia do TCS que isso nunca seja mencionado explicitamente! Todo estudante de CS descobre isso sozinho e pensa que algo importante está errado até que eles saibam que todo mundo sabe disso, mas ninguém fala sobre isso. Além disso, há alguns anos atrás, quando alguém explorou o modelo O (log n) para fornecer um algoritmo de tempo sub-cúbico para algum problema famoso que foi supostamente Omega (n ^ 3)? Isso já foi resolvido?
randomwalker
2
Eu não estou ciente da brouhaha que você está se referindo. Quanto a não mencionar, você está definitivamente certo. Depois que eu li primeiro post de Jeff Erickson, eu agora fazer-lhe um ponto para provar P = PSPACE na minha aula de geometria apenas por diversão :)
Suresh Venkat
11
Obrigado pela resposta. Eu nunca soube que era um grande negócio. O link fornece uma boa leitura.
Tomer Vromen
11
Conclusão: sempre explique seu modelo.
Jukka Suomela 17/08/10
2
Eu acho que a principal razão pela qual deixamos bit ops ser constante é que (em tempo polinomial) você pode programar uma tabela de consulta de acesso em tempo constante para todos os pares deoperandosde O ( log n ) bits, para a maioria modelos computacionais "modernos". Não há nada de "pecaminoso" nisso ... para mim, vejo essa propriedade como algo que pode ser simplesmente assumido sem perda de generalidade. O(registron)O(registron)
Ryan Williams
17

O modelo padrão de computação assume que operações aritméticas em números inteiros de O (log n) bits podem ser executadas em tempo constante, uma vez que essas operações geralmente são entregues em hardware. Portanto, no algoritmo Fisher-Yates, "escrever o número inteiro i na memória" leva apenas tempo O (1).

Obviamente, é perfeitamente significativo analisar o algoritmo em termos de operações de bits, mas o modelo de custo de bits é menos preditivo do comportamento real. Até o loop simples for i = 1 to n: print(i)requer operações de O (n log n) bit.

Jeffε
fonte
Bom ponto com o loop. Nunca reparou que ...
Tomer Vromen
8

Esta é uma resposta para "[O algoritmo de Fisher-Yates] não é melhor que o ingênuo. Estou perdendo alguma coisa aqui?" que você fez na pergunta.

No seu algoritmo "ingênuo", que usa números reais: quantos bits de precisão você usa? Se você está contando a complexidade de bits (como parece estar fazendo para Fisher-Yates) e o algoritmo usa k bits aleatórios para os números reais, então o tempo de execução seria Ω (kn log n), pois comparando dois k- números reais de bits levam tempo Ω (k). Mas k precisa ser pelo menos Ω (log n) para impedir que dois elementos sejam mapeados para o mesmo número real, o que significa que o algoritmo leva Ω (n log 2 n) tempo, que é mais lento que o embaralhamento de Fisher-Yates por um fator de log

Se você está apenas contando o número de operações aritméticas e de comparação e ignorando sua complexidade de bits, Fisher-Yates é Θ (n) e seu algoritmo é Θ (n log n), ainda um fator de log n distante.

ShreevatsaR
fonte
Eu suspeitava que o algoritmo "ingênuo" tivesse esse k implícito ...
Tomer Vromen
11
O algoritmo "ingênuo" pode ser implementado de maneira limpa em tempo linear, como a seguir. Atribua a cada elemento um número inteiro aleatório entre 1 e n ^ 3 e, em seguida, classifique os números no tempo O (n) via classificação por raiz. (Com alta probabilidade, há dois elementos terá o mesmo número aleatório Se houver duplicatas, remodelação-los de forma recursiva..)
Jeffε
@ Jeff: Obrigado! Isso é muito limpo e tem a mesma complexidade que Fisher-Yates. Depois de postar isso, eu estava realmente sentindo que o algoritmo “ingênuo” não deveria ser pior ... Perdi o fato de que números de nk bits podem ser classificados em O (nk), não precisando de O (nklog n). Mas acho que Knuth-Fisher-Yates ainda é melhor nas constantes: requer exatamente (log n!) Bits aleatórios - um número inteiro aleatório de 1 a n, depois 1 a n-1, etc. - o que é ideal (em vez de 3n log n), e isso pode ser feito no local com apenas memória extra constante.
ShreevatsaR
6

Não há nada especial sobre números inteiros para esse problema.

Por exemplo, as tabelas de hash (armazenando qualquer tipo de valor) não têm tempo O (1) para acessar se a função de hash precisar ler todo o valor para calcular seu hash. n elementos únicos requerem log n bits, em média, para representar, não importa quão inteligente seja sua representação, e qualquer função hash que leia toda a sua entrada levará, portanto, pelo menos o tempo necessário para a computação. Na prática, eles são mais rápidos do que as árvores vermelho-escuras, mas assintoticamente não são melhores.

O brouhaha referenciado por randomwalker foi sobre um artigo do POPL 2008 ( http://portal.acm.org/citation.cfm?doid=1328438.1328460 ), discutido aqui: http://blog.computationalcomplexity.org/2009/05/shaving- logs-with-unit-cost.html

Nesse post, Lance Fortnow descreve como, quando estudante, ele reclamou que a classificação realmente requer n log ^ 2 n tempo, se precisamos ler todos os bits de log n de dois elementos para compará-los, o que parece uma objeção razoável.

Dave Doty
fonte
Eu não entendo o autor da postagem do blog. Ele reclama que a classificação é realmente O (n log ^ 2 n), mas depois diz que o papel é sólido?
Tomer Vromen
O artigo é sólido (isto é, não é falso), pois existe um modelo no qual as operações aritméticas levam tempo unitário e, nesse modelo, o algoritmo do artigo é o primeiro a obter o (n ^ 3) operações.
Dave Doty
Não recebo a objeção O (n log ^ 2 n) porque, em termos de bits, a entrada em si é do tamanho O (n log n). Btw, como uma nota lateral, o nível de comentários no blog complexidade qualidade era muito maior, então ....
arnab
4

A página da Wikipedia diz que sua complexidade é O (n), mas acho que é O (n log n).

Na verdade, O (n log n) é um limite inferior para esse problema nos modelos em que a classificação é O (n log n). Se todas as permutações são igualmente prováveis, então o algoritmo como uma função de fluxos aleatórios a permutações deve ser subjetivo. Existem n! permutações, portanto, em algo como um modelo de árvore de decisão, existem ramos de comprimento pelo menos O (log n!) = O (n log n).

1 1-ϵO(ϵ)

Per Vognsen
fonte
3

No TCS, consideramos - se não declarado de outra forma explícita - a complexidade em uma Máquina de Turing. Embora isso seja bom para propósitos teóricos, os resultados não são muito úteis na prática, pois implementamos diferentes modelos de máquinas (ou seja, aproximações finitas) no hardware. Portanto, é uma questão viável pedir complexidade nesses modelos. Por exemplo, normalmente assumimos que máquinas de registro (semelhantes a CPUs reais) podem executar operações atômicas em dois registros em tempo constante - é isso que pode ter sido empregado aqui.

Em resumo: você pensa em termos de TMs, os autores do artigo em termos de RMs. Vocês dois estão certos.

Rafael
fonte