You are on page 1of 16

Algoritmos y Estructuras de Datos, Universidad Central de Venezuela, Escuela de Computación. Prof.

Rhadamés Carmona
__________________________________________________________________________________________________________

FUNDAMENTOS PARA EL CÁLCULO DE COMPLEJIDAD:

Los criterios para evaluar un programa son diversos: eficiencia, portabilidad,


eficacia, robustez, etc. El análisis de complejidad está relacionado con la
eficiencia del programa. La eficiencia mide el uso de los recursos del
computador por un algoritmo. Por su parte, el análisis de complejidad mide el
tiempo de cálculo para ejecutar las operaciones (complejidad en tiempo) y el
espacio de memoria para contener y manipular el programa más los datos
(complejidad en espacio). Así, el objetivo del análisis de complejidad es
cuantificar las medidas físicas: "tiempo de ejecución y espacio de memoria" y
comparar distintos algoritmos que resuelven el mismo problema.

Complejidad en memoria

Cm(T) = costo asociado el costo en espacio para una variable u objeto de tipo
T. Los tipos de datos no ocupan espacio, sino las variables u objetos de un
tipo. Las unidades para medir la complejidad en memoria son palabras. A
continuación veremos los siguientes costos:

a) Si T es un tipo elemental, entonces por conveniencia cualquier variable del


tipo T tiene como costo una palabra. Así, Cm(T)=1.

b) Un string de tamaño n ocupa Cm(string)=n/4+3 palabras de memoria,


asumiendo que un caracter en un string ocupa 1 byte y que la unidad de
palabra es de 4 bytes, y que el descriptor del string ocupa 3 palabras.

c) Para un arreglo unidimensional, usaremos la siguiente definición genérica:

Type Array Arr [ Li..Ls ] of Tbase

entonces Cm(Arr) = (Ls-Li+1)*Cm(Tbase)+3, en donde 3 es el tamaño del


descriptor. Para un arreglo bidimensional tenemos

Type Array AB [Li1..Ls1, Li2..Ls2] of Tbase

entonces Cm(AB)=(Ls1-Li1+1)*(Ls2-Li2+1)*Cm(Tbase)+6

d) Para un registro fijo usaremos la siguiente definición genérica:


Type Registro Reg
C1: T1;
C2: T2;
...
Cn: Tn;
FinRegistro

n
entonces Cm(Reg) = ∑ Cm( Ti)
i =1
Algoritmos y Estructuras de Datos, Universidad Central de Venezuela, Escuela de Computación. Prof. Rhadamés Carmona
__________________________________________________________________________________________________________

Para un registro variante se suma la cantidad de memoria de la parte fija y


discriminante, con el máximo en cantidad de memoria de la parte variable.

e) Para un registro variante

Type Registro Reg


C1: T1; C2: T2; ...; Ck:Tk;
In case of Discri:TipoDisc
<Caso1>: <Datos1>;
<Caso2>: <Datos2>;
...
<Cason>: <Datosn>
EndCase
EndRecord
k

Cm(Reg) = Cm(TipoDisc) + ∑ Cm(Ti ) + Max{Cm( Datos ),..., Cm( Datos


i =1
1 n
)}

f) Cualquier apuntador ocupa también una palabra

g) El costo de una constante es igual al costo en memoria de variables de su


tipo asociado.

h) La definición de tipos no tiene costo alguno (no transcienden en el tiempo ni


en el espacio). Sin embargo, se usa la notación Cm(Tipo) para la cantidad de
memoria requerida por variables, objetos o constantes de dicho tipo.

Aunque raramente los lenguajes compilados utilizan descriptores para los


arreglos en tiempo de ejecución, los lenguajes interpretados suelen utilizarlo.
Adicionalmente, cuando se compila un programa en modo de depuración,
siempre se conocen los tamaños de los arreglos para chequear
desbordamiento, y esa información está incluida en el descriptor. Para
considerar el peor de los casos, asumamos que los arreglos utilizan una
descriptor de 3 palabras, que pueden incluir los límites del arreglo, y el tamaño
en bytes del tipo base del arreglo.

Complejidad en tiempo

Definiciones matemáticas previas

Definición 1:
Sean f y g dos funciones, f,g: Ν→ℜ+-{0}. Se dice que f=Ο(g) (f es de orden g) si
y solo si existe c∈ℜ+ y n0∈Ν tal que ∀n>n0 se cumpla f(n)≤c.g(n). (Ν no
incluye el 0).

En la definición anterior, Ο denota una relación de dominancia de funciones,


