Cómo mejorar la eficiencia del código - complejidad de tiempo y complejidad de espacio - [Lenguaje C]

Cuando nos enfrentamos a un problema, hay muchas formas de solucionarlo. Nuestra tecnología informática actual ha alcanzado un nivel muy avanzado, por lo que cuando usamos diferentes métodos para resolver problemas, la diferencia de tiempo no será obvia, y la diferencia de memoria generalmente no se siente cuando tenemos pequeños problemas, por lo que no enredaremos el programar el proceso de optimización.

Pero en la vida futura, el contenido del programa será muy rico y la eficiencia del tiempo y el espacio se reflejará.Aprendamos sobre el tiempo y el espacio del programa de hoy.

Tabla de contenido

Eficiencia del algoritmo

 Cómo medir la calidad de un algoritmo

complejidad algorítmica 

 complejidad del tiempo

El concepto de complejidad del tiempo.

Notación asintótica para O grande

Ejemplos de cálculos de complejidad de tiempo comunes

complejidad del espacio

Ejemplos de cálculos de complejidad de espacio común

Comparación de complejidad común


Eficiencia del algoritmo

 Cómo medir la calidad de un algoritmo

¿Cómo medir la calidad de un algoritmo? Por ejemplo, para la siguiente sucesión de Fibonacci:

long long Fib(int N)
{
 if(N < 3)
 return 1;

 return Fib(N-1) + Fib(N-2);
}

La implementación recursiva de la sucesión de Fibonacci es muy concisa, pero ¿es bueno ser conciso? Entonces, ¿cómo medir lo bueno y lo malo?

Aunque el código es muy conciso, cuando configuramos el valor de N para que sea mayor que 50, es muy difícil calcular el resultado y la computadora realizará muchos cálculos.

complejidad algorítmica 

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 de tiempo y complejidad de espacio. La complejidad del tiempo mide principalmente qué tan rápido se ejecuta un algoritmo, mientras que la complejidad del espacio mide principalmente el espacio adicional requerido para que se ejecute un algoritmo . En los primeros días del desarrollo de las computadoras, las computadoras tenían muy poca capacidad de almacenamiento. Así que estoy muy preocupado por la complejidad del espacio. Sin embargo, con el rápido desarrollo de la industria informática, la capacidad de almacenamiento de las computadoras ha alcanzado un nivel muy alto. Así que ya no necesitamos prestar especial atención a la complejidad espacial de un algoritmo.

 complejidad del tiempo

El concepto de complejidad del tiempo.

Definición de complejidad temporal: en informática, la complejidad temporal de un algoritmo es una función que describe cuantitativamente el tiempo de ejecución de ese algoritmo. Teóricamente hablando, el tiempo que tarda en ejecutarse un algoritmo no se puede calcular, solo cuando pones tu programa en la máquina y lo ejecutas puedes saberlo. Pero, ¿necesitamos probar cada algoritmo en la computadora? Es posible probarlos todos en la computadora, pero es muy problemático, por lo que existe un método de análisis de la complejidad del tiempo. El tiempo empleado por un algoritmo es proporcional al número de ejecuciones de las sentencias que contiene, 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 oración básica determinada y el tamaño del problema N es calcular la complejidad temporal del algoritmo.

Damos un paso más para comprender la complejidad del tiempo con un programa:

// 请计算一下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);
}

¿Necesitamos calcular cuántas veces se ha ejecutado ++count en total? En el anidamiento del ciclo for, ++count se usa N² veces, y se ejecuta 2N veces en el tercer ciclo for, y finalmente M=10 en el ciclo while, por lo que ++count se repite 10 veces, por lo que ++count total Ejecutado N²+2N+10 veces

Cuando tomamos un valor grande de N, solo el elemento de mayor grado en la expresión tiene una gran influencia en la expresión. 

En la práctica, cuando calculamos la complejidad del tiempo, no necesariamente necesitamos 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. 

Notación asintótica para 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 de orden O grande:

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, solo se mantiene el término de mayor orden.

3. Si existe el artículo de mayor orden y no es 1, elimine la constante multiplicada por este artículo. El resultado es un gran pedido O.

Después de usar la representación asintótica de O grande, la complejidad temporal de Func1 es: O(N²);

