[Notas do algoritmo] DP de programação dinâmica, descrição do algoritmo

[Notas de algoritmo] DP de programação dinâmica

Programaçao dinamica

Acredito que todo mundo usa a recursão extensivamente na implementação do código, mas a recursão pura, embora a implementação seja muito simples, mas sabemos que no algoritmo, na maioria das vezes, não pode ter as duas. A recursão é uma chamada de função a si mesma, e neste momento é necessário Em termos de tempo e espaço, cada chamada de função será alocada na pilha de memória para salvar variáveis ​​temporárias, salvar parâmetros e endereços de retorno. Além disso, a recursão frequentemente tem etapas de cálculo repetidas. Finalmente, muitas chamadas podem transbordar facilmente.

A programação dinâmica resolve muito o problema de chamadas repetidas. É significativamente diferente da estratégia de dividir e conquistar (Dividir e conquistar) porque os subproblemas da programação dinâmica não são completamente sobrepostos. Na verdade, a programação dinâmica e os algoritmos de passagem são muito semelhantes .Não há nada de soberbo na ideia de ??, mas para os cálculos salvos que foram feitos, os resultados podem ser calculados rapidamente quando repetidas chamadas, economizando tempo. Vale ressaltar que todos os problemas que podem ser resolvidos usando algoritmos gulosos podem ser resolvidos por programação dinâmica. Muitos algoritmos específicos têm a sombra da programação dinâmica, como o algoritmo Floyd-Warshall de caminho mais curto (algoritmo Floyd-Warshall)

Definição:

Um algoritmo que define a relação entre o estado do problema e o estado dividindo o problema, de forma que o problema possa ser resolvido de maneira recursiva (ou dividir para conquistar) [1]

Obviamente, esta definição não é a ideal. Chamamos os subproblemas recorrentes de subproblemas de sobreposição (subproblemas de sobreposição). Então, sua definição pode ser: dividir um problema em vários subproblemas sobrepostos e, em seguida, combinar as soluções dos pequenos problemas para obter a solução do grande problema.

A programação dinâmica é amplamente utilizada nos campos da ciência da computação (IA, gráficos etc), teoria da informação, cibernética, etc.

Etapa

Métodos de programação dinâmica geralmente são usados ​​para resolver problemas de otimização. Este tipo de problema pode ter várias soluções viáveis, e cada solução tem um valor, esperamos encontrar a solução com o valor ótimo (valor máximo ou valor ótimo). Chamamos tal solução de solução ótima do problema (uma
solução ótima), não a solução ótima (a solução ótima), porque pode haver várias soluções que alcançam o valor ótimo [2. Thomas H. Cormen, Charles E. Leiserson , Ronald L. Rivest e Clifford Stein, 06 2019, China Machine Press, 204]

1. Caracterize as características estruturais de uma solução ótima
2. Defina recursivamente o valor
da solução ótima 3. Calcule o valor da solução ótima
4. Use as informações calculadas para construir uma solução ótima Os
três primeiros passos são a base para a programação dinâmica para resolver o problema, se precisarmos apenas do valor da solução ótima em vez da própria solução, então podemos ignorar a quarta etapa

Exemplo

1. Sequência de Fibonacci
Observe o seguinte código

double F(int n){
	return (n<=1)?1:F(n-1)+F(n-2)
}

O tempo de execução é mostrado na

Figura 1

Calcule T (n) = O (2 ^ n)
Se precisarmos calcular F (44) , precisamos calcular F (43) e F (42) , e para calcular F (43) , precisamos calcular F ( 42) , quando quando o número na função torna-se mais pequeno, a situação torna-se mais incontrolável.
Insira a descrição da imagem aqui
a Figura 2

Claro que não precisamos calcular * F (10) * quase dez milhões de vezes, então precisamos resolver este problema.
Existe uma estratégia viável:
· Para evitar o cálculo duplo, armazene a quantidade de cálculo intermediário em a tabela (memorização)
· Lembramos Os valores desses cálculos são as próximas chamadas repetidas possíveis.
No processo de programação dinâmica para resolver o problema ideal, a solução top-down pode precisar obter repetidamente a solução ideal para o mesmo sub-problema. É aqui também que é significativamente diferente do algoritmo de divisão e conquista, porque os subproblemas do algoritmo de divisão e conquista são completamente diferentes, então o efeito é melhor.

