Introdução às notas de estudo de algoritmo Capítulo 4 Estratégia de divisão e conquista

Na estratégia de dividir e conquistar, resolvemos um problema recursivamente, e as três etapas a seguir são aplicadas em cada nível de recursão:
1. Decomposição. Divida o problema em alguns subproblemas, a forma dos subproblemas é a mesma do problema original, mas em uma escala menor.
2. Resolva. Resolva os subproblemas recursivamente. Se o tamanho dos subproblemas for pequeno o suficiente, pare a recursão e resolva diretamente.
3. Mesclar. Combine as soluções dos subproblemas na solução do problema original.

Quando o subproblema é grande o suficiente para ser resolvido recursivamente, o chamamos de caso recursivo. Quando o subproblema se torna pequeno o suficiente para que a recursão não seja mais necessária, dizemos que a recursão atingiu o fundo e entrou na situação básica.

Expressões recursivas podem ter muitas formas. Um algoritmo recursivo pode dividir o problema em subproblemas de tamanhos variados, como a divisão de 1/3 e 2/3. E o tamanho do subproblema não precisa ser uma proporção fixa do problema original.

Em aplicações práticas, iremos ignorar alguns detalhes técnicos de declaração recursiva e solução, como chamar MERGE-SORT em n elementos. Quando n é um número ímpar, as escalas dos dois subproblemas são n / 2 arredondado para cima e para baixo, respectivamente inteiro, não exatamente n / 2. As condições de contorno são outro tipo de detalhes que geralmente são esquecidos. Quando n é pequeno o suficiente, o tempo de execução do algoritmo é θ (1), mas não alteramos a fórmula recursiva de que n é pequeno o suficiente para encontrar T (n), porque estes as alterações não excederão um fator constante, portanto, a ordem de crescimento da função não será alterada.

