[Estructura de datos e implementación del algoritmo C++] 2. Búsqueda binaria y recursividad simple

El video original es la enseñanza de la estación B de Zuo Chengyun.



1 dicotomía

La búsqueda binaria es un algoritmo de búsqueda para encontrar un elemento específico en una matriz ordenada . Su idea básica es dividir la matriz desde el medio y luego juzgar la relación de tamaño entre el elemento de destino y el elemento del medio para determinar si el elemento de destino está en la mitad izquierda o en la mitad derecha. Luego, continúe realizando la misma operación en el subarreglo correspondiente hasta que se encuentre el elemento de destino o se determine que el elemento de destino no existe.

Los pasos específicos son los siguientes:

  • Establece el límite izquierdo de la matriz a la izquierda y el límite derecho a la derecha.
  • Calcula la posición media mid, es decir, mid = (izquierda + derecha) / 2.
  • Compare la relación de tamaño entre el elemento de destino y el elemento intermedio:
  • Si el elemento de destino es igual al elemento del medio, se encuentra el elemento de destino y se devuelve su índice.
  • Si el elemento de destino es más pequeño que el elemento del medio, actualice el límite derecho right = mid - 1 y continúe la búsqueda binaria en la mitad izquierda.
  • Si el elemento de destino es más grande que el elemento del medio, actualice el límite izquierdo left = mid + 1 y continúe la búsqueda binaria en la mitad derecha.
  • Repita los pasos 2 y 3 hasta que se encuentre el elemento de destino o el límite izquierdo sea más grande que el límite derecho.

Complejidad de tiempo O(logN) , donde n es la longitud de la matriz. Dado que el rango de búsqueda se reduce a la mitad cada vez, el algoritmo es muy eficiente. Pero se requiere que la matriz esté ordenada, de lo contrario, la dicotomía no se puede aplicar para la búsqueda.

1.1 有序Encuentra un elemento específico en una matriz

La idea básica es reducir el rango de búsqueda a la mitad comparando la relación de tamaño entre el elemento intermedio y el elemento de destino hasta que se encuentre el elemento de destino o el rango de búsqueda esté vacío.

Como, por ejemplo, el número de arreglos es N=16, el peor de los casos se divide en 4 veces ( [ 8 ∣ 8 ] → [ 4 ∣ 4 ] → [ 2 ∣ 2 ] → [ 1 ∣ 1 ] ) ( [8 |8] \a [4|4] \a [2|2] \a [1|1] )([ 8∣8 ][ 4∣4 ][ 2∣2 ][ 1∣1 ]) , y4 = log 2 16 4 = log_2164=registro _ _216 _ Es decir, la complejidad del tiempo esO ( log N ) O(logN)O ( log N ) _ _

/* 注意:题目保证数组不为空,且 n 大于等于 1 ,以下问题默认相同 */
int binarySearch(std::vector<int>& arr, int value)
{
    
    
    int left = 0;
    int right = arr.size() - 1;
    // 如果这里是 int right = arr.size() 的话,那么下面有两处地方需要修改,以保证一一对应:
    // 1、下面循环的条件则是 while(left < right)
    // 2、循环内当 array[middle] > value 的时候,right = middle

    while (left <= right)
    {
    
    
        int middle = left + ((right - left) >> 1);  // 不用right+left,避免int溢出,且更快
        if (array[middle] > value)
            right = middle - 1;
        else if (array[middle] < value)
            left = middle + 1;
        else
            return middle;
        // 可能会有读者认为刚开始时就要判断相等,但毕竟数组中不相等的情况更多
        // 如果每次循环都判断一下是否相等,将耗费时间
    }
    return -1;
}

Tenga cuidado con los resultados de la izquierda + ((derecha - izquierda) >> 1), etc. para (derecha + izquierda) / 2, pero más rápido sin desbordamiento int

1.2 Encuentra la posición más a la izquierda de >= un cierto número en una matriz ordenada

La idea sigue siendo el método de dicotomía, que es diferente de encontrar un cierto valor y detener la dicotomía cuando se encuentra el valor objetivo. El problema de encontrar la posición más a la izquierda/más a la derecha debe ser dicotómico hasta el final .


int nearLeftSearch(const std::vector<int>& arr, int target)
{
    
    
	int left = 0;
	int right = arr.size() - 1;
	int result = -1;
	
	while (left <= right)
	{
    
    
		int mid = left + ((right - left) >> 1);
		if (target <= arr[mid]){
    
     // 目标值小于等于mid,就要往左继续找
			result = mid;// 暂时记录下这个位置,因为左边可能全都比目标值小了,就已经找到了
			right = mid - 1;
		} else{
    
    		// target > arr[mid]
			left = mid + 1;
		}
	}
	return result;
}

