Multiplicação de polinómios – part ii
Viva. Após a maré de degredo que afectou este estimado blog, venho tentar retomar a dignidade e qualidade do mesmo, uma tarefa deveras hercúlea.
Consideremos a multiplicação de polinómios em [latex]\mathbb{F}_2[x][/latex]. O algoritmo considerado no post anterior é conhecido há milénios e tem uma complexidade quadrática, i.e., [latex]O(n^2)[/latex] operações para multiplicar dois polinómios de grau n. Este algoritmo será óptimo? Não.
Nos anos 60, foi descoberto o algoritmo de Karatsuba para multiplicar números inteiros em assimptoticamente menos operações. Resumidamente, este algoritmo divide os números a multiplicar na sua parte superior e inferior, e efectua a multiplicação em 3, não 4, multiplicações menores. Por exemplo, para multiplicar 123456 e 789012, dividimos ambos os números a meio: 123, 456, 789 e 012. Obtemos a parte menos significativa do resultado por multiplicar 456*012 = 5472. A parte mais significativa é obtida por 789*123 = 98154. O resto do nosso produto é obtido por efectuar a operação: (123+456)(789+012) - 5472 - 98154 = 360153. O resultado final é 5472 + 10^3*360153 + 10^6*98154 = 97408265472. Podem facilmente verificar que o resultado está correcto.
Este método pode ser aplicado recursivamente aos produtos mais pequenos resultantes de cada passo (divide and conquer). Isto significa que esta recorrência vai precisar de [latex]O(n^{\log_2 3})[/latex] operações para multiplicar dois números. Isto pode parecer irrelevante, mas faz toda a diferença com números grandes (e.g., com mais de 1024 bits.)
O método de Karatsuba não se limita aos inteiros; aplica-se a qualquer anel comutativo. Isto significa que podemos facilmente adaptar este método para multiplicar polinómios. Os truques algébricos para acelerar a multiplicação são uma área extremamente interessante: duas referências relevantes e recomendadas são 'Multidigit multiplication for mathematicians' e 'Faster multiplication in GF(2)[x]'.
No caso dos polinómios com coeficientes em {0,1}, i.e., [latex]\mathbb{F}_2[x][/latex], a tarefa é ainda mais simples: a adição é equivalente à subtracção, ambas sendo efectuadas com um simples xor. Implementar este algoritmo em C++ sem operações condicionais é ainda mais simples, dada a natureza recursiva do mesmo:
template<unsigned N> inline word kmul(word a, word b) { word a0, a1, b0, b1; word z0, z1, z2; a0 = a&(const word)((1<<(N/2))-1); b0 = b&(const word)((1<<(N/2))-1); a1 = a>>(const word)(N/2); b1 = b>>(const word)(N/2); z0 = kmul<N/2>(a0,b0); z2 = kmul<N/2>(a1, b1); z1 = kmul<N/2>(a0^a1, b0^b1) ^ z0 ^ z2; return z0 ^ (z2<<N) ^ (z1<<(N/2)); } template<> inline word kmul<1>(word a, word b) { return a&b; }
Apesar da menor complexidade deste algoritmo, esta implementação não vai ser mais rápida (para todos os tamanhos úteis) do que a do post anterior. Alguém me sabe dizer porquê?
amo-te de verdade! até me sinto burro a comentar isto e a não perceber um caralhete! LOL
Sirs, vim cá avisar que segundo a reportagem da RTP há CP da Maddie na internet e espero em breve tê-lo em minha posse.
FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP FAP
Não te sei responder. Mas gostava de saber como é que o Karatsuba chegou a essa solução...
falco:
Aparentemente por observação. Depois de ser inventado, foi descoberta a teoria por trás, que é baseada na interpolação e avaliação de polinómios em certos pontos. Foi da mesma forma que se derivaram grande parte das variantes do Toom-Cook.
Tentando responder à pergunta: a implementação do post anterior usa apenas adições/subtracções e operadores binários. Neste post temos muitas divisões, que deve estragar tudo...
Gostei de conhecer o método de Karatsuba.
PS: andei cheio de trabalho durante a semana e só agora pude ler com olhos de ver!
As divisões são todas por 2, não era pra ser lento não, o compilador deve substituir /2 por >>1, anyway... boa..