Sei sulla pagina 1di 7

Introducción a la complejidad. Notación O.

1-Fundamentos para el cálculo de complejidad

Un problema se puede resolver aplicando diferentes algoritmos funcionalmente equivalentes, esto


es, algoritmos que reciben las mismas entradas y producen la misma salida, pero varían en el
proceso o la lógica de los pasos que realiza. Evidentemente desearíamos seleccionar el mejor de
ellos. Esto plantea la pregunta: ¿Cómo se debe elegir el mejor algoritmo?.

La respuesta a esta pregunta va a depender de una serie de factores. Uno de ellos es la naturaleza
del problema a resolver. Si este es simple y con pocos datos, es posible decidirse por el algoritmo
más sencillo de codificar, o por uno ya existente, sin preocuparse demasiado por sus propiedades
teóricas. En cambio, si el problema a resolver es complejo, con muchos casos y con una cantidad
de datos que tiende a crecer, es necesario seleccionar de una manera más cuidadosa, aplicando
un proceso de análisis más exhaustivo.

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.

En computación hay dos recursos que son escasos: memoria(espacio) y tiempo. Se puede utilizar
indistintamente el consumo de uno de estos recursos como métrico para evaluar la eficiencia del
algoritmo. La eficiencia mide el uso de los recursos del computador por un algoritmo, como 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).

Para efectos del análisis de un algoritmo se define la complejidad como la relación que existe
entre el consumo de recursos y el número de datos que se procesan. En sentido estricto, la
complejidad define el consumo de recursos como una función del tamaño de los datos a procesar.

En un análisis teórico de algoritmos, es común calcular su complejidad en un sentido asintótico, es


decir, para un tamaño de entrada n suficientemente grande. La notación ”O grande” (Big O) es
una notación para expresar esto.

Por ejemplo, una búsqueda binaria se dice que se ejecuta en un número de pasos proporcional al
logaritmo de la longitud de la estructura de datos donde se realiza la búsqueda, ´o en O(log(n)),
dicho coloquialmente como tiempo logarítmico.--> IR A PUNTO 2

Definición formal :

Ejemplo f(x) =x2(x al cuadrado) y g(x)= e. DIBUJA LA GRÁFICA DE ESTO


Aplicando la definición de que: f(x) =0(g(x)) lo que es equivalente a

x2=0(ex), se traduce en que x2 es de orden ex si y solo si existe una constante positiva cE R+ tal
que para todo n>n0 se cumpla que

x2<c.ex

Esto también refleja una dominancia de funciones donde la función x2 esta acotada superiormente
por un múltiplo de la función x2.

Entonces la expresión f(x) =0(g(x)) o x2=0(ex) también denota una dominancia de funciones donde
la función f (x2) esta acotada superiormente por un múltiplo de la función g, que en este caso es
(ex).

2-Análisis de complejidad de Algoritmos

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). El análisis de complejidad está relacionado con la eficiencia del
programa.

Definición formal: Se considerará la entrada de datos al programa como el factor para el cálculo
de su complejidad. Así, la complejidad T(n) de un algoritmo es de O(f(n)) si T, f : N ! R+ y 9 c 2 R+
y n0 2 N tal que 8n > n0 se cumpla T(n)

Entre los objetivos de un análisis de complejidad son:

 Cuantificar las medidas físicas: "tiempo de ejecución y espacio de memoria" y comparar distintos
algoritmos que resuelven el mismo problema.
 Proporcionar un criterio de medición objetivo que permita comparar algoritmos y determinar cuál
de ellos es mejor. Los criterios para evaluar algoritmos son diversos: sencillez, eficiencia,
portabilidad, eficacia, robustez, etc.

De acuerdo al recurso que se mide( espacio o tiempo) se definen dos tipos de complejidad:

Complejidad en tiempo o temporal: Se define como la relación que existe entre el tiempo de
ejecución de un algoritmo y el número de datos que procesa.

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.

a.- 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)