1.3 Encuentra la posición más a la derecha de <= un cierto número en una matriz ordenada

  • Si el elemento central es mayor que el valor objetivo, significa que el valor objetivo debe estar en la mitad izquierda, por lo que restringimos la búsqueda a la mitad izquierda y actualizamos la derecha a la mitad - 1.
  • Si el elemento central es menor o igual que el valor objetivo, significa que el valor objetivo debe estar en la mitad derecha o en la posición actual, por lo que actualizamos el resultado al índice medio actual para registrar la posición más a la derecha encontrada y reducir la rango de búsqueda a la mitad derecha, actualización de izquierda a mitad + 1.
int nearRightSearch(const std::vector<int>& arr, int target) 
{
    
    
    int left = 0;
    int right = arr.size() - 1;
    int result = -1;

    while (left <= right) {
    
    
        int mid = left + (right - left) / 2;

        if (target < arr[mid]) {
    
    
            right = mid - 1;
        } else {
    
    	// target >= arr[mid]
            result = mid;
            left = mid + 1;
        }
    }

    return result;
}

1.4 Problema mínimo local (un caso de arreglo desordenado usando dicotomía)

La matriz arr no está ordenada, dos números adyacentes cualesquiera no son iguales , encuentre una posición mínima local (valor mínimo) y se requiere que la complejidad del tiempo sea mejor que O (N)

El desorden también se puede dicotomizar , siempre que el problema objetivo deba tener una solución por un lado, y el otro lado no importa, se puede usar la dicotomía.

1. Primero juzga los dos límites de la matriz.

  • Encontrado si el límite izquierdo es arr[0] < arr[1]
  • Encontrado si hay un límite arr[n-1] < arr[n-2]
  • Si ninguno de los dos límites es un mínimo local, y debido a que dos números adyacentes cualesquiera no son iguales, el límite izquierdo decrece monótonamente localmente y el límite derecho aumenta monótonamente localmente . Por lo tanto, en la matriz, debe haber un punto de valor mínimo
    inserte la descripción de la imagen aquí

2. Realice la dicotomía y juzgue la relación entre las posiciones intermedias y adyacentes, que se dividen en 3 situaciones: (Recordatorio: ¡dos elementos adyacentes en la matriz no son iguales!) 3. Repita el proceso 2 hasta encontrar
inserte la descripción de la imagen aquí
el valor mínimo

int LocalMinimumSearch(const std::vector<int>& arr) 
{
    
    
    int n = arr.size();
    // 先判断元素个数为0,1的情况,如果题目给出最少元素个>1数则不需要判断
    if (n == 0) return -1;
    if (n == 1) return 0; // 只有一个元素,则是局部最小值
	
	if (arr[0] < arr[1]) return 0;
	
	int left = 0;
    int right = n - 1;
	// 再次提醒,数组中相邻两个元素是不相等的!
    while (left < right) 
    {
    
    
        int mid = left + ((right - left) >> 1);

        if (arr[mid] < arr[mid - 1] && arr[mid] < arr[mid + 1]) {
    
    
            return mid;  // 找到局部最小值的位置
        } else if (arr[mid - 1] < arr[mid]) {
    
    
            right = mid - 1;  // 局部最小值可能在左侧
        } else {
    
    
            left = mid + 1;  // 局部最小值可能在右侧
        }
    }

    // 数组中只有一个元素,将其视为局部最小值
    return left;
}

2 Pensamiento recursivo simple

Desde el comienzo de Zuoge P4 , pertenece al conocimiento previo del tipo de fusión. La dicotomía también se usa aquí. Principalmente para entender el proceso de ejecución.

Ejemplo: Encuentre el valor máximo en el rango especificado de la matriz, usando la recursividad para lograr

#incldue <vector>
#include <algorithm>
int process(const std::vector<int>& arr, int L, int R)
{
    
    
	if (L == R) return arr[L]; 
	
	int mid = L + ((R - L) >> 1);	// 求中点,右移1位相当于除以2
	int leftMax = process(arr, L, mid);
	int rightMax = process(arr, mid + 1, R);
	return std::max(leftMax, rightMax);
}

