Estrutura de dados e algoritmo (implementação de Golang) (8.2) Conhecimento básico - divisão, conquista e recursão

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=5processo 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 é 189comparar 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 189esse número está no subscrito 7da sequência , mas 500esse 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 .

Publicado 12 artigos originais · Curtidas0 · Visitas 90

Acho que você gosta

Origin blog.csdn.net/m0_46803965/article/details/105595077
Recomendado
Clasificación