Estructura de datos y algoritmo (implementación de Golang) (8.2) Conocimiento básico: dividir y conquistar y recurrir

Divide y vencerás y recurrirás

En informática, el método de divide y vencerás es un algoritmo muy importante.

La interpretación literal es 分而治之dividir un problema complejo en dos o más subproblemas idénticos o similares.

Hasta que el último subproblema pueda resolverse directamente, la solución del problema original es la combinación de los subproblemas.

El enfoque de divide y vencerás generalmente usa la recursividad para resolver problemas.

1. Recursion

La recursión es llamar continuamente a la función misma.

Por ejemplo, encontramos el factorial 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))
}

Ingresará repetidamente una función, su proceso es el siguiente:

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

La función se llama a sí misma constantemente, y también se multiplica por una variable: n * Rescuvie(n-1)este es un proceso recursivo.

Es fácil ver que debido a que el tipo recursivo usa operadores, cada llamada repetida hace que la cadena de operaciones continúe alargándose, y el sistema tiene que usar la pila para guardar y restaurar datos.

Si necesita operar en una cadena cada vez más larga cada vez que recurre, es extremadamente lento y la pila puede desbordarse, causando que el programa se bloquee.

Entonces, hay otra forma de escribir, llamada recursión de cola:

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))
}

Su proceso recursivo es el siguiente:

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

La recursividad de cola significa que la función recursiva devuelve directamente su valor después de llamarse a sí misma, sin agregarle operaciones, la eficiencia mejorará enormemente.

Si todas las llamadas recursivas en una función aparecen al final de la función, llamamos a esta función recursiva tail-recursive. Cuando la llamada recursiva es la última instrucción ejecutada en todo el cuerpo de la función y su valor de retorno no es parte de la expresión, la llamada recursiva es la recursividad de cola. La característica de las funciones recursivas de cola es que no se requiere ninguna operación durante el proceso de regresión, esta característica es muy importante porque la mayoría de los compiladores modernos usarán esta función para generar automáticamente código optimizado. -De la Enciclopedia Baidu.

Funciones recursivas de la cola, algunos compiladores de lenguaje de alto nivel se optimizarán para reducir la generación innecesaria de la pila, de modo que la pila del programa mantenga un número fijo de capas, y no habrá desbordamiento de la pila.

Daremos varios ejemplos para ilustrar.

2. Ejemplo: secuencia de Fibonacci

La secuencia de Fibonacci se refiere a una secuencia en la que el último número es la suma de los dos primeros números. Como sigue:

1 1 2 3 5 8 13 21 ... N-1 N 2N-1

La solución a la recursividad de la cola es:

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))
}

Salida:

1
2
3
5
8

El n=5proceso recursivo actual es el siguiente:

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. Ejemplo: búsqueda binaria

Encuentre un cierto número en una secuencia que se ha ordenado, como:

1 5 9 15 81 89 123 189 333

Encuentra el número de la secuencia ordenada arriba 189.

La idea de la búsqueda binaria es 189comparar primero la mediana de la secuencia ordenada con el número objetivo y, si solo coincide con el objetivo, finalizar.

Si la mediana es más grande que el número objetivo, porque ya está ordenada, entonces los números a la derecha de la mediana son definitivamente más grandes que el número objetivo, entonces búsquelo desde la izquierda de la mediana.

Si la mediana es más pequeña que el número objetivo, debido a que los números ya están ordenados, los números a la izquierda de la mediana son definitivamente más pequeños que el número objetivo, luego búsquelo desde la derecha de la mediana.

Este tipo de divide y vencerás, la búsqueda dividida se llama algoritmo de búsqueda binaria.

Solución 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)
}

Salida:

500 -1
189 7

Se puede ver que 189este número está en el subíndice 7de la secuencia , pero 500este número no se puede encontrar.

Por supuesto, las soluciones recursivas se pueden convertir en no 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)
}

Muchos problemas de la computadora pueden resolverse usando la recursividad. En teoría, todos los métodos recursivos pueden convertirse en métodos no recursivos, pero usando la recursividad, el código es más legible.

Entrada de artículo de serie

Soy la estrella Chen, bienvenido he escrito personalmente estructuras de datos y algoritmos (Golang lograr) , comenzando en el artículo para leer más amigable GitBook .

12 artículos originales publicados · Me gusta0 · Visitas 90

Supongo que te gusta

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