0 Complejidad temporal Complejidad espacial de la estructura de datos básica

1. Eficiencia del algoritmo

1.1 Cómo medir la calidad de un algoritmo

如何衡量一个算法的好坏呢? 比如对于下列的斐波那契数列:
long long Fib(int n)
{
    
    
    if(n < 3)
     {
    
    
         return 1;
     }
    else
    {
    
    
       return Fib(n - 1) + Fib(n - 2);
    }

La implementación recursiva de la secuencia de Fibonacci es muy concisa, pero ¿la simplicidad es necesariamente buena? Entonces, ¿cómo medir su bondad y maldad? A continuación presentaremos el estándar de medición.

1.2 Complejidad del algoritmo

Una vez que el algoritmo se compila en un programa ejecutable, necesita consumir recursos de tiempo y recursos de espacio (memoria) cuando se ejecuta. Por lo tanto, para medir la calidad de un algoritmo, generalmente se mide desde dos dimensiones de tiempo y espacio, es decir, complejidad temporal y complejidad espacial.

La complejidad del tiempo mide principalmente la velocidad de ejecución de un algoritmo, y la complejidad del espacio mide principalmente el espacio adicional requerido para que se ejecute un algoritmo.
(En los primeros días del desarrollo de la computadora, la capacidad de almacenamiento de la computadora era muy pequeña. Por eso estaba muy preocupado por la complejidad del espacio. Pero después del rápido desarrollo de la industria informática, la capacidad de almacenamiento de la computadora ha alcanzado un nivel muy alto. nivel Por lo tanto, ya no necesitamos prestar especial atención a la complejidad espacial de un algoritmo).

2. Complejidad del tiempo

2.1 El concepto de complejidad del tiempo.

En informática, la complejidad temporal de un algoritmo es una función (una función en el sentido matemático), y el número de ejecuciones de las operaciones básicas del algoritmo es la complejidad temporal del algoritmo.

Es decir: encontrar la expresión matemática entre una determinada oración básica y el tamaño del problema N es calcular la complejidad temporal del algoritmo.
Tomemos un ejemplo.

//请计算一下Func1中++count共执行了几次
void Func1(int N)
{
    
    
     int count = 0;
for (int i = 0; i < N ; ++ i)
{
    
    
     for (int j = 0; j < N ; ++ j)
     {
    
    
         ++count;
     }
}

for (int k = 0; k < 2 * N ; ++ k)
{
    
    
     ++count;
}
int M = 10;
   while (M--)
  {
    
    
    ++count;
  }
  printf("%d\n", count);
}

Obviamente, el número de bucles for en la primera capa de anidamiento es N*N , y el número de bucles en el segundo bucle for es 2*N mientras que el número de bucles es 10
, por lo que se puede expresar el número básico de ejecuciones de Func1. como F(N) = N 2 + 2*N + 10
De esta expresión, podemos encontrar que cuando N es muy grande, el valor de F(N) = N * N es cercano a F(N) = N * N + 2*N + 10. En la práctica, calculamos el tiempo. Cuando se trata de complejidad, en realidad no tenemos que calcular el número exacto de ejecuciones, sino solo el número aproximado de ejecuciones, por lo que aquí usamos la notación asintótica de O grande.

2.2 Representación asintótica de la O grande

Notación Big O (Notación Big O): es una notación matemática utilizada para describir el comportamiento asintótico de una función.
Derive el método Big O:
1. Reemplace todas las constantes aditivas en tiempo de ejecución con la constante 1.
2. En la función de tiempos de ejecución modificados, sólo se mantiene el término de mayor orden.
3. Si el elemento de mayor orden existe y no es 1, elimine la constante multiplicada por este elemento. El resultado es un orden O grande.

Luego, después de usar la representación asintótica de O grande en el ejemplo anterior, la complejidad temporal de Func1 es: O(N 2 )

A través de lo anterior, encontraremos que la representación asintótica de la O grande elimina aquellos elementos que tienen poca influencia en el resultado y expresa el número de ejecuciones de manera concisa. Además, la complejidad temporal de algunos algoritmos tiene los casos mejor, promedio y peor. :
el mejor caso: número mínimo de ejecuciones (límite inferior) para
cualquier tamaño de entrada Caso promedio: número esperado de ejecuciones para cualquier tamaño de entrada Peor para cualquier tamaño de entrada
caso: número máximo de ejecuciones (límite superior) Peor caso: N veces para encontrar Caso promedio: N/2 veces para encontrar En la práctica, la situación general se centra en la peor operación del algoritmo, por lo que la complejidad temporal de buscar datos en la matriz es O(N)




2.3 Ejemplos de cálculos de complejidad de tiempo comunes

En esta parte, damos brevemente algunos ejemplos para ayudarle a utilizar mejor la notación progresiva de O grande.

Ejemplo 1:

//计算func2的时间复杂度
void Func2(int N)
{
    
    
    int count = 0;
    for (int k = 0; k < 100; ++ k)
    {
    
    
++count; }
    printf("%d\n", count);
}

Obviamente, el número de ejecuciones del bucle for es 100. Según el primer elemento de la notación progresiva O grande, la complejidad temporal es O (1)
Ejemplo 2

// 计算BubbleSort的时间复杂度? 
void BubbleSort(int* a, int n) 
{
    
    
    assert(a);
    for (size_t end = n; end > 0; --end)
    {
    
    
        int exchange = 0;
        for (size_t i = 1; i < end; ++i)
        {
    
    
             if (a[i-1] > a[i])
             {
    
    
                 Swap(&a[i-1], &a[i]);
                 exchange = 1;
             }
        } 
        if (exchange == 0)
              break;
     }
}  

De acuerdo con la idea de clasificación de burbujas (los hierros viejos que no lo saben pueden buscarlo y no entrar en detalles aquí), la complejidad del tiempo real aquí es N-1 + N-2 + N-3. +…+2+1 y luego según
la fórmula de suma de secuencia aritmética es N*(N-1)/2, y finalmente, según la notación asintótica O grande de 2 y 3, la complejidad temporal es O(N 2 )
Ejemplo 3

// 计算BinarySearch的时间复杂度?
int BinarySearch(int* a, int n, int x)
{
    
    
    assert(a);
    int begin = 0;
    int end = n-1;
// [begin, end]:begin和end是左闭右闭区间,因此有=号 
    while (begin <= end)
    {
    
    
        int mid = begin + ((end-begin)>>1);
        if (a[mid] < x)
         begin = mid+1;
        else if (a[mid] > x)
            begin = mid+1;
        else
            end = mid-1;
            return mid;
    }
    return -1;
}

El código anterior es el proceso de implementación de la búsqueda binaria. De acuerdo con el principio de implementación de la búsqueda binaria, el peor caso de complejidad del tiempo real es

N/2/2/2.../2 = 1
supongamos que buscamos X multiplicado por
N = 2 y tomamos el logaritmo en ambos lados de X al mismo tiempo que log ⁡ 2 N \log_2N

iniciar sesión2N
aquí escribimos O(log ⁡ N \log Niniciar sesiónN ) ps: en el análisis del algoritmo, logN significa que la base es 2 y el logaritmo es N. En algunos lugares, se escribirá como lgN.

3. Complejidad espacial

La complejidad del espacio también es una expresión matemática, que es una medida de la cantidad de espacio de almacenamiento ocupado temporalmente por un algoritmo durante la operación .
La complejidad del espacio no es cuántos bytes ocupa el programa, porque esto no es muy significativo, por lo que la complejidad del espacio se calcula por el número de variables . Las reglas de cálculo de la complejidad espacial son básicamente similares a las de la complejidad práctica, y también se utiliza la notación asintótica O grande .
Nota: El espacio de pila (parámetros de almacenamiento, variables locales, cierta información de registro, etc.) requerido por la función en tiempo de ejecución se determina durante la compilación, por lo que la complejidad del espacio está determinada principalmente por el espacio adicional solicitado explícitamente por la función en tiempo de ejecución.
A continuación también damos algunos ejemplos para ayudarle a comprender el
Ejemplo 1.

// 计算BubbleSort的空间复杂度? 
void BubbleSort(int* a, int n) 
{
    
    
    assert(a);
    for (size_t end = n; end > 0; --end)
    {
    
    
        int exchange = 0;
        for (size_t i = 1; i < end; ++i)
        {
    
    
             if (a[i-1] > a[i])
             {
    
    
                 Swap(&a[i-1], &a[i]);
                 exchange = 1;
             }
        } 
        if (exchange == 0)
              break;
     }
}  

Aquí se utiliza una cantidad constante de espacio adicional, por lo que la complejidad del espacio es O(1) .
Ejemplo 2

// 计算阶乘递归Fac的空间复杂度? long long Fac(size_t N)
{
    
    
    if(N == 0)
        return 1;
    return Fac(N-1)*N;
}

El ejemplo 2 llama recursivamente N veces, abre N marcos de pila y cada marco de pila usa un espacio constante. La complejidad del espacio es O (N)

4. Comparación de complejidad común

inserte la descripción de la imagen aquí

Supongo que te gusta

Origin blog.csdn.net/weixin_69423932/article/details/127477698
Recomendado
Clasificación