Dividir e conquistar e recursão
Na ciência da computação, o método de dividir e conquistar é um algoritmo muito importante.
A interpretação literal é 分而治之
dividir um problema complexo em dois ou mais subproblemas idênticos ou semelhantes.
Até que o último subproblema possa ser resolvido diretamente, a solução do problema original é a combinação dos subproblemas.
A abordagem de dividir e conquistar geralmente usa recursão para resolver problemas.
1. Recursão
A recursão é chamar continuamente a própria função.
Por exemplo, encontramos o fatorial 1 * 2 * 3 * 4 * 5 *...* N
:
package main
import "fmt"
func Rescuvie(n int) int {
if n == 0 {
return 1
}
return n * Rescuvie(n-1)
}
func main() {
fmt.Println(Rescuvie(5))
}
Entrará repetidamente em uma função, seu processo é o seguinte:
Rescuvie(5)
{5 * Rescuvie(4)}
{5 * {4 * Rescuvie(3)}}
{5 * {4 * {3 * Rescuvie(2)}}}
{5 * {4 * {3 * {2 * Rescuvie(1)}}}}
{5 * {4 * {3 * {2 * 1}}}}
{5 * {4 * {3 * 2}}}
{5 * {4 * 6}}
{5 * 24}
120
A função se autodenomina constantemente e também é multiplicada por uma variável: n * Rescuvie(n-1)
Este é um processo recursivo.
É fácil ver que, como o tipo recursivo usa operadores, cada chamada repetida faz com que a cadeia de operações continue a se alongar e o sistema precisa usar a pilha para salvar e restaurar dados.
Se você precisar operar em uma cadeia cada vez mais longa, cada vez que recorrer, é extremamente lento e a pilha pode transbordar, causando a falha do programa.
Portanto, há outra maneira de escrever, chamada recursão da cauda:
package main
import "fmt"
func RescuvieTail(n int, a int) int {
if n == 1 {
return a
}
return RescuvieTail(n-1, a*n)
}
func main() {
fmt.Println(RescuvieTail(5, 1))
}
Seu processo recursivo é o seguinte:
RescuvieTail(5, 1)
RescuvieTail(4, 1*5)=RescuvieTail(4, 5)
RescuvieTail(3, 5*4)=RescuvieTail(3, 20)
RescuvieTail(2, 20*3)=RescuvieTail(2, 60)
RescuvieTail(1, 60*2)=RescuvieTail(1, 120)
120
Recursão de cauda significa que a função recursiva retorna seu valor diretamente após chamar a si mesma, sem adicionar operações, a eficiência será bastante aprimorada.
Se todas as chamadas recursivas em uma função aparecerem no final da função, chamamos essa função recursiva de recursiva de cauda. Quando a chamada recursiva é a última instrução executada em todo o corpo da função e seu valor de retorno não faz parte da expressão, a chamada recursiva é a recursão final. A característica das funções recursivas da cauda é que nenhuma operação é necessária durante o processo de regressão.Este recurso é muito importante porque a maioria dos compiladores modernos usará esse recurso para gerar automaticamente o código otimizado. -De Enciclopédia Baidu.
Como funções recursivas finais, alguns compiladores de linguagem de alto nível serão otimizados para reduzir a geração desnecessária de pilhas, para que a pilha do programa mantenha um número fixo de camadas e não haverá excesso de pilha.
Vamos dar vários exemplos para ilustrar.
2. Exemplo: sequência de Fibonacci
A sequência de Fibonacci refere-se a uma sequência na qual o último número é a soma dos dois primeiros números. Como segue:
1 1 2 3 5 8 13 21 ... N-1 N 2N-1
A solução para recursão de cauda é:
package main
import "fmt"
func F(n int, a1, a2 int) int {
if n == 0 {
return a1
}
return F(n-1, a2, a1+a2)
}
func main() {
fmt.Println(F(1, 1, 1))
fmt.Println(F(2, 1, 1))
fmt.Println(F(3, 1, 1))
fmt.Println(F(4, 1, 1))
fmt.Println(F(5, 1, 1))
}
Saída:
1
2
3
5
8
O n=5
processo recursivo atual é o seguinte:
F(5,1,1)
F(4,1,1+1)=F(4,1,2)
F(3,2,1+2)=F(3,2,3)
F(2,3,2+3)=F(2,3,5)
F(1,5,3+5)=F(1,5,8)
F(0,8,5+8)=F(0,8,13)
8
3. Exemplo: pesquisa binária
Encontre um determinado número em uma sequência que foi classificada, como:
1 5 9 15 81 89 123 189 333
Encontre o número da sequência ordenada acima 189
.
A idéia da pesquisa binária é 189
comparar a mediana da sequência ordenada primeiro com o número de destino e, se apenas coincidir com o alvo, terminar.
Se a mediana for maior que o número de destino, porque já está classificada, os números à direita da mediana são definitivamente maiores que o número de destino, procure-o à esquerda da mediana.
Se a mediana for menor que o número de destino, porque os números já estão classificados, os números à esquerda da mediana são definitivamente menores que o número de destino, procure-o à direita da mediana.
Esse tipo de divisão e conquista, a pesquisa dividida, é chamada de algoritmo de pesquisa binária.
Solução recursiva:
package main
import "fmt"
// 二分查找递归解法
func BinarySearch(array []int, target int, l, r int) int {
if l > r {
// 出界了,找不到
return -1
}
// 从中间开始找
mid := (l + r) / 2
middleNum := array[mid]
if middleNum == target {
return mid // 找到了
} else if middleNum > target {
// 中间的数比目标还大,从左边找
return BinarySearch(array, target, 1, mid-1)
} else {
// 中间的数比目标还小,从右边找
return BinarySearch(array, target, mid+1, r)
}
}
func main() {
array := []int{1, 5, 9, 15, 81, 89, 123, 189, 333}
target := 500
result := BinarySearch(array, target, 0, len(array)-1)
fmt.Println(target, result)
target = 189
result = BinarySearch(array, target, 0, len(array)-1)
fmt.Println(target, result)
}
Saída:
500 -1
189 7
Pode-se ver que 189
esse número está no subscrito 7
da sequência , mas 500
esse número não pode ser encontrado.
Obviamente, soluções recursivas podem ser convertidas em não recursivas, como:
package main
import "fmt"
// 二分查找非递归解法
func BinarySearch2(array []int, target int, l, r int) int {
ltemp := l
rtemp := r
for {
if ltemp > rtemp {
// 出界了,找不到
return -1
}
// 从中间开始找
mid := (ltemp + rtemp) / 2
middleNum := array[mid]
if middleNum == target {
return mid // 找到了
} else if middleNum > target {
// 中间的数比目标还大,从左边找
rtemp = mid - 1
} else {
// 中间的数比目标还小,从右边找
ltemp = mid + 1
}
}
}
func main() {
array := []int{1, 5, 9, 15, 81, 89, 123, 189, 333}
target := 500
result := BinarySearch2(array, target, 0, len(array)-1)
fmt.Println(target, result)
target = 189
result = BinarySearch2(array, target, 0, len(array)-1)
fmt.Println(target, result)
}
Muitos problemas de computador podem ser resolvidos usando a recursão.Em teoria, todos os métodos recursivos podem ser convertidos em métodos não recursivos, mas usando a recursão, o código é mais legível.
Artigo da série
Eu sou a estrela Chen, Bem-vindo eu ter escrito, pessoalmente, estruturas de dados e algoritmos (golang conseguir) , a partir do artigo para ler mais amigável GitBook .
- Estrutura de dados e algoritmo (implementação Golang) (1) Uma introdução simples ao Golang-Prefácio
- Estruturas e algoritmos de dados (implementação Golang) (2) Uma introdução simples aos pacotes, variáveis e funções Golang
- Estrutura de dados e algoritmo (implementação de Golang) (3) Uma introdução simples à declaração de controle de fluxo de Golang
- Estruturas de dados e algoritmos (implementação de Golang) (4) Uma introdução simples às estruturas e métodos de Golang
- Estrutura e algoritmo de dados (implementação Golang) (5) Uma introdução simples à interface Golang
- Estrutura e algoritmo de dados (implementação Golang) (6) Uma introdução simples à simultaneidade, corotinas e canais de Golang
- Estrutura e algoritmo de dados (implementação Golang) (7) Uma introdução simples à biblioteca padrão Golang
- Estrutura de dados e algoritmo (implementação de Golang) (8.1) Conhecimento básico-Prefácio
- Estrutura de dados e algoritmo (implementação de Golang) (8.2) Conhecimento básico - divisão, conquista e recursão
- Estrutura e algoritmo dos dados (implementação Golang) (9) Complexidade básica do algoritmo do conhecimento e símbolo progressivo
- Estrutura de dados e algoritmo (implementação de Golang) (10) Conhecimento básico - o principal método de complexidade de algoritmos
- Estruturas de dados e algoritmos (implementação de Golang) (11) Estruturas de dados comuns - Prefácio
- Estruturas e algoritmos de dados (implementação Golang) (12) Listas comuns vinculadas a estruturas de dados
- Estruturas e algoritmos de dados (implementação Golang) (13) Matrizes comuns de estruturas de comprimento variável
- Estruturas e algoritmos de dados (implementação de Golang) (14) Estruturas de dados comuns - pilha e fila
- Estruturas de dados e algoritmos (implementação de Golang) (15) Lista comum de estruturas de dados
- Estruturas de dados e algoritmos (implementação de Golang) (16) Dicionário de estruturas de dados comum
- Estruturas e algoritmos de dados (implementação de Golang) (17)
- Estrutura de dados e algoritmo (implementação de Golang) (18) Algoritmo de classificação-Prefácio
- Estrutura e algoritmo de dados (implementação Golang) (19) Classificação de algoritmos de classificação por bolhas
- Estrutura de dados e algoritmo (implementação Golang) (20) Classificação de seleção de algoritmo de classificação
- Estrutura e algoritmo de dados (implementação Golang) (21) Classificação de inserção de algoritmo de classificação
- Estrutura de dados e algoritmo (implementação de Golang) (22) Algoritmo de classificação-Classificação de Hill
- Estrutura e algoritmo de dados (implementação Golang) (23) Classificação de algoritmo de mesclagem
- Estrutura e algoritmo de dados (implementação Golang) (24) Classificação da fila de prioridade do algoritmo e classificação da pilha
- Estrutura e algoritmo de dados (implementação Golang) (25) Classificação rápida por algoritmo de classificação
- Estrutura de dados e algoritmo (implementação de Golang) (26) Tabela de algoritmos e hash de pesquisa
- Estrutura e algoritmo dos dados (implementação Golang) (27) Árvore de pesquisa binária do algoritmo de pesquisa
- Estrutura e algoritmo dos dados (implementação Golang) (28) Árvore de algoritmo de pesquisa-AVL
- Estrutura e algoritmo de dados (implementação de Golang) (29) Localização da árvore do algoritmo-2-3 e da árvore vermelha e preta inclinada para a esquerda
- Estrutura e algoritmo de dados (implementado por Golang) (30) Localização da árvore do algoritmo-2-3-4 e da árvore comum de vermelho-preto