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=5
proceso 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 189
comparar 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 189
este número está en el subíndice 7
de la secuencia , pero 500
este 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 .
- Estructura de datos y algoritmo (implementación de Golang) (1) Una introducción simple a Golang-Prefacio
- Estructuras de datos y algoritmos (implementación de Golang) (2) Una introducción simple a los paquetes, variables y funciones de Golang
- Estructura de datos y algoritmo (implementación de Golang) (3) Una introducción simple a la declaración de control de flujo de Golang
- Estructuras de datos y algoritmos (implementación de Golang) (4) Una introducción simple a las estructuras y métodos de Golang
- Estructura de datos y algoritmo (implementación de Golang) (5) Una introducción simple a la interfaz de Golang
- Estructura de datos y algoritmo (implementación de Golang) (6) Una introducción simple a la concurrencia, las rutinas y los canales de Golang
- Estructura de datos y algoritmo (implementación de Golang) (7) Una introducción simple a la biblioteca estándar de Golang
- Estructura de datos y algoritmo (implementación de Golang) (8.1) Conocimientos básicos-Prefacio
- Estructura de datos y algoritmo (implementación de Golang) (8.2) Conocimiento básico: dividir y conquistar y recurrir
- Estructura y algoritmo de datos (implementación de Golang) (9) Complejidad del algoritmo de conocimiento básico y símbolo progresivo
- Estructura de datos y algoritmo (implementación de Golang) (10) Conocimientos básicos: el método principal de complejidad del algoritmo
- Estructuras de datos y algoritmos (implementación de Golang) (11) Estructuras de datos comunes-Prefacio
- Estructuras de datos y algoritmos (implementación de Golang) (12) Listas enlazadas de estructuras de datos comunes
- Estructuras de datos y algoritmos (implementación de Golang) (13) Estructuras de datos comunes: matrices de longitud variable
- Estructuras de datos y algoritmos (implementación de Golang) (14) Estructuras de datos comunes: pila y cola
- Estructuras de datos y algoritmos (implementación de Golang) (15) Lista de estructuras de datos comunes
- Estructuras de datos y algoritmos (implementación de Golang) (16) Estructuras de datos comunes-Diccionario
- Estructuras de datos y algoritmos (implementación de Golang) (17) Estructuras de datos comunes: árboles
- Estructura de datos y algoritmo (implementación de Golang) (18) Algoritmo de clasificación-Prefacio
- Estructura de datos y algoritmo (implementación de Golang) (19) Algoritmo de clasificación-clasificación de burbujas
- Estructura de datos y algoritmo (implementación de Golang) (20) Clasificación de algoritmos de selección
- Estructura de datos y algoritmo (implementación de Golang) (21) Clasificación de algoritmo de inserción
- Estructura de datos y algoritmo (implementación de Golang) (22) Algoritmo de clasificación-Clasificación de Hill
- Estructura de datos y algoritmo (implementación de Golang) (23) Clasificación de algoritmo de fusión
- Estructura de datos y algoritmo (implementación de Golang) (24) Algoritmo de clasificación prioritario y clasificación de montón
- Estructura de datos y algoritmo (implementación de Golang) (25) Algoritmo de clasificación: clasificación rápida
- Estructura de datos y algoritmo (implementación de Golang) (26) Tabla de algoritmo de búsqueda de hash
- Estructura de datos y algoritmo (implementación de Golang) (27) Árbol de búsqueda binario de algoritmo de búsqueda
- Estructura y algoritmo de datos (implementación de Golang) (28) Árbol de algoritmo de búsqueda-AVL
- Estructura de datos y algoritmo (implementación de Golang) (29) Árbol de algoritmo de búsqueda-2-3 y árbol rojo-negro inclinado a la izquierda
- Estructura de datos y algoritmo (implementado por Golang) (30) Árbol de algoritmo de búsqueda-2-3-4 y árbol rojo-negro ordinario