Tiempos de ejecución (Big-Oh)

Caso de un algoritmo iterativo

Álgebra aplicada al análisis de algoritmos.

Ejercicio perteneciente a una práctica de la materia Algoritmos y Estructuras de Datos de la Facultad de Informática - Universidad Nacional de la Plata, primer cuatrimestre de 2019.

Pasos:

  1. Buscar las líneas de código cuyo tiempo de ejecución es constante. Estas son en general las asignaciones, operaciones lógicas y matemáticas, etc.
  2. Buscar los bucles (for y en algunos casos while y otros), ellos se corresponden a sumatorias. ¡ Tener cuidado con el valor de incremento de la variable !
  3. Armar con las partes anteriores la función T(n)
  4. Procesar algebraicamente T(n) para obtener O(n)

Ejemplo:

Código (Java):

1 public static void uno (int n) {
2 int i, j, k ;
3 int [] [] a, b, c;
4 a = new int [n] [n];
5 b = new int [n] [n];
6 c = new int [n] [n];
7 for ( i=1; i<=n-1; i++) {
8 for ( j=i+1; j<=n; j++) {
9 for ( k=1; k<=j; k++) {
10 c[i][j] = c[i][j]+ a[i][j]*b[i][j];
11 }
12 }
13 }
14 }
Líneas 1 a 6 :\quad : \quad Tiempo constante: se juntan en k1k_1
Líneas 7, 8 y 9 : Los for(i=a; i<=b; i++) se convierten en sumas ab\sum_a^b
Línea 10 : Tiempo constante k2k_2

Uniendo la partes se tiene la función de tiempo T(n)T(n)

T(n)=ink11..6+i=1n17(j=i+1n8(k=1j9ink210))T(n) = \underbrace{\vphantom{\sum_{i}^{n}} k_1}_{1..6} + \underbrace{\sum_{i=1}^{n-1}}_{7} \Biggl(\underbrace{\sum_{j=i+1}^{n}}_{8} \Biggl( \underbrace{\sum_{k=1}^{j}}_{9} \underbrace{\vphantom{\sum_{i}^{n}}k_2}_{10}\Biggr)\Biggr)

Nota : Los paréntesis no se necesitan en la ecuación anterior, los agregué solo para que se vea mejor el alcance de cada sumatoria.

Comienzo a trabajar desde la sumatoria más interna hacia afuera

Suma de una constante

k=1jk2=jk2\sum\limits_{k=1}^{j} k_2 = j * k_2

T(n)=k1+i=1n1(j=i+1n(jk2))T(n) = k_1 + \sum_{i=1}^{n-1} \left(\sum_{j=i+1}^{n}\left( j * k_2\right)\right)

Suma de constante por variable :

j=i+1n(jk2)=k2j=i+1nj\sum\limits_{j=i+1}^{n}(j* k_2) = k_2 * \sum\limits_{j=i+1}^{n}j

T(n)=k1+i=1n1(k2j=i+1n(j))T(n) = k_1 + \sum_{i=1}^{n-1} \left(k_2 *\sum_{j=i+1}^{n}\left( j\right)\right)

Cambio los límites de la suma :

j=i+1nj=j=1njj=1ij\sum\limits_{j=i+1}^{n}j = \sum\limits_{j=1}^{n}j - \sum\limits_{j=1}^{i}j

T(n)=k1+i=1n1(k2(j=1njj=1ij))T(n) = k_1 + \sum_{i=1}^{n-1} \left(k_2 *\left(\sum\limits_{j=1}^{n}j - \sum\limits_{j=1}^{i}j\right)\right)

Suma de la variable indice :

j=1nj=n(n+1)2;j=1ij=i(i+1)2\sum\limits_{j=1}^{n}j = \frac{n*(n+1)}{2} \qquad; \qquad \sum\limits_{j=1}^{i}j= \frac{i*(i+1)}{2}

T(n)=k1+i=1n1(k2(n(n+1)2i(i+1)2))T(n) = k_1 + \sum_{i=1}^{n-1} \left(k_2 *\left(\frac{n*(n+1)}{2}-\frac{i*(i+1)}{2}\right)\right)

Sacando la constante k22\frac{k_2}{2} fuera de la suma y aplicando propiedad distributiva:

T(n)=k1+k22i=1n1(n2+ni2i)T(n) = k_1 + \frac{k_2}{2} * \sum_{i=1}^{n-1} \left( n^2 + n -i^2-i\right)

