Recorrências e funções de geração em algoritmos


18

A combinatória desempenha um papel importante na ciência da computação. Utilizamos frequentemente métodos combinatórios tanto na análise quanto no design de algoritmos. Por exemplo, um método para encontrar uma cobertura -vertex definida em um gráfico pode apenas inspecionar todos os \ subconjuntos possíveis \ binom {n} {k} . Enquanto as funções binomiais crescem exponencialmente, se k é alguma constante fixa, terminamos com um algoritmo de tempo polinomial por análise assintótica.k(nk)k

Muitas vezes, problemas da vida real exigem mecanismos combinatórios mais complexos que podemos definir em termos de recorrências. Um exemplo famoso é a sequência de fibonacci (ingenuamente) definida como:

f(n)={1if n=10if n=0f(n1)+f(n2)otherwise

Agora calcular o valor do n º prazo cresce exponencialmente usando essa recorrência, mas graças a programação dinâmica, podemos calcular-lo em tempo linear. Agora, nem todas as recorrências se prestam ao DP (mão secundária, a função fatorial), mas é uma propriedade potencialmente explorável ao definir algumas contagens como uma recorrência em vez de uma função geradora.

As funções de geração são uma maneira elegante de formalizar algumas contagens para uma determinada estrutura. Talvez a mais famosa seja a função geradora binomial definida como:

(x+y)α=k=0(αk)xαkyk

Felizmente, isso tem uma solução de formulário fechado. Nem todas as funções geradoras permitem uma descrição tão compacta.

Agora, minha pergunta é a seguinte: com que frequência as funções geradoras são usadas no design de algoritmos? É fácil ver como eles podem ser explorados para entender a taxa de crescimento exigida por um algoritmo por meio de análise, mas o que eles podem nos dizer sobre um problema ao criar um método para resolver algum problema?

Se muitas vezes a mesma contagem pode ser reformulada como uma recorrência, ela pode se prestar à programação dinâmica, mas, novamente, talvez a mesma função geradora tenha uma forma fechada. Portanto, não é tão uniformemente cortado.


Se a função geradora fornecer uma fórmula (por exemplo, a fórmula de Binet para números de fibonacci) que pode ser usada para calcular o número em vez de usar a recorrência (talvez com mais eficiência), você considera isso como uma resposta?
Aryabhata

Respostas:


11

As funções de geração são úteis ao projetar algoritmos de contagem. Ou seja, não apenas quando você está procurando o número de objetos com uma determinada propriedade, mas também quando você está procurando uma maneira de enumerar esses objetos (e, talvez, gerar um algoritmo para contar os objetos). Há uma apresentação muito boa no capítulo 7 da Matemática Concreta, de Ronald Graham, Donald Knuth e Oren Patashnik . Os exemplos abaixo são desses livros (os erros e a falta de clareza são meus).

Suponha que você esteja procurando maneiras de fazer alterações com um determinado conjunto de moedas. Por exemplo, com denominações americanas comuns¹, as moedas possíveis são . Para dar 42 em mudança, uma possibilidade é ; outra possibilidade é . Escreveremos . De maneira mais geral, podemos escrever uma função geradora para todas as formas de alteração: Em termos mais técnicos, é um termo no espaço da série de potências sobre as cinco variáveis[1],[5],[10],[25],[100][25][10][5][1][1][10][10][10][10][1][1]42[25][10][5][1]2=[10]4[1]2

H=h0q0d0n0p0[100]h[25]q[10]d[5]n[1]p
H[100],[25],[10],[5],[1]. Defina a avaliação de um monômio nesse espaço por em seguida, as maneiras de dar centavos na mudança são o número de monômios cuja valorização é . Podemos expressar de uma maneira incremental, primeiro escrevendo as maneiras de dar troco apenas em moedas de um centavo, depois as maneiras de dar a mudança de moedas de um centavo e centavo, e assim por diante. ( não dizer moeda.)
[100]h[25]q[10]d[5]n[1]p=100h+25q+10d+5n+p
vvHPNI
P=I+[1]+[1]2+[1]3+=II[1]N=(I+[5]+[5]2+[5]3+)P=PI[5]D=(I+[10]+[10]2+[10]3+)N=NI[10]Q=(I+[25]+[25]2+[25]3+)D=DI[25]H=(I+[100]+[100]2+[100]3+)Q=QI[100]
Se você deseja contar e não apenas enumerar as maneiras de alterar, existe uma maneira simples de usar a série formal que obtivemos. Aplique o homomorfismo o coeficiente de em é o número de maneiras para dar centavos em mudança.
S:[1]X,[5]X5,[10]X10,[25]X25,[100]X100
XvS(C)v

Um exemplo mais difícil: suponha que você queira estudar todas as maneiras de ladrilhar retângulos com dominós 2 × 1. Por exemplo, existem duas maneiras de ladrilhar um retângulo 2 × 2, com dois dominós horizontais ou com dois dominós verticais. Contar o número de maneiras de ladrilhar um retângulo é bastante fácil, mas o caso se torna rapidamente não-óbvio. Podemos enumerar todas as inclinações possíveis de uma faixa horizontal de altura 3 juntando dominós, o que gera rapidamente padrões repetitivos: 2×n3×n

{U=o+LV+ΓΛ+UV=IU+=VΛ=IU+=Λ
onde as formas engraçadas representam arranjos elementares de dominó: não é dominó, é um dominó vertical no topo da parte esquerda de um dominó horizontal, é um dominó vertical alinhado com a parte inferior da faixa da altura 3, é um dominó horizontal alinhado com a parte superior da banda, mais dois dominós horizontais abaixo e um passo à direita, etc. Aqui, multiplicação representa concatenação horizontal e não é comutativa, mas existem equações entre os padrões elementares que formam variáveis ​​nesta série de potências. Como antes, com as moedas, podemos substituir por cada dominó e obter uma série geradora pelo número de inclinações de umoLI=X3×(2n/3)Retângulo (ou seja, o coeficiente de é o número de maneiras de ladrilhar um retângulo da área , que contém dominós e tem a largura ). A série também pode ser usada de maneiras mais versáteis; por exemplo, ao distinguir dominós verticais e horizontais, podemos contar as inclinações com um determinado número de dominós verticais e horizontais.X3k6k3k2k

Mais uma vez, leia Concrete Mathematics para uma apresentação menos apressada³.

¹ Eu sei que minha lista está incompleta; assuma um EUA simplificado adequado para exemplos matemáticos.²
² Além disso, se aparecer, assuma moedas esféricas.
³ E melhor digitado.


8

Lembro-me de um problema que tive que resolver durante um concurso de programação para estudantes em 2001. O problema era este:

Dadas massas de 1, 7, 13, ... (não me lembro quais massas, mas havia um conjunto finito e determinado de massas), projete uma função que determine se um determinado peso pode ser pesado em uma balança com este conjunto de massas.

Comecei com loops aninhados, mas bati rapidamente em uma parede. Então percebi que tinha que começar enumerando o que pode ser feito com as massas mais leves antes de prosseguir com as mais pesadas. Eu poderia resolver o problema com muitos loops não aninhados.

Se eu não tivesse sido jovem arrogante e auto-suficiente naquela época (e eu sabia e praticava funções geradoras), eu poderia ter definido o problema de gerar funções como tal:

Defina como OGF para o número de maneiras que um peso pode ser pesado, dado o conjunto de massas.f(x)n

Que peso no prato direito posso pesar com uma única massa de 1?

Três possibilidades:

  • Se eu colocar a massa na panela esquerda, posso pesar 1.
  • Se eu colocar a massa na panela direita, posso pesar -1.
  • Se eu não usar a massa, posso pesar 0.

Portanto, há uma maneira de pesar , uma maneira de pesar e uma maneira de pesar . A função geradora para essa massa é algo como , que corresponde a:101x1+1+x

1x3x(1x)

A função geradora de uma única massa é , que é:mxm+1+xm

1x3mxm(1xm)

Dado um multiset de massas, é expresso como o produto das funções geradoras de massa única:Mf

f(x)=mM(1x3m)xmMmmM(1xm)

Agora, dado um pacote que pode executar operações em polinômios, você apenas precisa:

  • Calcular os dois produtos.
  • Execute a divisão desses produtos, começando com o menor grau. (que termina)
  • Desloque o polinômio (divisão euclidiana por , mantendo o quociente e despejando o restante)xk

E você terminou. Agora seu polinômio tem o número de maneiras de pesar no índice . A única entrada é o multiconjunto de massas .w0wM

Eu projetei o algoritmo usando componentes matematicamente sólidos. A parte principal do algoritmo, que é uma divisão polinomial com o menor grau primeiro, é linear e pode ser implementada por um pacote pronto para uso. Pode não ser o ideal, mas certamente tem um desempenho melhor do que o que fiz no concurso e de maneira menos propensa a erros.

Se você observar atentamente o processo de divisão, verá rapidamente que o restante pode ser visto como o "estado oculto atual" em todos os estados do processo e o quociente como resultado. O processo termina quando o "estado oculto atual" atinge zero em qualquer lugar.

Você pode implementar polinômios como matrizes ou, se eles forem realmente esparsos, como listas ordenadas com coeficiente de índice e isso não mudará o algoritmo.


3

Ao desenvolver um algoritmo para maximização submodular monótona sobre um matróide, tivemos que resolver a recorrência Depois de perceber que , reduzimos o problema para calcular alguma sequência universal . O último foi realizado usando funções geradoras e, a partir daí, obtivemos uma fórmula explícita para , novamente usando funções geradoras. Você pode encontrar a solução no artigo se estiver curioso, embora nunca tenhamos nos incomodado em incluir essa derivação.

γ+1(m)=(2m)γ(m)+(m+1)γ1(m),γ0(m)=1,γm+1(m)=e.
γ(m)=m(γ(m1)γ1(m1))γ(0)γ(m)

0

Talvez o extenso estudo do Quicksort, e suas muitas variantes, seja o exemplo mais claro. Algumas considerações combinatórias governaram a consideração de alternativas, e a análise das soluções para equações bastante complexas mostra vantagens de desempenho (ou não) delas.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.