en donde la función f está acotada superiormente por un múltiplo de la función
g (f es dominada por c.g(n)). Así la expresión f=Ο(g) refleja que el orden de
crecimiento asintótico de la función f es inferior o igual al de la función g.
Algoritmos y Estructuras de Datos, Universidad Central de Venezuela, Escuela de Computación. Prof. Rhadamés Carmona
__________________________________________________________________________________________________________

Ejemplo:
f(n)=log2(n+1). Tomemos g(n)=log2(n). Aunque f(0) está definida, podemos
restringir su dominio a f(n) con n>0. Así, que f,g:Ν→ℜ+-{0}, y además ∃c∈ℜ+
(c=2) y n0∈Ν (n0=2) tal que ∀n>n0 se cumpla f(n)≤c.g(n). Así,

log2(n+1)=Οlog2(n)

Ejemplo:
Demostrar np≠Ο(np+1). Por reducción al absurdo, tomemos np=Ο(np+1).
Entonces ∃c∈ℜ+ y n0∈Ν tal que ∀n>n0 se cumpla np≤c.np+1⇒ c≥n
(contradicción, c debe ser constante).

Se puede demostrar que Ο es una relación de preorden, porque es reflexiva y


transitiva (no es simétrica). Así,

Es reflexiva: f=Ο(f)
Es transitiva: f=Ο(g) ∧ g=Ο(h) ⇒ f=Ο(h).

Además, Ο satisface varias proposiciones; entre ellas citaremos:

Proposición 1: Si c∈ℜ+, y f: Ν→ℜ+-{0}, entonces, c.f=Ο(f)

Proposición 2: Si c∈ℜ+, y f: Ν→ℜ+-{0}, entonces Ο(c.f)≡Ο(f). Note el símbolo


de equivalente, y no el símbolo igual.

Proposición 3: Si f1=Ο(g1) ∧ f2=Ο(g2) entonces f1+f2=Ο(g1+g2)

Proposición 4: Si f1=Ο(g1) ∧ f2=Ο(g2) entonces f1.f2=Ο(g1.g2)

Proposición 5: Si f1=Ο(g) ∧ f2=Ο(g) entonces f1+f2=Ο(g)

Pruebas:
(Prop.1) c∈ℜ+, y f: Ν→ℜ+-{0} ⇒ ∀n>0: c.f(n) ≤ c.f(n) ⇒ c.f=Ο(f)

(Prop. 2) c.f. ≤ 1.c.f. Por tanto, c.f = O(c.f). Igualmente, c.f ≤ c.f,
por tanto c.f = O(f). Así, O(c.f) es equivalente a O(f).

(Prop. 3) f1=Ο(g1) ∧ f2=Ο(g2) ⇒ Por Def. 1,


∃c,d∈ℜ+ y n1,n2∈Ν tal que ∀n>n1: f1≤c.g1 y ∀n>n2: f2≤d.g2 ⇒
f1(n) ≤ c.g1(n) +
f2(n) ≤ d.g2(n)
------------------
f1(n)+f2(n) ≤ c.g1(n) + d.g2(n)

Definamos la constante k como k = c+d. Es claro que

f1(n)+f2(n) ≤ c.g1(n) + d.g2(n) ⇒f1(n)+f2(n) ≤ k(g1(n) + g2(n))


Algoritmos y Estructuras de Datos, Universidad Central de Venezuela, Escuela de Computación. Prof. Rhadamés Carmona
__________________________________________________________________________________________________________

Por lo tanto, ∃k∈ℜ+ (k=d+c) y n0∈Ν (n0=Max{n1,n2}) tal que ∀n>n0 se


cumpla:

f1(n)+f2(n) ≤ k.(g1(n) + g2(n)) ⇒ f1+f2=Ο(g1+g2)

(Prop. 4) f1=Ο(g1) ∧ f2=Ο(g2) ⇒ f1.f2=Ο(g1.g2)

Por Def. 1
∃c,d∈ℜ+ y n1,n2∈Ν tal que ∀n>n1: f1≤c.g1 y ∀n>n2: f2≤d.g2 ⇒
∃k∈ℜ+ (k=d.c) y n0∈Ν (n0=Max{n1,n2}) tal que ∀n>n0 se cumpla:
f1(n).f2(n) ≤ (d.c).(g1(n)+g2(n)) ⇒ f1.f2=Ο(g1.g2)

(Prop. 5) f1=Ο(g) ∧ f2=Ο(g). Por Prop.3 tenemos: f1+f2=Ο(g+g)≡Ο(2.g),


y por la Prop. 2, Ο(2.g)≡Ο(g). Por lo tanto, f1+f2=Ο(g)
{Fin de pruebas}