#define MAX 100
int d[MAX]
int F(int n){
    
    
	if(n<=2)
	{
    
    
		return 1;
	}
	else
	{
    
    
		d[n-1]=F(n-1);
		d[n-2]=F(n-2);
		d[n]=d[n-1]+d[n-2];
		return d[n]
	}
}
//也可以用循环的方式
}

2. Programação de intervalo
· Curso j começa em s [j] e termina em f [j], e o grau de importância é w [j]> 0;
· Quando dois cursos não se sobrepõem, esses dois cursos são chamados de É compatível .
Objetivo: Encontrar um conjunto de percursos compatíveis com o maior peso.
Insira a descrição da imagem aqui
Figura 3

Vamos do mais raso para o mais profundo, e o método de força mais bruta (força bruta) é, claro, tirar todo o arranjo de cada curso e ver qual peso é o maior. No entanto, isso obviamente tem muito espaço para melhorias, por exemplo, deve haver muitas sobreposições na disposição desses cursos. Então o que fazer?
Quando os pesos são todos 1, você pode usar o algoritmo guloso para classificar diretamente de acordo com o horário de término e começar de trás. No entanto, pesos diferentes são adicionados aqui, portanto, quanto mais cursos incluídos, não significa necessariamente que o peso final e o máximo sejam obtidos.
Neste momento, o DP é útil. A ideia é semelhante ao método acima, mas uma matriz p precisa ser adicionado.

p [j] = o maior curso compatível com o curso j que é menor que j
. Na figura abaixo, p [2] = 0, p [7] = 3
Insira a descrição da imagem aqui
Figura 4

Apresentamos mais completamente
Def. OPT (j) = qualquer subconjunto compatível de cursos com a maior soma de peso, contendo apenas os cursos 1, 2, ..., j
Objetivo. OPT (n) = com a maior soma de peso O compatível mutuamente conjunto de cursos
Caso 1.OPT (j) não contém o curso j.Assim,
sua solução é conter apenas o peso máximo dos cursos 1, 2, ..., j e são compatíveis entre si.

Caso 2. OPT (j) contém curso j
· Adicionar peso w [j]
· Não contém cursos incompatíveis {p [j] + 1,…, j-1}
· Deve conter 1, 2, ..., p ( j) Solução ótima

Após a análise acima, ficou muito claro? A fórmula de Bellman para programação dinâmica também apareceu na
Insira a descrição da imagem aqui
Figura 5

Vamos analisar a complexidade (complexidade) do algoritmo.
Observe que a cada iteração, o pior é OPT (j-1), portanto, no processo de programação dinâmica, ele roda no máximo 2n vezes, que é O (n) e p [j ] A aquisição é obtida classificando os intervalos de curso fornecidos de acordo com o tempo final e contando de trás para frente.A complexidade total é O (nlogn), e a complexidade total do algoritmo é O (nlogn).

3. Ajuste de curva multiponto
Deixe-me dar outro exemplo que acho mais interessante. Acredito que todos usarão o ajuste de curva ao fazer o processamento de dados, que faz parte do nosso processo de pesquisa, mas quando os pontos que aparecem estão dispostos em diferentes Quando o intervalo muda muito, não há como ajustar uma curva. A seguir, usamos a programação dinâmica para resolver esse problema.
Primeiro, olhe para o problema mais básico:

