Como a complexidade do algoritmo é modelada para linguagens funcionais?

38

A complexidade do algoritmo é projetada para ser independente dos detalhes de nível inferior, mas é baseada em um modelo imperativo, por exemplo, o acesso à matriz e a modificação de um nó em uma árvore levam o tempo O (1). Este não é o caso em linguagens funcionais puras. A lista Haskell leva tempo linear para acesso. Modificar um nó em uma árvore envolve fazer uma nova cópia da árvore.

Deve haver uma modelagem alternativa da complexidade do algoritmo para linguagens funcionais?

wsaleem
fonte
3
Este pode ser o que você está procurando.
Aristu
1
Sua pergunta pode ser respondida aqui: cs.stackexchange.com/q/18262/755 . Em particular, a complexidade do tempo em uma linguagem puramente funcional difere da complexidade do tempo em uma linguagem imperativa em, no máximo, uma proporção de , para algumas suposições adequadas sobre as capacidades de ambas as línguas. O(registron)
DW
3
O GHC Haskell suporta matrizes e árvores mutáveis ​​e assim por diante, permitindo que você faça acesso à matriz e modifique os nós das árvores no tempo O (1), usando "threads de estado" (as STmônadas).
precisa saber é o seguinte
1
@BobJarvis Depends. Uma lista é um tipo de dados abstrato para você ou você está pensando especificamente em listas vinculadas?
Raphael
1
Que objetivo você procura modelar a complexidade algorítmica? Você está procurando algo matematicamente puro ou prático? Por um valor prático, deve-se prestar atenção a coisas como se você tem ou não uma memorização disponível, mas, de um ponto de vista purista matemático, os recursos da implementação não devem importar.
Cort Ammon

Respostas:

34

Se você assumir que o calculus é um bom modelo de linguagens de programação funcional, então pode-se pensar: o λ- calculus tem uma noção aparentemente simples de complexidade de tempo: basta contar o número de etapas de redução β ( λ x . M ) N M [ N / x ] .λλβ(λx.M)NM[N/x]

Mas isso é uma boa medida de complexidade?

Para responder a essa pergunta, devemos esclarecer o que entendemos por medida de complexidade em primeiro lugar. Uma boa resposta é dada pela tese de Slot e van Emde Boas : qualquer boa medida de complexidade deve ter uma relação polinomial com a noção canônica de complexidade de tempo definida usando máquinas de Turing. Em outras palavras, deve haver um 'razoável' que codifica A partir de X termos -calculus para máquinas Turing, tal para alguns polinómio p , que é o caso que, para cada termo M de tamanho | M | : M reduz para um valor em p ( | M |tr(.)λpM|M|M etapas de redução β exatamente quando t r ( M ) se reduz a um valor emetapas p ( | t r ( M ) | ) de uma máquina de Turing.p(|M|) βtr(M)p(|tr(M)|)

Durante muito tempo, não ficou claro se isso pode ser alcançado no cálculo λ. Os principais problemas são os seguintes.

  • Existem termos que produzem formas normais (em um número polinomial de etapas) que são de tamanho exponencial. Mesmo anotando os formulários normais leva tempo exponencial.
  • A estratégia de redução escolhida desempenha um papel importante. Por exemplo, existe uma família de termos que reduz em um número polinomial de etapas β paralelas (no sentido de redução λ ideal ), mas cuja complexidade é não elementar (significando pior que exponencial).

O artigo " Beta Reduction is Invariant, Indeed ", de B. Accattoli e U. Dal Lago, esclarece a questão, mostrando uma codificação 'razoável' que preserva a classe P de complexidade das funções de tempo polinomial, assumindo reduções de chamada por nome mais à esquerda . O insight principal é que a explosão exponencial só pode acontecer por razões 'desinteressantes' que podem ser derrotadas pelo compartilhamento adequado. Em outras palavras, a classe P é a mesma, independentemente de você a definir contando as etapas da máquina de Turing ou as reduções (mais à esquerda na extrema) .β

Não tenho certeza de qual é a situação para outras estratégias de avaliação. Não sei que um programa semelhante foi realizado para complexidade do espaço.

Martin Berger
fonte
23

A complexidade do algoritmo foi projetada para ser independente dos detalhes de nível inferior.

Não, não mesmo. Sempre contamos operações elementares em alguns modelos de máquinas:

  • Etapas para máquinas de Turing.
  • Operações básicas em RAMs.

Você provavelmente estava pensando em toda a / Θ / O -business. Embora seja verdade que você possa abstrair alguns detalhes de implementação com os assintóticos Landau, você não se livra do impacto do modelo da máquina. Os algoritmos têm tempos de execução muito diferentes, digamos, TMs e RAMs - mesmo se você considerar apenas classes Θ !ΩΘOΘ

Portanto, sua pergunta tem uma resposta simples: conserte um modelo de máquina e quais "operações" contar. Isso lhe dará uma medida. Se você deseja que os resultados sejam comparáveis ​​aos algoritmos não funcionais, seria melhor compilar seus programas na RAM (para análise de algoritmos) ou TM (para teoria da complexidade) e analisar o resultado. Teoremas de transferência podem existir para facilitar esse processo.

Rafael
fonte
Acordado. Nota lateral: Pessoas que freqüentemente fazem um monte de erros sobre o que as operações são "constante". Por exemplo, assumindo a + b é O(1)quando é realmenteO(log ab)
Paul Draper
3
@PaulDraper Essa é uma suposição diferente, não necessariamente um erro. Podemos modelar o que queremos - a questão é se ela responde a perguntas interessantes. Veja também aqui .
Raphael
que soa um lote terrível como "livrar-se do modelo de máquina"
Paul Draper
@PaulDraper Depende do tipo de sentimento que você atribui à palavra "máquina". Veja também esta discussão . FWIW, o modelo de RAM de custo unitário - sem dúvida o modelo padrão na análise de algoritmos! - é útil, caso contrário não teria sido usado por décadas agora. Todos os limites familiares para classificação, árvores de pesquisa etc. são baseados nesse modelo. Faz sentido porque modela bem os computadores reais, desde que os números se ajustem aos registros.
Raphael
1

Em vez de formular sua medida de complexidade em termos de alguma máquina abstrata subjacente, você pode incluir o custo nas próprias definições de linguagem - isso é chamado de Dinâmica de Custo . Um atribui um custo a todas as regras de avaliação na linguagem, de maneira composicional - ou seja, o custo de uma operação é uma função do custo de suas subexpressões. Essa abordagem é mais natural para linguagens funcionais, mas pode ser usada para qualquer linguagem de programação bem definida (é claro que a maioria das linguagens de programação infelizmente não está bem definida).

Gardenhead
fonte
<Discussão sobre o que é um modelo de máquina excluído.> Vamos continuar esta discussão no chat .
Raphael