El tiempo de ejecución depende de diversos factores. Se tomará como el más


relevante el relacionado con la entrada de datos del programa, asociando a un
problema un entero llamado tamaño del problema, el cual es una medida de la
cantidad de datos de entrada.

Se denota como T(n) el tiempo de ejecución de un programa sobre entradas de


datos de tamaño n; intuitivamente T(n) es el número de instrucciones
ejecutadas en un computador ideal, y es denominado también función de
complejidad en tiempo para el algoritmo. Para el orden de complejidad del
algoritmo, es usada la notación conocida como "big-oh" y es de la forma Ο
(f(n)).

De manera general, la complejidad T(n) de un algoritmo es de Ο(f(n)) si T,f: Ν→


ℜ+-{0}, y ∃c∈ℜ+ y n0∈Ν tal que ∀n>n0 se cumpla T(n)≤c.f(n). (Ver Def. 1).
Generalmente nos interesa la tasa de crecimiento f(n) del tiempo requerido
para resolver instancias más grandes del problema, basándonos en el
concepto de dominancia de funciones.

La tasa de crecimiento obtenida para hallar el orden de complejidad en tiempo


de un algoritmo, permite entre otras cosas:

• Determinar el comportamiento del algoritmo en función del tamaño del


problema, y reflejar cuan grande puede ser el mismo
• Determinar cuanto tiempo de cómputo aumenta al incrementar el tamaño
del problema en una unidad (f(n+1)-f(n))
• Facilita la comparación de algoritmos y permite determinar entre varios
algoritmos, cual será el más eficiente en teoría (el que tiene menor tasa de
crecimiento).
Algoritmos y Estructuras de Datos, Universidad Central de Venezuela, Escuela de Computación. Prof. Rhadamés Carmona
__________________________________________________________________________________________________________

Las tasas de crecimiento suelen ser logarítmicas, polinomiales, y


exponenciales. A continuación tenemos una comparación entre las tasas de
crecimiento más comunes:

1 < log2n <n < n.log2n <n2 <p3(n) < ... pk(n) < … < 2n < en < 3n <... < nn < ...

Complejidad en el peor caso, caso promedio y mejor caso

En muchos casos, la complejidad de tiempo de un algoritmo es igual para todas


las instancias de tamaño n del problema. Como veremos posteriormente, este
es el caso del algoritmo de ordenamiento conocido como Selección Directa.

En otros casos, la complejidad de un algoritmo de tamaño n es distinta


dependiendo de las instancias de tamaño n del problema que resuelve. Esto
nos lleva a estudiar la complejidad en el pero caso, mejor caso, y caso
promedio.

Para un tamaño dado (n), la complejidad del algoritmo en el peor caso resulta
de tomar el máximo tiempo (complejidad máxima) en que se ejecuta el
algoritmo, entre todas las instancias del problema (que resuelve el algoritmo)
de tamaño n; la complejidad en el caso promedio es la esperanza matemática
del tiempo de ejecución del algoritmo para entradas de tamaño n, y la
complejidad mejor caso es el menor tiempo en que se ejecuta el algoritmo
para entradas de tamaño n. Por defecto se toma la complejidad del peor caso
como medida de complejidad T(n) del algoritmo.

Antes de presentar los principios básicos para calcular el tiempo de ejecución


de un programa, se plantearán las reglas de suma y producto.

Regla de la suma

Esta regla se deriva de la prop. 3. y la prop. 5. Sean T1(n) y T2(n) las funciones
de complejidad para ejecutar dos instrucciones P1 y P2 respectivamente (dos
instrucciones de un programa), con T1(n)=Ο(f(n)) y T2(n)=Ο(g(n)). La
complejidad en tiempo de la secuencia P1;P2 es T1(n)+T2(n)=Ο
(Max{f(n),g(n)}). Nótese que T1,T2:Ν→ℜ+-{0}, porque el tamaño de los
problemas es entero positivo, y el tiempo Ti(n) siempre es real positivo.

La demostración de esta regla es sencilla.

Caso 1: Si f>g, entonces

T1(n)≤c1.f(n) y T2(n)≤c2.f(n)

⇒ T1(n) + T2(n) ≤ c1.f(n) + c2.f(n)


⇒ T1(n) + T2(n) ≤ max(c1,c2) * (f(n) + f(n))
⇒ T1(n) + T2(n) ≤ max(c1,c2) * 2 * f(n)
⇒ T1(n) + T2(n) ≤ 2*max(c1,c2) * f(n), tomando c=2*max(c1,c2)
⇒ T1(n) + T2(n) ≤ c*f(n)
Algoritmos y Estructuras de Datos, Universidad Central de Venezuela, Escuela de Computación. Prof. Rhadamés Carmona
__________________________________________________________________________________________________________