Cuando se llama a la función process(arr, L, R), hace lo siguiente:

  1. En primer lugar, se comprueba la condición de terminación de la recurrencia. Si L y R son iguales, significa que el intervalo mínimo de la matriz se recurrió y solo hay un elemento. En este momento, el elemento arr[L] se devuelve directamente.
  2. Si no se cumple la condición de terminación, la recursividad debe continuar. Primero, calcule el punto medio mid y divida el intervalo [L, R] en dos subintervalos [L, mid] y [mid+1, R].
  3. Luego, llame recursivamente a process(arr, L, mid) para procesar el subintervalo izquierdo. Este paso empuja la función actual a la pila, ingresando a un nuevo nivel de recursividad.
  4. En el nuevo nivel recursivo, ejecute los pasos 1 a 3 nuevamente hasta que se cumpla la condición de terminación y devuelva el valor máximo leftMax del subintervalo izquierdo.
  5. A continuación, llame recursivamente a process(arr, mid+1, R) para procesar el subintervalo correcto. Nuevamente, este paso empuja la función actual a la pila, ingresando a un nuevo nivel de recursividad.
  6. En el nuevo nivel recursivo, ejecute los pasos 1-3 nuevamente hasta que se cumpla la condición de terminación y devuelva el valor máximo rightMax del subintervalo derecho.
  7. Finalmente, compare los valores máximos leftMax y rightMax de los subintervalos izquierdo y derecho, tome el valor mayor como el
    valor máximo de todo el intervalo [L, R] y devuélvalo como resultado.
  8. Al volver recursivamente a la capa anterior, pasar el valor máximo obtenido a la capa anterior hasta volver al punto de llamada original para obtener el valor máximo de todo el arreglo.

En el proceso de recursividad, cada llamada recursiva creará un nuevo marco de pila de funciones para guardar las variables locales y los parámetros de la función. Cuando se cumple la condición de finalización, la recursividad comienza a retroceder y el resultado final se devuelve capa por capa. Al mismo tiempo, las variables locales y los parámetros de cada capa también se destruyen y los marcos de la pila de funciones se extraen de la pila. Sucesivamente.

gráfico de dependencia

  • Suponga que la matriz de destino es [3, 2, 5, 6, 7, 4], llame al proceso (0,5) para encontrar el valor máximo y se omite el parámetro arr
  • El número rojo es el flujo de ejecución del proceso, que omite el proceso de comparación y devolución de std::max
    inserte la descripción de la imagen aquí

Palabras humanas: si desea obtener el valor de retorno de la matriz, es decir, 1el valor de retorno del primer paso, primero debe obtener 2el valor máximo, y su valor máximo debe ejecutarse primero de acuerdo con el código 3, y 3debe También se ejecutará primero 4Cuando los dos puntos lleguen a 4este paso, solo hay un elemento L == R, mire el código, este elemento es el valor máximo del intervalo actual (0,0), regrese a, 3después de 3obtener leftMax , necesita el valor máximo de la parte derecha, ejecútelo 5, obtenga rightMax y, finalmente, compare el valor máximo de los intervalos izquierdo y derecho, obtenga el valor máximo del intervalo (0,1) y vuelva a él 2. 2es necesario 6volver a obtener el valor máximo de (0,2) después de la comparación 6, y 2volver a él 1. Luego haz el de la derecha. . . . luego omitido

2.1 Complejidad temporal del algoritmo recursivo (fórmula maestra)

En programación, la recursividad es un algoritmo muy común. Se usa ampliamente debido a su código conciso. Sin embargo, en comparación con la ejecución secuencial o los programas cíclicos, la recursividad es difícil de calcular. La fórmula maestra se usa para calcular la complejidad temporal de los programas recursivos.

Condiciones de uso: Todos los subproblemas deben ser del mismo tamaño . Para decirlo sin rodeos, es básicamente el algoritmo recursivo creado por árboles binarios y binarios.

公式T ( norte ) = a T ( norte / segundo ) + O ( norte re ) T(N) = aT(N/b) + O(N^d)T ( norte )=un T ( N / b )+O ( nortere )

  • NNN : El tamaño de los datos del proceso principal es N
  • N/b N/bN / b : tamaño de datos del subproceso
  • Automóvil club británicoa : el número de llamadas a la subrutina
  • O (Nd) O(N^d)O ( norted ): Complejidad temporal de otros procesos excepto la invocación de subproblemas

Después de obtener abd, obtenga la complejidad del tiempo de acuerdo con las siguientes situaciones diferentes

  • logba > d log_ba > dregistro _ _segundoa>d : la complejidad del tiempo esO (N logba) O(N^{log_ba})O ( norteregistro _ _segundoun )
  • registro = d registro_ba = dregistro _ _segundoa=d : La complejidad del tiempo esO ( N d ⋅ log N ) O(N^d · logN)O ( nortereregistro N ) _ _
  • logba < d log_ba < dregistro _ _segundoa<d : La complejidad del tiempo esO ( N d ) O(N^d)O ( nortere )

Por ejemplo, el ejemplo de encontrar el valor máximo mencionado anteriormente puede usar esta fórmula:

norte = 2 ⋅ T ( norte 2 ) + O ( 1 ) norte = 2·T(\frac{N}{2}) + O(1)norte=2 T (2norte)+O ( 1 ) . donde a = 2, b = 2, d = 0

registro 2 2 = 1 > 0 registro_22 = 1 > 0registro _ _22=1>0 Por lo tanto, la complejidad del tiempo es:O ( N ) O(N)O ( N )

Supongo que te gusta

Origin blog.csdn.net/Motarookie/article/details/131382340
Recomendado
Clasificación