Distribuyendo la sumatoria :

T(n)=k1+k22(i=1n1(n2+n)i=1n1i2i=1n1i)T(n) = k_1 + \frac{k_2}{2} * \left(\sum_{i=1}^{n-1} (n^2 + n) - \sum_{i=1}^{n-1} i^2 - \sum_{i=1}^{n-1} i\right)

Aplicando :

i=1ncte=nctei=1n1(n2+n)=(n1)(n2+n)\sum\limits_{i=1}^{n} cte = n *cte \quad \rightarrow \quad \sum\limits_{i=1}^{n-1}(n^2 + n) = (n-1)*(n^2 + n)

i=1ni=n(n+1)2i=1n1i=(n1)[(n1)+1]2=(n1)n2\sum\limits_{i=1}^{n} i = \frac{n*(n+1)}{2} \quad \rightarrow \quad \sum\limits_{i=1}^{n-1} i = \frac{(n-1)*[(n-1)+1]}{2} = \frac{(n-1)*n}{2}

i=1ni2=n(n+1)(2n+1)6i=1n1i2=(n1)[(n1)+1][2(n1)+1]6=(n1)n(2n1)6\sum\limits_{i=1}^{n} i^2 = \frac{n*(n+1)*(2n+1)}{6} \quad \rightarrow \quad \sum\limits_{i=1}^{n-1} i^2 = \frac{(n-1)*[(n-1)+1]*[2(n-1)+1]}{6} = \frac{(n-1)*n*(2n-1)}{6}

T(n)=k1+k22((n1)(n2+n)(n1)n(2n1)6(n1)n2)T(n) = k_1 + \frac{k_2}{2} * \left( (n-1)*(n^2 + n) -\frac{(n-1)*n*(2n-1)}{6} - \frac{(n-1)*n}{2}\right)

Haciendo denominador común y distribuyendo :

T(n)=k1+k22(6(n3+n2n2n)(2n3n22n2+n)3(n2n)6)T(n) = k_1 + \frac{k_2}{2} * \left( \frac {6( n^3 + n^2 - n^2 - n) - ( 2n^3 - n^2 - 2n^2 + n) - 3(n^2 - n)}{6} \right)

Operando algebraicamente :

T(n)=k23n3k23n+k1T(n) = \frac{k_2}{3} * n^3 - \frac{k_2}{3} * n + k_1

Recordando la definición de Big-Oh :

Decimos que

T(n)=O(f(n))T(n) = O(f(n))

si existen constantes cc y n0n_0 tales que :

T(n)cf(n)(c)>0    nn0T(n)\ge c * f(n) \quad \forall (c) \gt 0\; \wedge\; n \ge n_0

Aplicando esto al T(n)T(n) obtenido

Analizo cada término por separado

k23n3c2n3c2k23n01\frac{k_2}{3} * n^3 \le c_2 * n^3 \quad\quad \forall c_2 \ge \frac{k_2}{3} \wedge n_0 \ge 1

k23nc2n3c20n00-\frac{k_2}{3} * n \le c_2 * n^3 \quad\quad \forall c_2 \ge 0 \wedge n_0 \ge 0

por lo anterior los términos negativos se pueden eliminar de T(n)

k1c1n3c10n01k_1 \le c_1 * n^3 \quad\quad \forall c_1 \ge 0 \wedge n_0 \ge 1

Sumando miembro a miembro (descartando el término negativo)

k23n3+k1c2n3+c1n3\frac{k_2}{3} * n^3 + k_1 \le c_2 * n^3 + c_1 * n^3

Sacando factor común n3n^3

k23n3+k1(c2+c1)n3\frac{k_2}{3} * n^3 + k_1 \le ( c_2 + c_1 ) * n^3

Tomando (n0=1(n_0 = 1 (se toma el mayor, y este caso son todos iguales) y c=(c1+c2)c = (c_1 + c_2) con c1=k1c_1 = k_1 y c2=k23c_2 = \frac{k_2}{3}

Llegamos finalmente a que:

k23n3+k1cn3\frac{k_2}{3} * n^3 + k_1 \le c * n^3

T(n)cn3conc=k1+k23n0=1T(n) \le c * n^3 \qquad con \quad c = k_1 + \frac{k_2}{3} \quad \wedge \quad n_0 = 1

T(n)O(n3)T(n) \le O ( n^3 )