⇒ T1(n) + T2(n) = O(f(n))

Caso 2: si f <= g entonces

T1(n)≤c1.g(n) y T2(n)≤c2.g(n)

⇒ T1(n) + T2(n) ≤ c1.g(n) + c2.g(n)


⇒ …
⇒ T1(n) + T2(n) = O(g(n))

Regla del producto

La regla de producto se deriva de la Prop. 4.:


T1(n)=Ο(f(n)) ∧ T2(n)=Ο(g(n)) ⇒. T1(n).T2(n)=Ο(f(n).g(n))

Sean c,d constantes tan que c,d∈ℜ+-{0}. De las reglas del producto, suma y
las proposiciones, se deriva:

1.- T(n)=c ⇒ T(n)=Ο(1)


2.- T(n)=c+f(n)⇒ T(n)=Ο(f(n))
3.- T1(n)=c.f(n) ⇒ T1(n)=Ο(f(n))
4.- T1(n)=c.f(n)+d ⇒ T1(n)=Ο(f(n))
5.- T1(n)=Ο(nk) ∧ T2(n)=Ο(nk+1) ⇒ T1(n)+T2(n)=Ο(nk+1)
6.- T(n)=c.nd ⇒ T(n)=Ο(nd)
7.- T(n) = Pk(n) ⇒ T(n)=Ο( nk). (Pk(n) es un polinomio completo de grado k,
con k=constante positiva). La razón es que cada término de este polinomio se
puede acotar por el término dominante que es nk, y como hay k+1 términos,
tendremos que Pk(n) ≤ (k+1)*nk, lo cual es O(nk). La regla de la suma no se
puede aplicar n veces, ni ningún factor que dependa de n, puesto que estamos
eliminando un término multiplicativo que depende del tamaño del problema, y
que no es constante.
8.- T1(n)=Ln(n) ∧ T2(n)=nk ∧ k>1 ⇒ T1(n)+T2(n)=Ο(nk)
9.- T1(n)=rn ∧ T2(n)=Pk(n) ∧ r>1 ⇒ T1(n)+T2(n)=Ο(rn)
10.- Demuestre que O( log na ) ≡ O( log bn ), para cualesquiera a,b>1 y
constantes.

Análisis de complejidad en tiempo de las instrucciones de un lenguaje

A continuación se presentan algunas reglas para evaluar la complejidad en


tiempo de los programas, tomando como base, el análisis para instrucciones en
un pseudo-lenguaje.

Regla 1: La función de complejidad en tiempo de una instrucción de asignación


simple es una constante (que depende de la arquitectura donde se ejecutará el
Algoritmos y Estructuras de Datos, Universidad Central de Venezuela, Escuela de Computación. Prof. Rhadamés Carmona
__________________________________________________________________________________________________________

algoritmo), independientemente del tamaño de la entrada de datos, y es de


orden Ο(1). No todas las asignaciones son Ο(1). Por ejemplo, una asignación
como f←factorial(n) involucra un algoritmo de n pasos para hallar el factorial de
n, lo cual no puede ser Ο(1). De aquí que nos referimos a sólo aquellas
asignaciones que no involucran llamadas a funciones (que estudiaremos
posteriormente).

Ejemplos:

b ← 100
a ← a+1
a ← (b div 5) mod 7 - 3

Regla 2: La complejidad en tiempo de una salida o entrada simple (Read y


Write) es T(n)=c (constante), por lo tanto es de orden Ο(1)

Regla 3: De la regla de la suma se deriva que la complejidad en tiempo de una


secuencia de k (k es una constante) instrucciones cualesquiera P1,P2, ..., Pk,
siendo Ti(n)=Ο(fi(n)) la complejidad de Pi, entonces la complejidad del bloque
de instrucciones viene dada por
k

∑ Ti( n ) = Ο (Max{ f ( n ), f
1 2 ( n ),..., fk ( n )})
i =1

Ejemplo:
Las tres instrucciones {b ← 100; a ← a+1; a ← (b div 5) mod 7 - 3} son de
complejidad T1(n)=c1, T2(n)=c2, T3(n)=c3 respectivamente con c1≤c2≤c3
(porque las asignación de una constante puede ser más rápido que la de una
suma, y esta a la vez, menos costosa que residuos y divisiones). Así, la función
de complejidad de las tres instrucciones en conjunto es
T(n)=T1(n)+T2(n)+T3(n)= c1+c2+c3 = O(1).