b.- 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))

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.

3- 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.
a 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
cuán grande puede ser el mismo

 Determinar cuánto 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, cuál será el
más eficiente en teoría (el que tiene menor tasa de crecimiento).

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  ...

Cálculo de la complejidad en tiempo de un algoritmo: Calcular el tiempo de ejecución de


un programa arbitrario, aunque sólo sea un aproximación a un factor constante, puede ser un
problema matemático complejo. Sin embargo, en la práctica esto suele ser más sencillo; basta con
aplicar unos cuantos principios básicos. Antes de presentar estos principios, es importante
aprender a sumar y a multiplicar en notación asintótica.

La notación asintótica es una notación matemática utilizada para representar la complejidad en


tiempo cuando el tamaño de los datos n tiende al infinito.

4- Regla de la suma
Sean T1(n) y T2(n) las funciones de complejidad para ejecutar dos instrucciones P1 y P2 de
complejidad T1(n) = O(f(n)) y T2(n) = O(g(n)) respectivamente, la secuencia de instrucciones P1 y
P2 es T1(n) + T2(n) = O(Maxf(n), g(n)).

Regla del Producto


Se define como:

T1(n) = O(f(n)) ^ T2(n) = O(g(n)) = O(g(n)) ) T1(n).T2(n) = O(f(n).g(n))


Sean c, d 2 R+ se puede derivar que:
• T(n) = c ) T(n) = O(1)
• T(n) = c + f(n) ) T(n) = O(f(n))
• T1(n) = c.f (n) ) T1(n) = O(f(n))
• T1(n) = c.f (n) + d ) T1(n) = O(f(n))
• T1(n) = O(nk) ^ T2(n) = O(nk+1) ) T1(n) + T2(n) = O(nk+1)
• T(n) = c.nd ) T(n) = O(nd)
• T(n) = Pk(n) ) T(n) = O(nk), donde Pk(n) es un polinomio de grado k _ 0
• T1(n) = Ln(n) ^ T2(n) = nk ^ k > 1 ) T1(n) + T2(n) = O(nk)
• T1(n) = rn ^ T2(n) = Pk(n) ^ r > 1 ) T1(n) + T2(n) = O(rn)
La función de complejidad en tiempo de una instrucción de asignación simple es una constante
independiente del tamaño de la entrada de datos ) O(1).

La operación de E/S es T(n) = c, por lo tanto es O(1). De la regla de la suma se deriva que la
complejidad de una secuencia de k instrucciones con Ti(n) = O(fi(n)) es Pk i=1 Ti(n) = O(Maxf1(n),
f2(n), ..., fk(n)).

La complejidad de una selección viene dada por la complejidad de ejecutar la condición y las
instrucciones. Del mismo modo, en un ciclo iterativo la complejidad es la sobre del cuerpo de
iteración más la evaluación de la condición de terminación del ciclo.

Si un programa tiene procedimientos recursivos, se calcula cada tiempo de ejecución a la vez y


aplicando la ecuación de recurrencia correspondiente.
IR A CLASE 09-7COMPLEJIDAD EN TIEMPO DE ALGORITMOS ITERATIVO Y RECURSIVO

Complejidad en espacio o Complejidad en Memoria


También conocida como Complejidad en memoria o espacial. Se define como la relación que
existe entre la cantidad de memoria necesaria para la ejecución del algoritmo y el número de datos
que procesa.

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 se discuten diferentes 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

e) Para un registro variante

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.

Type Registro Reg

C1: T1; C2: T2; ...; Ck:Tk;

In case of Discri:TipoDisc

<Caso1>: <Datos1>;

<Caso2>: <Datos2>;

...

<Cason>: <Datosn>

EndCase

EndRecord

 Cm(Ti)  Max{Cm( Datos ),..., Cm( Datos )}


k

Cm(Reg) = Cm(TipoDisc) + 1 n
i 1

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 o constantes de dicho tipo.

Potrebbero piacerti anche