O investimento em ações só pode ser comprado e vendido uma vez, a fim de maximizar o retorno, ou seja, comprar pelo preço mais baixo e vender pelo preço mais alto, mas o preço mais baixo pode aparecer depois do preço mais alto:!
[Insira a descrição da imagem aqui] (https: // img- blog.csdnimg.cn/20210320124004302.png?x-oss- processo = image / marca d'água, type_ZmFuZ3poZW5naGVpdGk, shadow_10, text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3R1czAwt_FF, quando
você pode vender ao preço mais elevado, ou você acha que pode vender no preço mais alto ou pelo preço mais baixo que você acha que pode maximizar a receita. Se essa estratégia for eficaz, é muito simples determinar a receita máxima: procure o preço mais alto e o mais baixo e comece do preço mais alto até à esquerda para encontrar o preço mais baixo à esquerda e começar do preço mais baixo à direita para encontrar o preço mais alto certo de e, em seguida, pegue os dois pares de preços com a maior diferença, mas a figura a seguir é um contra exemplo: o
Insira a descrição da imagem aqui
problema acima pode ser resolvido simplesmente pela violência, tente todas as combinações possíveis de datas de compra e venda, desde que a data de venda seja na data de compra após a lata, um total de n(n-1)/2tipos de combinação de data de n dias , e o o tempo de processamento gasto para cada data é pelo menos constante, portanto, este método de tempo operacional Ω (n²).

Podemos examinar as mudanças diárias de preço. A mudança de preço no i-ésimo dia é definida como a diferença de preço entre o i-ésimo dia e o i-1 ° dia. Então o problema se torna encontrar a maior submatriz contínua não vazia , chamada de submatriz Array é a maior submatriz: O
Insira a descrição da imagem aqui
maior problema de submatriz é significativo apenas quando a matriz contém números negativos. Se todos os elementos da matriz forem não negativos, a maior submatriz é a soma de todos variedade.

Considere usar a tecnologia de divisão e conquista para resolver o maior problema de submatriz. Usar a tecnologia de divisão e conquista significa que temos que dividir a submatriz em duas submatrizes do mesmo tamanho possível, ou seja, encontrar a posição central no meio da submatriz e, em seguida, considere resolver as duas submatrizes A [baixo ... médio] e A [médio + 1 ... alto], a posição de qualquer submatriz contínua A [i ... j] de A [baixo… alto] deve ser uma das três situações a seguir:
1. Totalmente localizado na submatriz In A [baixo… médio], neste momento baixo <= i <= j <= médio.
2. Ele está completamente localizado no submatriz A [mid + 1… high], neste momento mid <i <= j <= high.
3. Cruze o ponto médio, neste momento baixo <= i <= médio <= j <= alto.

Na verdade, uma maior submatriz de A [baixo… alto] deve estar completamente localizada em A [baixo… médio], completamente localizada em A [médio + 1… alto], e a maior de todas as submatrizes no ponto médio , nós É possível resolver recursivamente as maiores submatrizes que estão completamente localizadas em ambos os lados, então todo o trabalho restante é encontrar a maior submatriz que abrange o ponto médio e, em seguida, escolher a maior soma nestes três casos:
Insira a descrição da imagem aqui
encontre a maior submatriz que abrange o ponto médio:

FIND-MAX-CROSSING-SUBARRAY(A, low, mid, high)
	left-sum = -∞
	sum = 0
	for i = mid down to low
		sum = sum + A[i]
		if sum > left-sum
			left-sum = sum
			max-left = i
	right-sum = -∞
	sum = 0
	for j = mid + 1 to high
		sum = sum + A[j]
		if sum > right-sum
			right-sum = sum
			max-right = j
	return (max-left, max-right, left-sum + right-sum)

O processo acima leva tempo θ (n).

Com o pseudocódigo de tempo linear acima, você pode escrever o seguinte pseudocódigo do algoritmo de divisão e conquista para resolver o maior problema de subarray:

FIND-MAXIMUM-SUBARRAY(A, low, high)
	if high == low    // base case: only one element
		return (low, high, A[low])
	else 
		mid = floor((low + high) / 2)    // 向下取整
		(left-low, left-high, left-sum) = FIND-MAXIMUM-SUBARRAY(A, low, mid)
		(right-low, right-high, right-sum) = FIND-MAXIMUM-SUBARRAY(A, mid + 1, high)
		(cross-low, cross-high, cross-sum) = FIND-MAX-CROSSING-SUBARRAY(A, low, mid, high)
		if left-sum >= right-sum and left-sum >= cross-sum
			return (left-low, left-high, left-sum)
		elseif right-sum >= left-sum and right-sum >= cross-sum
			return (right-low, right-high, right-sum)
		else 
			return (cross-low, cross-high, cross-sum)

A complexidade de tempo dessa solução é θ (nlgn).

Implementação em C ++ do processo acima:

#include <iostream>
#include <vector>
using namespace std;

int findMaxCrossSubarray(const vector<int> &nums, size_t start, size_t mid, size_t end) {
    
    
	int leftSum = 0;
	int sum = 0;
	for (size_t i = mid + 1; i > start; --i) {
    
    
		sum += nums[i - 1];
		if (sum > leftSum) {
    
    
			leftSum = sum;
		}
	}

	int rightSum = 0;
	sum = 0;
	for (size_t i = mid + 1; i <= end; ++i) {
    
    
		sum += nums[i];
		if (sum > rightSum) {
    
    
			rightSum = sum;
		}
	}

	return leftSum + rightSum;
}

int findMaximumSubarray(const vector<int>& nums, size_t start, size_t end) {
    
    
	if (start == end) {
    
    
		return nums[start];
	}

	size_t mid = (start + end) >> 1;
	int leftMax = findMaximumSubarray(nums, start, mid);
	int rightMax = findMaximumSubarray(nums, mid + 1, end);
	int crossMax = findMaxCrossSubarray(nums, start, mid, end);

	return max(leftMax, max(rightMax, crossMax));
}

int main() {
    
    
	vector<int> ivec = {
    
     13,-3,-25,20,-3,-16,-23,18,20,-7,12,-5,-22,15,-4,7 };
	cout << findMaximumSubarray(ivec, 0, ivec.size() - 1);
}

Encontre a maior submatriz não recursiva e linearmente, e a complexidade de tempo é O (n):

#include <iostream>
#include <vector>
#include <limits>
using namespace std;

int findMaximumSubarray(const vector<int>& nums, size_t start, size_t end) {
    
    
	int sumMax = numeric_limits<int>::min();
	int curSum = 0;
	for (size_t i = 0; i < nums.size(); ++i) {
    
    
		curSum += nums[i];
		if (curSum > sumMax) {
    
    
			sumMax = curSum;
		}

		if (curSum < 0) {
    
    
			curSum = 0;
		}
	}
	return sumMax;
}

int main() {
    
    
	vector<int> ivec = {
    
     13,-3,-25,20,-3,-16,-23,18,20,-7,12,-5,-22,15,-4,7 };
	cout << findMaximumSubarray(ivec, 0, ivec.size() - 1);
}

Pseudocódigo de multiplicação de matriz:

SQUARE-MATRIX-MULTIPLY(A, B)
	n = A.rows
	let C be a new nXn matrix
	for i = 1 to n
		for j = 1 to n
			cij = 0
				for k = 1 to n
					cij = cij + aik * bkj
	return C

A multiplicação de matrizes não leva necessariamente Ω (n³) tempo. Mesmo a definição natural da multiplicação de matrizes requer tantas multiplicações escalares. Existe o algoritmo recursivo de multiplicação de matrizes de Strassen, que tem uma complexidade de tempo de O (n 2,81 ).

Para simplicidade, suponha que a matriz é nxn, em que n é uma potência de 2, e então dividir a matriz nxn em 4 sub-matrizes de n / 2xN / 2 As propriedades de uma operação de matriz são as seguintes:. O
Insira a descrição da imagem aqui
seguinte fórmula é equivalente à fórmula acima: de
Insira a descrição da imagem aqui
acordo com o acima O processo pode escrever pseudo código:

SQUARE-MATRIX-MULTIPLY-RECURSIVE(A, B)
	n = A.rows
	let C be a new nXn matrix
	if n == 1
		c11 = a11 * b11
	else
		C11 = SQUARE-MATRIX-MULTIPLY-RECURSIVE(A11, B11) + SQUARE-MATRIX-MULTIPLY-RECURSIVE(A12, B21)
		C12 = SQUARE-MATRIX-MULTIPLY-RECURSIVE(A11, B12) + SQUARE-MATRIX-MULTIPLY-RECURSIVE(A12, B22)
		C21 = SQUARE-MATRIX-MULTIPLY-RECURSIVE(A21, B11) + SQUARE-MATRIX-MULTIPLY-RECURSIVE(A22, B21)
		C22 = SQUARE-MATRIX-MULTIPLY-RECURSIVE(A21, B12) + SQUARE-MATRIX-MULTIPLY-RECURSIVE(A22, B22)
	return C

O código acima esconde um detalhe sutil, mas importante de como decompor a matriz. Se realmente criarmos 12 novas matrizes n / 2Xn / 2, levará θ (n²) tempo para copiar os elementos da matriz. Na verdade, podemos passar o subscrito Para especificar uma sub-matriz.

No processo acima, a quinta linha leva θ (1) tempo para calcular a matriz de decomposição e, em seguida, SQUARE-MATRIX-MULTIPLY-RECURSIVE é chamada oito vezes. Cada chamada completa a multiplicação de duas matrizes n / 2Xn / 2, então o o tempo total gasto é 8T (N / 2). Neste processo, leva tempo θ (n²) para chamar a adição da matriz 4 vezes, então o tempo total do caso recursivo é:
Insira a descrição da imagem aqui
se a decomposição da matriz é realizada copiando os elementos , o custo necessário é θ (n²), O tempo total de execução será aumentado por um fator constante e T (n) permanecerá inalterado. Portanto, a fórmula do tempo de execução é a seguinte:
Insira a descrição da imagem aqui
Este método T (n) = θ (n³), então o algoritmo simples de divisão e conquista não é melhor do que o método direto.

Na fórmula de T (n), θ (n²) realmente omite os coeficientes constantes antes de n², porque o símbolo θ já contém todos os coeficientes constantes, mas o 8 na fórmula 8T (n / 2) não pode ser omitido, porque 8 representa um árvore recursiva Cada nó tem vários nós filhos, o que determina quantos itens cada camada da árvore contribui para a soma.Se 8 for omitido, a árvore recursiva torna-se uma estrutura linear.

A ideia central do algoritmo de Strassen é tornar a árvore recursiva um pouco menos exuberante, ou seja, apenas executar recursivamente sete vezes em vez de oito multiplicações de matriz n / 2Xn / 2. O custo de reduzir uma multiplicação de matriz pode ser um adicional número de n / 2Xn / 2 Adição de matriz, mas apenas tempos constantes. O algoritmo inclui as seguintes etapas. As etapas 2 a 4 explicarão as etapas específicas posteriormente:
1. Decomponha a matriz de acordo com o índice. O método é o mesmo que o método recursivo comum, e leva θ (1) tempo.
2. Crie 10 matrizes n / 2Xn / 2, cada matriz salva a soma ou diferença das duas submatrizes criadas na etapa 1 e leva tempo θ (n²).
3. Usando a submatriz criada na etapa 1 e as 10 matrizes criadas na etapa 2, calcule recursivamente 7 produtos de matriz e o resultado de cada produto de matriz é n / 2Xn / 2.
4. Calcule C11, C12, C21 e C22 da matriz de resultado C com base no resultado do produto de matriz na etapa 3.

As etapas 1, 2 e 4 levam um total de θ (n²) tempo, e a etapa 3 requer 7 vezes n / 2Xn / 2 multiplicações de matrizes, de modo que o tempo de execução T (n) do algoritmo de Strassen é obtido:
Insira a descrição da imagem aqui
T (n) = θ (n) Ig7 ), Ig7 está entre 2,80 e 2,81.