Nótese que no es relevante cual instrucción se tarda más y cual menos, porque
al final T(n) es una constante y por ende de orden Ο(1). Por esto, la
complejidad de cada instrucción simple la tomaremos como la misma constante
"c" para simplificar las cuentas.

Regla 4: La complejidad en tiempo de una instrucción selectiva condicional


simple de la forma:

If <Condición> then
<Instrucciones>
EndIf

está dado por la complejidad T1(n) de <Condición> sumado a la complejidad


T2(n) de <Instrucciones> (porque en el peor caso se ejecutan ambas). Por
regla de la suma, si T1(n)=Ο(f(n)) y T2(n)=Ο(g(n)), entonces el tiempo
Algoritmos y Estructuras de Datos, Universidad Central de Venezuela, Escuela de Computación. Prof. Rhadamés Carmona
__________________________________________________________________________________________________________

requerido para ejecutar el condicional simple es la suma T1(n)+T2(n), lo cual es


Ο(Max{f(n),g(n)}).

Análogamente, para una instrucción selectiva de la forma:

// <Condición> es de complejidad T1(n)=Ο(f(n))


If <Condición> Then
<Instrucciones1> // bloque de complejidad T2(n)=Ο(g(n))
Else
<Instrucciones2> // bloque de complejidad T3(n)=Ο(h(n))
EndIf

su complejidad está dado por: T1(n)+Max{T2(n)+T3(n)}, lo cual, por regla de la


suma es de orden Ο( Max{ f(n), Max{g(n),h(n)} } ) o sencillamente
Ο( Max { f(n),g(n), h(n) } )

Ejemplo: Para una construcción de la forma:


If <Cond1> Then
<Inst1>
Else
If <Cond2> Then
<Inst2>
EndIf
EndIf

Donde:
<Cond1> tiene como tiempo T1(n)=O(f1(n))
<Inst1> tiene como tiempo T2(n)=Ο( f2(n))
<Cond2> tiene como tiempo T3(n)=O(f3(n))
<Inst2> tiene como tiempo T4(n)=Ο( f4(n))

La complejidad viene dada por

T(n) = Max{T1(n) + T2(n), T1(n) + T3(n) + T4(n)}


= Ο(Max { Max{f1(n),f2(n)}, Max{f1(n),f3(n),f4(n)} } )
≡ Ο(Max { f1(n),f2(n),f3(n),f4(n)} )

Regla 5: La complejidad en tiempo de un ciclo iterativo de tipo While o Repeat


es la suma sobre todas las iteraciones de:

• la complejidad en tiempo para ejecutar el cuerpo de la iteración,


• y la complejidad en tiempo para evaluar la condición de terminación del
ciclo.

While <No Condición> Do // se evalúa k veces


<Instrucciones 1> // se ejecuta k-1 veces
Algoritmos y Estructuras de Datos, Universidad Central de Venezuela, Escuela de Computación. Prof. Rhadamés Carmona
__________________________________________________________________________________________________________

FinMientras

La fórmula general supone que en cada iteración, se tiene un tiempo de


evaluación de la condición Tcondi(n) y un tiempo de ejecución de las
<instrucciones 1> Ti(n) que puede variar iteración por iteración. Lo más
importante es que la condición se evalúa una vez más que las instrucciones.

k k −1
T (n) = ∑ Tcond i (n) + ∑ Ti (n)
i =1 i =1

Generalmente, Tcondi(n) es una comparación sencilla, por lo que Tcondi(n)=c.,


pero en ocasiones puede haber invocaciones a funciones complejas cuyo
tiempo no es una constante. El tiempo requerido para ejecutar las
<instrucciones 1> puede depender de la iteración. El valor de k no tiene que ser
una constante. En caso que dependa de n, entonces tendrá un impacto sobre
el tiempo total, y efectivamente sobre el orden de complejidad.

Repeat
<Instrucciones 1> // se ejecuta k veces
Until <Condición> // se evalúa la misma cantidad de veces (k)

En este caso:

k
T ( n) = ∑ Tcond i (n) +Ti (n)
i =1

For <Variable> = <Inicio> To <Final> Do


<Instrucciones 1> // se ejecuta Final-Inicio+1 veces
EndFor

El for es equivalente a:

Variable = Inicio // se ejecuta 1 vez


While (Variable ≤ Final) Do // se evalúa Final-Inicio+2 veces
<Instrucciones 1> // se ejecuta Final-Inicio+1 veces
Variable = Variable+1 // se ejecuta Final-Inicio+1 veces
EndWhile

Tanto la condición, el incremento y la inicialización tienen un tiempo


equivalente a c. El tiempo es

 final 