Mínimos quadrados
n pontos (x1, y1), (x2, y2), ..., (xn, yn) em um determinado plano. Encontre uma linha y = ax + b para caber.
O problema mais comum é usar o método dos mínimos quadrados.
Insira a descrição da imagem aqui
Figura 6
!] (https://img-blog.csdnimg.cn/20210110134349420.png)

Figura 7

Mínimos quadrados segmentados
· Dados n pontos (x1, y1),…, (xn, yn), x1 <x2 <… <xn, encontre um conjunto de curvas para minimizar o erro de f (x)
Insira a descrição da imagem aqui
Figura 8
f (x) = E + cL
E: a soma dos erros quadrados de cada linha reta
L: o número de linhas retas
OPT (j) = o consumo mínimo para os pontos p1, ..., pj
eij = pi, ..., SSE de pj
então cada vez Custo = eij + c + OPT (i-1)
finalmente obtém a equação de Bellman
Insira a descrição da imagem aqui
Figura 9

Amostra

Todos os algoritmos precisam dos três componentes a seguir: ideia principal, prova de correção e análise de tempo de execução

1. A ideia principal do algoritmo. Você deve transmitir corretamente a ideia do algoritmo nesta seção. Não é necessário fornecer todos os detalhes da solução ou o motivo correto. Por exemplo, em um problema de programação dinâmica, você deve nos dizer os subproblemas, condições básicas e fórmulas recursivas nesta parte. Você também pode usar pseudo-código para torná-lo mais claro

2. Prova da correção do algoritmo. Não importa qual seja a entrada, você deve provar que seu algoritmo está correto. Para loops e algoritmos iterativos, o método usual é procurar invariantes. Um invariante de loop precisa seguir os três princípios a seguir:
1) Está logo antes da primeira iteração e do loop
2) Se estiver logo antes da i-ésima iteração, então também deve estar logo antes da i + 1ª vez Sim
3 ) Se estiver
correto após a última iteração ou loop, então sua saída está correta como uma prova. Esta invariante precisa ser descrita cuidadosamente pelo sistema. Se a correção for provada como correta na classe, então você não tem para provar sua correção todas as vezes

3. Análise de complexidade, geralmente análise de complexidade de tempo, marcada com O (·). E provar porque essa complexidade

Dito isso, vamos dar um exemplo. Este exemplo vem da minha lição de casa.
Dado um conjunto de inteiros não negativos de comprimento N e um valor W, julgue se há um subconjunto neste conjunto de números, de modo que a soma dos subconjuntos é igual a W?
1. Idéia central: deixe S denotar este conjunto de inteiros não negativos de entrada, construímos um array bidimensional do tipo booleano dp [] [] para realizar a programação dinâmica, dp [i] [j ] indica se há i antes de incluir S O subconjunto de elementos e a soma do subconjunto é j, queremos saber dp [N-1] [W]. Inicializamos primeiro, quando j = 0, dp [i] [j] = verdadeiro, a fórmula de Bellman é a
Insira a descrição da imagem aqui
Figura 10

2. Correção
Obviamente, uma vez que o conjunto vazio é um subconjunto de todos os conjuntos, quando j = 0, dp [i] [j] = verdadeiro. Para dp [i] [j], se o i-ésimo elemento for maior que j, então esse subconjunto não existe na resposta final, dp [i] [j] = dp [i-1] [j]. Caso contrário, há dois casos. Se a resposta não contém o i-ésimo elemento, então dp [i] [j] = dp [i-1] [j]. Se a resposta contém o i-ésimo elemento, então o subconjunto e reduz, dp [i] [j] = dp [i-1] [jS [i]]. É correto satisfazer qualquer uma das duas condições acima d [i] [j]

3. Análise de complexidade
Porque existem duas camadas de loops, uma das quais é o comprimento do conjunto e a outra é o valor W, então a complexidade é θ (NW)

Oh, estou cansado de traduzir meu dever de casa, vamos mostrar a todos minha resposta.
Insira a descrição da imagem aqui
Figura 11

Resumindo

Em geral, a ideia da programação dinâmica é muito simples, ou seja, salvar os valores calculados anteriormente, economizando tempo para cálculos repetidos. Dividido em três etapas, defina as subquestões, divida a questão principal em subquestões e determine a ordem das subquestões pequenas a grandes que podem fazer com que a resposta à subquestão pequena constitua a resposta à subquestão maior pergunta. O método de análise também é muito simples, como escolher um dos dois, escolher mais um e adicionar o novo parâmetro blablabla (demorei quase seis horas para escrever um blog com o qual estou bastante satisfeito. Realmente não o imaginaria se não fosse por um feriado)

Acho que você gosta

Origin blog.csdn.net/Cplus_ruler/article/details/112414247
Recomendado
Clasificación