Na etapa 2 do algoritmo de Strassen, as seguintes 10 matrizes são criadas: As
Insira a descrição da imagem aqui
Insira a descrição da imagem aqui
etapas acima calculam 10 adições e subtrações de matrizes n / 2Xn / 2, o que leva θ (n²) tempo.

Na etapa 3, calcule recursivamente a multiplicação da matriz n / 2Xn / 2 7 vezes: na
Insira a descrição da imagem aqui
fórmula acima, apenas a multiplicação na coluna do meio precisa ser realmente calculada, e a coluna da direita apenas ilustra a relação entre esses produtos e o submatriz criada na etapa 2.

Etapa 4 Realize operações de adição e subtração na matriz criada na Etapa 3:
Insira a descrição da imagem aqui
Expanda o lado direito da fórmula acima:
Insira a descrição da imagem aqui
e C12 é igual a:
Insira a descrição da imagem aqui
C21 é igual a:
Insira a descrição da imagem aqui
C22 é igual a:
Insira a descrição da imagem aqui
Método de substituição para resolver a fórmula recursiva:
1. Adivinhe a forma da solução.
2. Use a indução matemática para encontrar as constantes na solução e provar que a solução está correta.

O método de substituição pode ser usado para encontrar o limite superior da seguinte fórmula recursiva:
Insira a descrição da imagem aqui
nós achamos que a solução é O (nlgn), e o método de substituição requer prova de que se a constante c> 0 for selecionada apropriadamente, pode haver T (n ) <= cnlgn. O inteiro m <n é totalmente verdadeiro, especialmente para m = ⌊n / 2⌋, há T (⌊n / 2⌋) <= c⌊n / 2⌋lg (⌊n / 2⌋) , que é substituído na fórmula recursiva, Get:
Insira a descrição da imagem aqui
Entre eles, desde que c> = 1, a última etapa será estabelecida. O método de indução matemática requer prova de que a solução também é válida sob as condições de contorno. Suponha que T (1) = 1 é a única condição de contorno (condição inicial) da fórmula recursiva. Para n = 1, a condição de contorno T (n ) <= cnlgn deriva T (1) <= c1lg1 = 0, o que contradiz T (1) = 1. Neste momento, n pode ser estendido, tomando n = 2 como a condição de contorno, e T (2) = 4 e T (3) = pode ser calculado a partir da fórmula recursiva 5. Neste momento, a prova indutiva pode ser concluída: para uma certa constante c> = 1, T (n) <= cnlgn, o método é escolher um grande o suficiente c para satisfazer T (2) <= c2lg2 e T (3) <= c3lg3.