T (n) = c + ( final − inicio + 2) * c +  ∑ Ti (n)  + ( final − inicio + 1) * c
 i =inicio 
Algoritmos y Estructuras de Datos, Universidad Central de Venezuela, Escuela de Computación. Prof. Rhadamés Carmona
__________________________________________________________________________________________________________

 final 
T (n) = 2 * ( final − inicio + 2) * c +  ∑ Ti (n) 
 i =inicio 

EJERCICIO: Hallar tiempo y orden de complejidad para los algoritmos de


búsqueda lineal y búsqueda binaria.

Regla 6: Si se tiene un programa con procedimientos (no recursivos), se


calcula su complejidad en tiempo usando las reglas anteriores. Si existen
procedimientos recursivos, la complejidad es obtenida por una ecuación de
recurrencia. En ocasiones, la complejidad de algoritmos no recursivos (como
una implementación de búsqueda binaria) puede hallarse por ecuaciones de
recurrencia.

Por lo general, las ecuaciones de recurrencia surgen cuando resolvemos un


problema mediante la descomposición de éste, en varios subproblemas
idénticos de tamaño menor.

Ecuaciones de recurrencia por partición:

Este tipo de ecuaciones de recurrencia surgen cuando un problema de tamaño


n es resuelto mediante la solución de varios sub-problemas de tamaño n/b.
Problemas tipo divide y conquista caen dentro de este grupo de problemas.
Antes de introducir la fórmula general de una ecuación de recurrencia por
partición, estudiaremos la complejidad en tiempo del algoritmo de
ordenamiento por mezcla (merge-sort).

El algoritmo de merge-sort se basa en que el problema de ordenar una lista de


n elementos, se reduce a ordenar dos sublistas de tamaño n/2 cada una, y
luego de ordenarlas, mezclarlas para obtener la solución. Este proceso se
repite recursivamente, hasta que el tamaño de la lista sea uno. Por simplicidad
asumimos que n es una potencia de dos (n=2k). El algoritmo puede expresarse
a muy alto nivel como sigue:

Function MergeSort(Integer n; Lista L) -> Lista


Lista L1, L2;
If n==1 Then
Return L;
Else
L1 = Lista de los primeros n/2 elementos de L
L2 = Lista de los últimos n/2 elementos de L
Retornar(Mezcla(MergeSort(n/2,L1),MergeSort(n/2,L2)));
EndIf
EndFunction

La ejecución del algoritmo puede verse gráficamente en la siguiente figura para


una lista de 4 elementos: (5,2,4,6).
Algoritmos y Estructuras de Datos, Universidad Central de Venezuela, Escuela de Computación. Prof. Rhadamés Carmona
__________________________________________________________________________________________________________

2 4 5 6

mezcla

2 5 4 6

mezcla mezcla

5 2 4 6
Niveles de recursión del algoritmo de MergeSort

El las hojas del árbol de ejecución, las listas sin de tamaño uno (n=1), y la
complejidad en este caso es la complejidad de la instrucción de retorno la cual
es un constante que llamaremos c1.

En cada activación de la función con n>1, el problema es dividido en dos


subproblemas con un costo adicional para construir las listas L1 y L2 (c3.n/2
cada una) y una mezcla de dos listas de tamaño n/2 (costo c4.n).

T(1) = c1
T(n) = 2.T(n/2) + c3*n/2+ c3*n/2+c4.n, si n>1.

Si denominamos c2=c3+c4, tenemos que la complejidad del algoritmo tiene la


siguiente ecuación de recurrencia:

T(1) = c1
T(n) = 2.T(n/2) + c2.n, si n>1.

Bajo la hipótesis de que n=2k, podemos hallar la fórmula no recurrente de T(n).


Probamos para n=1,2,4,8; luego generalizaremos para cualquier n=2k.

T(1) =1
T(2) = 2.T(1)+2.c2 = 2.c1+2.c2 = 2.c1+2.Log2(2).c2
T(4) = 2.T(2)+2.c2 = 2.(2.c1+2.Log2(2).c2)+2.c2 = 4.c1+4.Log2(4).c2
T(8) = 2.T(4)+2.c2 = 2.(4.c1+4.Log2(4).c2)+2.c2 = 8.c1+8.Log2(8).c2
...
T(n) = 2.T(n/2)+2.c2 = n.c1+n.Log2(n).c2

Haciendo inducción sobre n=2k, tenemos:

Hipótesis: T(n)=n.c1+n.Log2(n).c2
Tesis: T(2.n)=2.n.c1+n.Log2(2.n).c2

