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)=∑ink1⏟1..6+∑i=1n−1⏟7(∑j=i+1n⏟8(∑k=1j⏟9∑ink2⏟10))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=j∗k2\sum\limits_{k=1}^{j} k_2 = j * k_2

T(n)=k1+∑i=1n−1(∑j=i+1n(j∗k2))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(j∗k2)=k2∗∑j=i+1nj\sum\limits_{j=i+1}^{n}(j* k_2) = k_2 * \sum\limits_{j=i+1}^{n}j

T(n)=k1+∑i=1n−1(k2∗∑j=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=1nj−∑j=1ij\sum\limits_{j=i+1}^{n}j = \sum\limits_{j=1}^{n}j - \sum\limits_{j=1}^{i}j

T(n)=k1+∑i=1n−1(k2∗(∑j=1nj−∑j=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=1n−1(k2∗(n∗(n+1)2−i∗(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+k22∗∑i=1n−1(n2+n−i2−i)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=1n−1(n2+n)−∑i=1n−1i2−∑i=1n−1i)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=n∗cte→∑i=1n−1(n2+n)=(n−1)∗(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)2→∑i=1n−1i=(n−1)∗[(n−1)+1]2=(n−1)∗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)6→∑i=1n−1i2=(n−1)∗[(n−1)+1]∗[2(n−1)+1]6=(n−1)∗n∗(2n−1)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∗((n−1)∗(n2+n)−(n−1)∗n∗(2n−1)6−(n−1)∗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+n2−n2−n)−(2n3−n2−2n2+n)−3(n2−n)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)=k23∗n3−k23∗n+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)≥c∗f(n)∀(c)>0  ∧  n≥n0T(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

k23∗n3≤c2∗n3∀c2≥k23∧n0≥1\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

−k23∗n≤c2∗n3∀c2≥0∧n0≥0-\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)

k1≤c1∗n3∀c1≥0∧n0≥1k_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)

k23∗n3+k1≤c2∗n3+c1∗n3\frac{k_2}{3} * n^3 + k_1 \le c_2 * n^3 + c_1 * n^3

Sacando factor común n3n^3

k23∗n3+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:

k23∗n3+k1≤c∗n3\frac{k_2}{3} * n^3 + k_1 \le c * n^3

T(n)≤c∗n3conc=k1+k23∧n0=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 )