Mas o método de substituição não tem um método universal para adivinhar a solução recursiva correta. Adivinhar a solução requer experiência e, ocasionalmente, criatividade. Você pode pegar emprestada uma árvore recursiva para fazer uma boa estimativa. Se a fórmula recursiva for semelhante à fórmula recursiva que você viu, é razoável adivinhar uma solução semelhante, como a seguinte fórmula recursiva:
Insira a descrição da imagem aqui
Comparado com o exemplo acima, é apenas +17. Quando n é grande, é próximo para n Metade, então suponha que T (n) = O (nlgn), esta estimativa está correta.

Outro bom método de adivinhação é provar os limites superior e inferior soltos da fórmula recursiva primeiro e, em seguida, reduzir o intervalo de incerteza. Como no exemplo acima, você pode começar a partir do limite inferior Ω (n), porque a fórmula recursiva contém o termo n. Podemos provar um limite superior O (n²) e, em seguida, diminuir gradualmente o limite superior e aumentar o limite inferior até que convirja para o limite assintoticamente compacto θ (nlgn).

Às vezes, o limite assintótico é adivinhado, mas a prova indutiva falha. Nesse momento, modifique a suposição e subtraia um termo de ordem inferior dela. A prova matemática pode frequentemente prosseguir suavemente, como a seguinte fórmula recursiva:
Insira a descrição da imagem aqui
achamos que a solução é T (n) = O (N), e tente provar que para uma constante adequada c, T (n) <= cn é verdadeiro, substitua a suposição na fórmula recursiva e obtenha:
Insira a descrição da imagem aqui
Mas isso não significa que para qualquer c, T (n) <cn, Podemos adivinhar um limite maior, como T (n) = O (n²), embora o resultado possa ser inferido, o T (n) = O (n) original está correto.

Fazemos uma nova suposição: T (n) <= cn-d, d é uma constante ≥0, e agora há:
Insira a descrição da imagem aqui
enquanto d≥1, esta fórmula é válida, então, como antes, escolha um c grande o suficiente para lidar com a condição de contorno.

Você pode descobrir que a ideia de subtrair um termo de ordem inferior é contra-intuitiva. Afinal, se o limite superior não for bem-sucedido, a estimativa deve ser aumentada em vez de diminuída, mas um limite mais flexível não é necessariamente mais fácil de provar, porque para provar um limite mais fraco Para o limite superior, o mesmo limite mais fraco deve ser usado na prova indutiva.

No exemplo acima, podemos ter a seguinte prova errada:
Insira a descrição da imagem aqui
porque c é uma constante, o erro é que não provamos uma forma que seja estritamente consistente com a hipótese de indução, ou seja, T (n) ≤cn.

O resto é muito difícil de aprender, talvez mais tarde

Acho que você gosta

Origin blog.csdn.net/tus00000/article/details/114990966
Recomendado
Clasificación