Prueba: T(2.n) = 2.T(n)+2.c2 = 2.(n.c1+n.Log2(n).c2)+2.c2


= 2.n.c1+2.n.c2.(Log2(n)+1) = 2.n.c1+2.n.c2.(Log2(n)+Log2(2))
= 2.n.c1+n.(Log2(2.n)).c2
Algoritmos y Estructuras de Datos, Universidad Central de Venezuela, Escuela de Computación. Prof. Rhadamés Carmona
__________________________________________________________________________________________________________

Claramente vemos que T(n) = O(n*log2(n))

Así como el algoritmo de MergeSort, muchos algoritmos suelen reducir


problemas de tamaño n, a varios subproblemas de tamaño n/b (b>0). A estos
algoritmos se les denomina comúnmente algoritmos divide-conquista, y el
orden de complejidad en tiempo de éstos tiene como fórmula general de
recurrencia:

T(1) = 1 { se toma T(1)=1 por conveniencia }


T(n) = a.T(n/b)+f(n), si n>1

en donde a es el número de subproblemas, n/b el tamaño de cada


subproblema, y f(n) la función que denota el costo de dividir y/o combinar los
subproblemas. Asumiendo que n=bk, tenemos:

T(1) =1
T(b) = a.T(1)+f(b) = a+1.f(b)
T(b2) = a.T(b)+f(b2) = a.(a+1.f(b))+f(b2) = a2+a.f(b)+1.f(b2)
T(b3) = a.T(b2)+f(b3) = a.(a2+a.f(b)+1.f(b2)) = a3+a2.f(b)+a.f(b2)+f(b3)
...
k −1 k −1 k
b
T(bk) = a.T(bk-1)+f(bk) = a k + ∑ a i . f ( b k −i ) = a k + ∑ a i . f ( i )
i= 0 i= 0 b

Considerando que n=bk, tenemos:


lognb −1

∑ a .f (
n
T( n) = alogb + i n
bi
)
i= 0

logn
n a b n a n a a
Sabiendo que por propiedades alogb = blogb = blogb .logb = ( blogb )logb = nlogb , tenemos
finalmente que:
lognb −1
T ( n) = n logba
+ ∑ a .f ( i n
bi
)
i= 0

Como es de notarse, esta función no es fácil de evaluar, amenos que se posea


más información acerca de la función f(n). Para el caso de que f sea un función
multiplicativa (f(x.y)=f(x).f(y)), se cumple que f(bk-i)=f(b)k-i. Por lo tanto, si f es
multiplicativa T(n) nos queda:
k −1 k −1
T ( n ) = ak + ∑
i= 0
a i . f ( b ) k − i = ak + f ( b ) k . ∑(
i= 0
a i
f (b)
) (4.1)

k −1
r k −1
Además, como ∑ ri =
i =0 r −1
, nos queda que:

( f (ab) )k − 1
ak − f ( b ) k logba nlogb − n b
a logf ( b )
T ( n) = a + f ( b)
k k
= a + =
k
n +
( f (ab) ) − 1 ( f (ab) ) − 1 ( f (ab) ) − 1
Algoritmos y Estructuras de Datos, Universidad Central de Venezuela, Escuela de Computación. Prof. Rhadamés Carmona
__________________________________________________________________________________________________________

Para conocer el orden de T(n) estudiaremos los siguientes casos:

Caso a: a<f(b). Si a<f(b), entonces a/f(b)-1= -c, c>0, y para valores grandes de
n tenemos que:
a logbf( b ) logbf( b ) a f ( b)
nlogb − n n − nlogb logb a logf ( n )
logbf( n ) logf ( n ) logf ( n )
logbf( n )
= ≤ ⇒ T( n) ≤ nlogb + ≤n + ≤ (1 + c1 ) = Ο ( n
n b n b n b
)
−c c c c c c

Los casos a y b pueden resumirse en Ο n ( log


Max { a , f ( n )}
b
)
Caso b: a=f(b). Sustituimos a por f(b) en (4.1) y obtenemos:
a a
T( n) = ak + k. ak = ak (1 + k ) = nlogb (1 + lognb ) = Ο (nlogb .lognb )

Caso c: a>f(b). Si a>f(b), entonces a/f(b)-1 = c > 0, y para valores grandes de n


tenemos que:
a logbf( b ) a
nlogb − n nlogb a a
≤ ⇒ T (n ) ≤ nlogb .(1 + c1 ) = Ο (nlogb )
c c

Nótese que Merge-Sort cae dentro de este caso c considerando a=b=2.

Casos particulares que se pueden encontrar en los libros e internet

En muchos de los casos, el costo de dividir o combinar soluciones es n. Es