N = 10 F(N) = 100 N = 100 F(N) = 10000 N = 1000 F(N) = 1000000

Por lo anterior, encontraremos que la representación progresiva de la gran O elimina aquellos elementos que tienen poca influencia en el resultado, y expresa el número de ejecuciones de forma clara y concisa.

Además, existen complejidades de tiempo en el mejor, promedio y peor de los casos de algunos algoritmos:

Peor caso: número máximo de ejecuciones para cualquier tamaño de entrada (límite superior)

Caso promedio: Número esperado de ejecuciones para cualquier tamaño de entrada Mejor caso: Número mínimo de ejecuciones para cualquier tamaño de entrada (límite inferior) Ejemplo: Buscar un dato x en una matriz de longitud N

Mejor caso: 1 hallazgo Peor caso: N hallazgos Promedio de casos: N/2 hallazgos 

En la práctica, la situación general se refiere a la peor operación del algoritmo, por lo que la complejidad temporal de la búsqueda de datos en el arreglo es O(N)

Ejemplos de cálculos de complejidad de tiempo comunes

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

Este programa se ejecuta M veces en el primer bucle for y N veces en el segundo bucle for, así que usa la O grande para representar O(M+N)

// 计算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;
 }
}

Encuentre la complejidad temporal de BubbleSort. Esta función es el método de clasificación de burbujas, si clasificamos un conjunto de matrices, el mejor de los casos es N, solo es necesario verificar una vez. El peor de los casos es que la orden debe cambiarse todo el tiempo y debe ejecutarse (N * (N + 1)) / 2 veces. Al derivar el método de gran orden O + complejidad de tiempo, generalmente es el peor, y la complejidad del tiempo es O(N^2 ).

// 计算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)
 end = mid-1;
 else
 return mid;
 }
 return -1;
}

Esta función es un método de búsqueda binaria, necesitamos encontrar la complejidad temporal de esta función.

 De acuerdo con la figura anterior, podemos concluir que la complejidad temporal de la búsqueda binaria es O(logN).

// 计算斐波那契递归Fib的时间复杂度?
long long Fib(size_t N)
{
 if(N < 3)
 return 1;

 return Fib(N-1) + Fib(N-2);
}

Ahora que hemos aprendido la complejidad del tiempo, podemos calcular la complejidad del tiempo de usar la recursión para encontrar la secuencia de Fibonacci, y podemos saber por qué usar la recursión para encontrar la secuencia de Fibonacci es solo un código simple, ¡pero no es una buena manera! !

De la figura anterior, podemos obtener que la complejidad temporal de esta recursión es O(2^N), ¡así que este algoritmo es muy malo! ! !

complejidad del espacio

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 de la O grande.

Nota: El espacio de la pila (parámetros de almacenamiento, variables locales, cierta información de registro, etc.) requerido por la función en tiempo de ejecución se ha determinado 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.

Ejemplos de cálculos de complejidad de espacio común

// 计算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;
 }
}

Esta función utiliza una cantidad constante de espacio extra y abre tres espacios variables: size_t int, int exchange y size_t i, por lo que la complejidad del espacio es O(1).

// 计算斐波那契递归Fib的空间复杂度?
long long Fib(size_t N)
{
 if(N < 3)
 return 1;

 return Fib(N-1) + Fib(N-2);
}

Use la recursividad para calcular los números de Fibonacci, porque cada vez que se llama a una función, se debe abrir un marco de pila de función, y el espacio del marco de pila de esta función se destruirá después de que se complete la llamada, por lo que cuando calculamos la complejidad del espacio, tenemos para realizar cálculos acumulativos en orden, y no llamará recursivamente a otro contenido cuando no se haya utilizado la función. Porque la recursión de los números de Fibonacci es como un árbol, pero la complejidad espacial es diferente de la complejidad temporal.La complejidad espacial depende del número de llamadas al mismo tiempo, por lo que la complejidad espacial de esta función es N, expresada como O( NORTE).

Comparación de complejidad común

 

Entonces, la complejidad tiene una gran influencia en un programa. Cuando diseñamos un programa, ¡podríamos detenernos y pensar cómo diseñarlo es la solución óptima! ! ! !

Supongo que te gusta

Origin blog.csdn.net/m0_74755811/article/details/131965924
Recomendado
Clasificación