A programação dinâmica oferece uma maneira de pensar sobre o design de algoritmos. Isso geralmente é muito útil.
Os métodos de memorização e de baixo para cima fornecem uma regra / método para transformar relações de recorrência em código. Memoização é uma idéia relativamente simples, mas as melhores idéias geralmente são!
A programação dinâmica oferece uma maneira estruturada de pensar sobre o tempo de execução do seu algoritmo. O tempo de execução é basicamente determinado por dois números: o número de subproblemas que você precisa resolver e o tempo necessário para resolver cada subproblema. Isso fornece uma maneira fácil e conveniente de pensar sobre o problema de design do algoritmo. Quando você tem uma relação de recorrência de candidato, pode examiná-la e ter uma noção rápida de qual será o tempo de execução (por exemplo, é possível dizer com muita rapidez quantos subproblemas haverá, o que é um limite inferior no tempo de execução; se houver exponencialmente muitos subproblemas a serem resolvidos, a recorrência provavelmente não será uma boa abordagem). Isso também ajuda a descartar decomposições de subproblemas de candidatos. Por exemplo, se tivermos uma sequênciaS [ 1 .. i ]S[1..n], definindo um subproblema por um prefixo ou sufixo S [ j . . n ] ou substring S [ i . . j ] pode ser razoável (o número de subproblemas é polinomial em n ), mas definir um subproblema por uma subsequência de S provavelmente não será uma boa abordagem (o número de subproblemas é exponencial em n ). Isso permite remover o "espaço de pesquisa" de possíveis recorrências.S[1..i]S[j..n]S[i..j]nSn
A programação dinâmica oferece uma abordagem estruturada para procurar relações de recorrência de candidatos. Empiricamente, essa abordagem geralmente é eficaz. Em particular, existem algumas heurísticas / padrões comuns que você pode reconhecer por maneiras comuns de definir subproblemas, dependendo do tipo de entrada. Por exemplo:
Se a entrada for um número inteiro positivo , uma maneira candidata de definir um subproblema é substituindo n por um número inteiro menor n ′ (st 0 ≤ n ′ ≤ n ).nnn′0≤n′≤n
Se a entrada for uma string , algumas maneiras candidatas de definir um subproblema incluem: substitua S [ 1 .. n ] pelo prefixo S [ 1 .. i ] ; substitua S [ 1 .. n ] por um sufixo S [ j . . n ] ; substitua S [ 1 .. n ] por uma substring S [ i . . j ]S[1..n]S[1..n]S[1..i]S[1..n]S[j..n]S[1..n]S[i..j]. (Aqui o subproblema é determinado pela escolha de .)i,j
Se a entrada for uma lista , faça o mesmo que faria para uma sequência.
Se a entrada for uma árvore , uma maneira candidata de definir um subproblema é substituir T por qualquer subárvore de T (ou seja, escolha um nó x e substitua T pela subárvore com raiz em x ; o subproblema é determinado pela escolha de x )TTTxTxx
Se a entrada for um par , observe recursivamente o tipo de xe o tipo de y para identificar uma maneira de escolher um subproblema para cada um. Em outras palavras, uma maneira candidata de definir um subproblema é substituir ( x , y ) por ( x ′ , y ′ ) onde x ′ é um subproblema para x e y ' é um subproblema para y . (Você também pode considerar subproblemas do formulário ( x , y(x,y)xy(x,y)(x′,y′)x′xy′y Ou ( x ′ , y ) .)(x,y′)(x′,y)
E assim por diante. Isso fornece uma heurística muito útil: apenas observando a assinatura de tipo do método, você pode criar uma lista de maneiras candidatas para definir subproblemas. Em outras palavras, apenas olhando para a declaração do problema - olhando apenas para os tipos de entradas - você pode encontrar várias maneiras candidatas para definir um subproblema.
Isso geralmente é muito útil. Ele não informa qual é a relação de recorrência, mas quando você tem uma opção específica de como definir o subproblema, geralmente não é muito difícil descobrir uma relação de recorrência correspondente. Portanto, muitas vezes transforma o design de um algoritmo de programação dinâmica em uma experiência estruturada. Você escreve em um pedaço de papel uma lista de maneiras candidatas para definir subproblemas (usando a heurística acima). Em seguida, para cada candidato, você tenta anotar uma relação de recorrência e avaliar seu tempo de execução contando o número de subproblemas e o tempo gasto por subproblema. Depois de experimentar cada candidato, você mantém o melhor que conseguiu encontrar. Fornecer alguma estrutura para o processo de design de algoritmos é uma grande ajuda, pois, caso contrário, o design de algoritmos pode ser intimidador (há '