decir, F(n) = n. Esto genera una simplificación clara, en donde se suele
plantear:

T(1) = 1
T(n) = a.T(n/b)+n, si n>1

Esta ecuación de recurrencia tiene como resultado:

 O (n) si a < b

T (n) =  O (n log b n) si a = b
O(n logba ) si a > b

El algoritmo de merge-sort cae en el caso a=b=2, es decir, O(nlog2(n)), debido


a que se reduce un problema de tamaño n a dos problemas de tamaño n/2 con
un costo de combinación (mezcla y partición) igual a n.

En otros casos, el costo de combinar las soluciones es una constante. En estos


casos se suele plantear la recurrencia

T(1) = 1
Algoritmos y Estructuras de Datos, Universidad Central de Venezuela, Escuela de Computación. Prof. Rhadamés Carmona
__________________________________________________________________________________________________________

T(n) = a.T(n/b)+1, si n>1

Lo cual resulta

 O(1) si a < 1

T (n) =  O (log b n) si a = 1
O(n logba ) si a > 1

El algoritmo de búsqueda binaria cae en el caso a=1 y b=2, pues se reduce un


problema de tamaño n a un problema de tamaño n/2 con un costo de
combinación igual a una constante.

Demostración:
T(1) = 1
T(b) = a.T(1)+1 = a+1
T(b2) = a.T(b)+1 = a(a+1)+1 = a2+a+1
T(b3) = a.T(b2)+1 = a(a2+a+1)+1 = a3+a2+a+1

k
T(bk) = a.T(bk-1)+1 = ∑a
i =0
i

Para expresarlo en función de n, igualamos n=bk  k = logbn. Sustituyendo:

k logbn
k
T(b ) = ∑a
i =0
i
 T(n)= ∑a
i =0
i

Analicemos ahora los tres casos:

log bn
a=1: en este caso T(n)= ∑1  T(n)= log
i =0
i n
b

logbn
a>1: T(n)= ∑a
i =0
i
. Esto es la serie geométrica con razón a, lo cual tiene como

resultado:

a logb +1 − 1 a.a logb − 1 a.n logb − 1


n n a
a a
T(n) = = = <= 2* n logb =O( n logb )
a −1 a −1 a −1

a.a logb − 1
n
logbn
a<1: T(n)= ∑ a . De manera similar al caso anterior, esto es
i
. Es claro
i =0 a −1
a.a logb − 1
n

log bn
que si a<1, entonces a <1 también. Por lo tanto < 2 = O(1).
a −1

En casos menos comunes, el costo de combinar las soluciones puede ser nk,
con k un entero mayor a 0. En este caso se puede plantear
Algoritmos y Estructuras de Datos, Universidad Central de Venezuela, Escuela de Computación. Prof. Rhadamés Carmona
__________________________________________________________________________________________________________

T(1) = 1
T(n) = a.T(n/b)+nk, si n>1

Lo cual resulta

O (n k ) si a < b k

T (n) = O (n k log b n) si a = b k
O(n logba ) si a > b k

Otras fórmulas maestras para algoritmos recursivos, pero no por


partición

Estos se aplican por ejemplo para factorial, fibonacci, etc.

Ejemplo 1:

T(n) = 1 si n≤n0
T(n) = a*f(n-c)+1 si n>n0

Demuestre que

 O(1) si a < 1

T (n) =  O(n) si a = 1
O(a n / c ) si a > 1

Ejemplo 2:

T(n) = 1 si n≤n0
T(n) = a*f(n-c)+n si n>n0

Demuestre que

 O ( n) si a < 1

T (n) = O(n 2 ) si a = 1
 O(a n / c ) si a > 1

Ejercicios:
- Hallar el orden de complejidad en tiempo de la función potencia xy, con
el algoritmo divide y conquista. Demuestre que es O(Log2n)
- Hallar la complejidad en tiempo de búsqueda binaria. Demuestre que es
O(log2n)
- Hallar la complejidad en tiempo de Quick Sort y Selección Directa.
Demuestre que en peor de los casos QuickSort es O(n2), aunque en el
caso promedio es O(nlog2n). Demuestre que Selección Directa es O(n2).
- Demuestre que Fibonacci en su implementación recursiva es de orden
exponencial, mediante la siguiente recurrencia en el cálculo del tiempo
Algoritmos y Estructuras de Datos, Universidad Central de Venezuela, Escuela de Computación. Prof. Rhadamés Carmona
__________________________________________________________________________________________________________

T(n) = 1, si n<2

T(n) = T(n-2)+T(n-1)+1, si n>=2

- Demuestre que factorial es lineal O(n)