Sei sulla pagina 1di 31

APUNTES PARA ALGORITMOS Y ESTRUCTURAS DE DATOS II

Por favor, reportar errores, omisiones y sugerencias a dfridlender@gmail.com


Consultar la pgina de la materia:
http://cs.famaf.unc.edu.ar/wiki/doku.php?id=algo2:main

Introduccin
Introduccin a los Algoritmos y Algoritmos y Estructuras
de Datos I el nfasis estaba puesto en qu hace un programa. Se especicaba con
En las materias

detalle las pre- y post- condiciones que el programa deba satisfacer y se derivaba un
programa que satisciera dicha especicacin.
En

Algoritmos y Estructuras de Datos II, el nfasis estar puesto en cmo

resuelve el programa el problema especicado. Desarrollaremos instrumentos que nos


permitan comparar diferentes programas que resuelven un mismo problema.
Uno de los aspectos que es importante considerar al comparar algoritmos es el referido
a los recursos que el programa necesita para ejecutarse:

tiempo de procesamiento,

espacio de memoria, tiempo de utilizacin de un dispositivo. El estudio de la necesidad


de recursos de un programa o algoritmo se llama

anlisis del algoritmo y lo que dicho

eciencia del mismo.


eciencia en espacio de un programa determinar cunta memoria
debe tener el equipo para su correcta ejecucin. El anlisis de eciencia en tiempo
anlisis determina es la
El anlisis de

de un programa nos permite estimar el tiempo que tardar el mismo en completar su


ejecucin.
Es habitual poner mayor nfasis en el anlis de eciencia en tiempo que en el de la
eciencia en espacio, pero ambos son relevantes para entender el comportamiento de
un algoritmo.
Considere el siguiente

problema del pintor

Un pintor tarda una hora y media en pintar una pared de 3 metros de


largo. Cunto tardar en pintar una de 5 metros de largo?
Sabemos cmo resolver este tipo de problemas: si pintar una pared de 3 metros de
largo requiere una hora y media, pintar cada metro est insumiendo 30 minutos. Por
lo tanto, pintar 5 metros requerir dos horas y media. La correccin de este sencillo
razonamiento se debe a que sabemos que el tiempo que lleva pintar una pared (de altura
ja) es

proporcional a su longitud. Esto lo podemos deducir de la manera habitual,

conocida, de pintar una pared.


Podemos hacernos preguntas similares al abordar otros problemas.
considere el siguiente

Por ejemplo,

problema del bibliotecario

Un bibliotecario tarda un da en ordenar alfabticamente una biblioteca


con 1000 expedientes. Cunto tardar en ordenar una con 2000 expedientes?

APUNTES PARA ALGORITMOS Y ESTRUCTURAS DE DATOS II


Con un razonamiento similar al utilizado para el problema del pintor, podramos

rpidamente concluir que el bibliotecario tardar dos das. Pero es correcto razonar
as?

El tiempo que lleva ordenar una biblioteca, es proporcional a la cantidad de

expedientes?

Cunto ms trabajo es ordenar 2000 expedientes que 1000?

Es el

doble? Para contestar estas preguntas tenemos que analizar la manera de ordenar. No
contamos -como en el caso de pintar- con una manera nica, habitual o universal de
ordenar expedientes.
Consideremos nuevamente problemas ms familiares, como los siguientes
(1) Un pintor demora una hora y media en pintar una pared cuadrada de 3 metros
de lado. Cunto demorar en pintar una de 5 metros de lado?
(2) Si lleva cinco horas inar un globo aerosttico esfrico de 2 metros de dimetro,
cunto llevar inar uno de 4 metros de dimetro?
El primero de ellos se parece al problema del pintor, salvo que ahora la pared es
cuadrada. Al pensarlo con detenimiento, descubrimos que el tiempo que lleva pintar la
pared cuadrada, no es

proporcional a la longitud del lado, sino a la supercie de la

pared. La magnitud de la tarea de pintar la pared, est determinada por su supercie.


Como la supercie de un cuadrado de 3 metros de lado es 9 metros cuadrados y se pinta
en una hora y media, pintar cada metro cuadrado lleva 10 minutos. La supercie de
un cuadrado de 5 metros de lado es 25 metros cuadrados, pintarlos lleva 250 minutos,
o sea 4 horas y 10 minutos.
Para el segundo problema, como el tiempo necesario para inar el globo, es decir, la
magnitud de la tarea de inarlo, es
el globo una esfera- es

proporcional a su volumen, que a su vez -por ser


3

V = d6
23 = 8. Inar

proporcional al cubo del dimetro (su frmula es

al duplicarse el dimetro de 2 a 4 metros, el volumen se multiplica por

),
el

globo de 4 metros de dimetro llevar entonces 40 horas.

Estos dos problemas sirven para mostrar que para resolverlos es fundamental determinar a qu es

proporcional la magnitud de la tarea. Tambin resulta destacable,

que no es imprescindible conocer la frmula exacta. Por ejemplo, el problema del globo

aerosttico se pudo resolver ignorando el factor


en la frmula del volumen de la esfera.
6
Slo se us que el volumen es proporcional al cubo del dimetro.
Concluimos entonces que para resolver el problema del bibliotecario es necesario
determinar

a qu es proporcional la magnitud de la tarea de ordenar expe-

dientes. Para ello es imprescindible estudiar mtodos de ordenacin.


En lo que sigue, asumiremos la existencia de ciertos elementos o items a ordenar, que
existe una relacin de orden total entre ellos, y que deben ordenarse de menor a mayor.
No se asume que los items sean diferentes, pueden contener repeticiones.

Ordenacin por seleccin. El mtodo de ordenacin ms sencillo -aunque no el


ms rpido- es el de ordenacin por seleccin. Como su nombre lo indica, ordena
seleccionando elementos. En un primer paso selecciona el mnimo elemento y lo coloca
en el primer lugar, separndolo de este modo del resto de los elementos. En un segundo
paso vuelve a seleccionar el mnimo elemento (de los restantes) y lo coloca en el segundo
lugar, separndolo del resto. En el paso
1

i,

habiendo ordenado ya los

i1

elementos

Ejercicio: comprobar realizando las cuentas detalladamente, la correccin de este resultado.

APUNTES PARA ALGORITMOS Y ESTRUCTURAS DE DATOS II

mnimos, vuelve a seleccionar el mnimo y lo coloca en el lugar i, separndolo del resto.


Y contina de esta manera hasta colocar el ltimo de los elementos en su lugar correcto.
Para escribir este algoritmo con precisin, utilizaremos pseudo-cdigo: un lenguaje
algortmico de fcil traduccin a distintos lenguajes de programacin. Asumimos que
los elementos a ordenar vienen dados en un arreglo (llamado a) que el algoritmo de
ordenacin debe modicar para que al nalizar la ejecucin del algoritmo, dicho arreglo
contenga los mismos elementos que el original, pero ordenados de menor a mayor.
Durante la ejecucin de la ordenacin por seleccin, podemos pensar que el arreglo
est dividido en dos partes: una primera parte a[1,i) (inicialmente vaca pero que crece
a cada paso del algoritmo) que contiene los elementos que ya han sido seleccionados, es
decir, los

i1

menores elementos del arreglo en orden creciente; y una segunda parte

a[i,n] (inicialmente es la totalidad del arreglo pero decrece a cada paso) con el resto de
los elementos an no seleccionados.
a[1,i)

a[i,n]

mnimos ordenados

an no seleccionados

-

a
1

i -1

n-1

De acuerdo a lo explicado, el algoritmo de ordenacin por seleccin consiste de un


ciclo en el que se cumplen las siguientes condiciones como invariante:

el arreglo a es una permutacin del original,


un segmento inicial a[1,i) del arreglo est ordenado, y
dicho segmento contiene los elementos mnimos del arreglo.

El algoritmo de ordenacin por seleccin, puede escribirse en pseudo-cdigo de la


siguiente manera:
{Pre: n

a = A}

proc selection_sort (in/out a: array[1..n] of T)


var i, minp: nat
i:= 1

{Inv: a es permutacin de A

a[1,i) est ordenado

{ los elementos de a[1,i) son menores o iguales a los de a[i,n]}

do i

<

minp:= min_pos_from(a,i)
swap(a,i,minp)
i:= i+1

od
end proc
{Post: a est ordenado y es permutacin de A}
La precondicin denota por A el valor inicial del arreglo a. La postcondicin dice que
al nalizar la ejecucin, el arreglo a es una permutacin de su valor inicial, y que est
ordenado. Estas dos condiciones especican el problema de ordenacin: un algoritmo
de ordenacin es uno que devuelve una permutacin ordenada de su entrada.
El invariante es el mismo que se explic ms arriba.
Cuando un algoritmo realiza modicaciones (como en este caso al arreglo a) lo llamamos procedimiento (proc). Un procedimiento puede recibir parmetros con datos

APUNTES PARA ALGORITMOS Y ESTRUCTURAS DE DATOS II

in), con variables para inicializar (prejo out), o con variables


para modicar (prejo in/out).

(indicadas con el prejo

Cuando un algoritmo no realiza modicaciones sino que simplemente calcula frmulas


o resultados, lo llamamos funcin (fun).

Como la funcin no realiza modicaciones,

todos sus parmetros son meramente datos, por consiguiente el prejo es innecesario.
El procedimiento selection_sort utiliza dos variables: la variable i, cuyo rol se advierte
ya en las explicaciones del algoritmo, y la variable minp que tiene como propsito
sealar la posicin donde se encuentra el nuevo elemento seleccionado. Para calcularlo
se utiliza la funcin min_pos_from que al aplicarse al arreglo a y a la variable i devuelve
la posicin del mnimo del segmento de arreglo a[i,n]. Luego de calcularlo se invoca al
procedimiento swap para intercambiarlo con el que se encuentra en la posicin i. Este
intercambio tiene dos propsitos: por un lado, coloca al elemento seleccionado en su
posicin correspondiente como elemento ya ordenado (la i), y por el otro, evita que el
elemento que ocupaba la celda i se pierda, consiguindole una nueva celda provisoria:
la que justamente se libera por haber estado ocupada por el elemento seleccionado.
{Pre: a = A

i,j

n }

proc swap (in/out a: array[1..n] of T, in i,j: nat)


var tmp: T
tmp:= a[i]
a[i]:= a[j]
a[j]:= tmp

end proc
{Post: a[i] = A[j]

a[j] = A[i]

k. k

6{i,j}

a[k]=A[k]}

La postcondicin del procedimiento swap implica que el mismo produce una permutacin del arreglo que recibe como parmetro.

Como el algoritmo selection_sort

slo modica el arreglo a invocando al procedimiento swap reiteradamente, la condicin que establece que el arreglo ordenado debe ser una permutacin del inicial queda
sta es una observacin interesante: si uno se limita a modicar
el arreglo slo invocando al procedimiento swap, necesariamente se tendr
siempre una permutacin del arreglo inicial.

garantizada.

Como ya se explic, la funcin min_pos_from aplicada a los parmetros a e i debe


devolver la posicin del mnimo del segmento de arreglo a[i,n].

Como el objetivo es

intercambiarlo con el que se encuentra en la posicin i del arreglo, es necesario devolver


la

posicin donde ese mnimo se encuentra.

{Pre: 0

<i

n}

fun min_pos_from (a: array[1..n] of T, i: nat) ret minp: nat


var j: nat
minp:= i
j:= i+1

do j

{Inv: a[minp] es el mnimo de a[i,j)}


n

if a[j]

<

a[minp]

then minp:= j 

j:= j+1

od
end fun
{Post: a[minp] es el mnimo de a[i,n]}

APUNTES PARA ALGORITMOS Y ESTRUCTURAS DE DATOS II

La variable minp que se utiliza para devolver el resultado se declara en el encabezado


de la funcin por medio de la clusula

ret.

Por qu la funcin min_pos_from no devuelve el mnimo sino su posicin?


Por qu dice i

<

n en vez de i

n en la condicin del

do del procedimiento selec-

tion_sort?
Por qu se llama selection_sort?

El comando for. En el algoritmo presentado los dos ciclos do se utilizan para repetir
ciertas acciones para cada valor de las variables i (o j) desde un valor (cota inferior) a
otro (cota superior). La variable i (o j) recibe el nombre de ndice. Se observa que los
ndices slo son modicados al nal de cada ciclo, cuando se los incrementa. En estas
situaciones, utilizaremos una notacin ms compacta.

En primer lugar, omitiremos

declarar el ndice. Adems, en vez de escribir


k:= n

do k

C
k:= k+1

od
escribiremos

for k:= n to m do C od
Para que esta notacin tenga sentido es fundamental que k no sea modicado en el
cuerpo C del ciclo. El propsito de esta notacin es hacer ms evidente que C se ejecuta
una vez para cada valor de k desde n hasta m. Observar que si n es mayor que m, no
se ejecuta el cuerpo del ciclo

for ni siquiera una vez.

A veces es deseable que el primer valor del ndice sea la cota superior y que gradualmente se decremente hasta alcanzar la cota inferior. Para esos casos, reemplazamos
por

to

downto.

Por ejemplo, la funcin factorial puede programarse de cualquiera de las formas siguientes
{Pre: 0

n}

fun fact (n: nat) ret r: nat


r:= 1

for i:= 1 to n do
r:= r * i

od
end fun
{Post: r = n!}
que realiza la multiplicacin

fun fact (n: nat) ret r: nat


r:= 1

for i:= n downto 1 do


r:= r * i

od
end fun

((((1 1) 2) 3) . . .) n

APUNTES PARA ALGORITMOS Y ESTRUCTURAS DE DATOS II

que realiza la multiplicacin


Utilizando ciclos
{Pre: n

((((1 n) (n 1)) (n 2)) . . .) 1.

for el procedimiento selection_sort puede escribirse

a = A}

proc selection_sort (in/out a: array[1..n] of T)


var minp: nat
for i:= 1 to n-1 do
{Inv: a es permutacin de A

a[1,i) est ordenado

{ los elementos de a[1,i) son menores o iguales a los de a[i,n]}


minp:= min_pos_from(a,i)
swap(a,i,minp)

od
end proc
{Post: a est ordenado y es permutacin de A}
Asimismo, la funcin de
{Pre: 0

<i

seleccin min_pos_from se puede escribir

n}

fun min_pos_from (a: array[1..n] of T, i: nat) ret minp: nat


minp:= i

for j:= i+1 to n do


if a[j] < a[minp] then minp:= j 
od
end fun

{Inv: a[minp] es el mnimo de a[i,j)}

{Post: a[minp] es el mnimo de a[i,n]}


Ahora que tenemos un algoritmo de ordenacin, podemos intentar resolver el problema del bibliotecario. Para ello, debemos analizar el algoritmo con el propsito de
averiguar cunto ms trabajo implica ordenar 2000 expedientes que 1000 expedientes.
Debemos hallar algn parmetro respecto del cual el trabajo sea proporcional. Con el
propsito de medir el trabajo que realiza el algoritmo, volvemos nuestra atencin sobre el
mismo y observamos que contiene distintas tareas u operaciones: realiza asignaciones,
sumas, comparaciones, llamadas a funciones y procedimientos, ejecuciones de ciclos,
intercambios.
Una posibilidad sera contabilizar todas estas operaciones. Sera complicado ya que no
todas ellas son equivalentes, no todas ellas requieren el mismo tiempo para realizarse.
Deberamos contabilizarlas por separado.

Realizaremos un anlisis ms sencillo, que

consiste en elegir una sola operacin y contar cuntas veces se repite.

Por supuesto

que para que el anlisis sea correcto, debemos ser criteriosos al elegir dicha operacin:
debe ser una que sea representativa del trabajo que realiza el algoritmo. Para ello se
requiere:

que sea constante (es decir, que el trabajo que implica realizar dicha operacin
debe ser el mismo, independientemente del nmero de celdas del arreglo),

que ninguna otra operacin se repita ms que la elegida; ms precisamente,


toda otra operacin puede repetirse a lo sumo en forma proporcional al modo
en que se repite la operacin elegida.

Por ejemplo, en el caso que nos ocupa, el procedimiento selection_sort contiene


un ciclo; debemos buscar dentro de l la tarea a elegir ya que all se encuentran las

APUNTES PARA ALGORITMOS Y ESTRUCTURAS DE DATOS II


operaciones que ms se repiten.

El

for contiene implcitamente operaciones que se

repiten: se incrementa el ndice y se chequea la condicin luego de cada ejecucin del


cuerpo del mismo. Adems, cada vez que se ejecuta el cuerpo se invoca a la funcin
min_pos_from y al procedimiento swap. Cada una de estas operaciones se realiza una
vez para cada valor de i, es decir, un total de n-1 veces.
Todas estas operaciones son constantes, con la sola excepcin de la ejecucin de
la funcin min_pos_from.

En efecto, la funcin min_pos_from contiene a su vez

for que requiere la repeticin de ciertas


for contiene incrementos y chequeos de condicin de

(adems de la inicializacin de minp) un ciclo


operaciones.

Como todo ciclo

salida implcitos luego de cada ejecucin. Adems, cada vez que se ejecuta el ciclo se
realizan accesos al arreglo, una comparacin entre celdas del mismo y posiblemente una
asignacin a minp. Todas ests operaciones (salvo la asignacin a minp que requiere
que la condicin del

if sea verdadera para ejecutarse) se realizan para cada valor de j,

es decir, un total de n-i veces.


Cualquiera de estas operaciones es representativa del comportamiento del algoritmo.
Adems de ser constantes, son las que ms se repiten porque fueron encontradas en el
ciclo

for de la funcin min_pos_from, que a su vez se invoca desde el ciclo for del

procedimiento selection_sort.

Elegimos, como es habitual al analizar algoritmos de

ordenacin, la comparacin entre elementos del arreglo a[j] < a[minp] que se encuentra
en la condicin del

if.

Como ya observamos, la misma se realiza n-i veces cuando

se invoca la funcin select con el parmetro i.

Tambin observamos que la funcin

min_pos_from se invoca n-1 veces, cada vez con distintos valores de i

{1. . . n-1}.

Entonces, el nmero total de veces que se realiza dicha comparacin es n-1 + n-2 +
n(n1)
n2
veces, o distribuyendo,
n2 veces.
. . . + 1. Esto es, un total de
2
2

Resolviendo el problema del bibliotecario. Este anlisis nos permite concluir que
el trabajo que realiza el procedimiento selection_sort para ordenar un arreglo de longin2
tud n es proporcional a
n2 . Para el problema del bibliotecario, entonces, tenemos que
2
ordenar 1000 expedientes con este algoritmo involucra 499500 comparaciones mientras
que ordenar 2000, involucra 1999000 comparaciones.

Es decir, ordenar 2000 expedi-

entes es aproximadamente 4 veces ms trabajoso que ordenar 1000. Si tarda un da en


ordenar 1000, tardar 4 en ordenar 2000.

n2
n2 , es innecesariamente complicada
2
para resolver el problema. El trmino que predomina en esta ecuacin es el de grado
n2
2. Podramos decir que el trabajo de selection_sort es proporcional a
. Por lo tanto,
2
2
tambin es proporcional a n .
Puede observar que la frmula utilizada,

Repetimos el clculo con esta frmula ms sencilla: ordenar 1000 expedientes involucra 1 milln de comparaciones, y ordenar 2000 involucra 4 millones. Arribamos a la
misma conclusin (que ordenar 2000 expedientes es 4 veces ms trabajo que ordenar
1000) que con la frmula innecesariamente complicada.

Por esta razn es habitual

simplicar la notacin lo ms posible, ignorando constantes multiplicativas (en nuestro


1
caso, ) y trminos de crecimiento despreciable comparado con otros (en nuestro caso,
2
n
n2
que crece ms lentamente que
).
2
2

APUNTES PARA ALGORITMOS Y ESTRUCTURAS DE DATOS II

Nmero de operaciones de un comando. Frecuentemente vamos a querer contar o


estimar el nmero de operaciones que se realizan al ejecutarse un comando determinado.
Como no todas las operaciones son igualmente signicativas para la performance de un
programa dado, frecuentemente se cuentan slo operaciones de un cierto tipo. La cuenta
que se realice depender de las operaciones que se pretenden contar y del comando que
se est analizando.
Si bien lo habitual es contabilizar las operaciones intuitivamente como hicimos en
el caso de selection_sort, basndonos en comprender cmo se ejecuta el algoritmo, a
continuacin se da una descripcin informal para contar metdicamente las operaciones
que se realizan durante la ejecucin de un comando dado.
Si queremos contar el nmero de operaciones que se realizan al ejecutarse la secuencia
de comandos C1 ;C2 ;. . . ;Cn , sumamos las operaciones que se realizan durante la ejecucin
de cada comando de la secuencia:
ops(C1 ;C2 ;. . . ;Cn )

= ops(C1 ) + ops(C2 ) + . . . + ops(Cn )

o ms brevemente
ops(C1 ;C2 ;. . . ;Cn )

n
X

ops(Ci )

i=1

skip representa una secuencia vaca de comandos, ops(skip) = 0.


El ciclo for k:= n to m do C(k) od puede verse intuitivamente como una abreviatura

En particular, como

de la secuencia de comandos C(n);C(n+1);. . . ;C(m).


nmero de operaciones de un ciclo

Por ello, si queremos contar el

for, sumamos las operaciones que se realizan en cada

ejecucin del cuerpo del mismo. Podemos utilizar por ejemplo la frmula:

ops(for k:= n

to m do C(k) od)

m
X

ops(C(k))

k=n
Notar que en el lado derecho de la ecuacin hemos utilizado

para referirnos,

respectivamente, a los valores de las expresiones n y m.


Esta frmula no tiene en cuenta las operaciones necesarias para evaluar n y m ni
las necesarias para inicializar y modicar k y para compararlo con el valor de m. La
frmula:
ops(for k:= n

to m do C(k) od)

= ops(n) + ops(m) +

m
X

ops(C(k))

k=n
s tiene en cuenta las operaciones necesarias para evaluar n y m. Es importante tenerla
en cuenta, especialmente cuando n o m son expresiones complejas, como llamadas a
funciones tales como factorial o bonacci, por dar tan solo algunos ejemplos.
2

En realidad, como n y m no necesariamente son constantes, el ciclo for no puede reemplazarse

en el cdigo por C(n);C(n+1);. . . ;C(m). Por ejemplo, en el caso de la ordenacin por seleccin, cada
llamada a la funcin min_pos_from, es con un parmetro i diferente. Por lo tanto, el comando for en
el cuerpo de dicha funcin itera, para cada i, un nmero diferente de veces.

APUNTES PARA ALGORITMOS Y ESTRUCTURAS DE DATOS II

La frmula

ops(for k:= n

= (m n + 2) ops(k m) +

to m do C(k) od)

m
X

ops(C(k))

k=n
no tiene en cuenta las operaciones necesarias para evaluar n y m pero s las necesarias
para comparar k con m. Observar que se contabilizan
se utiliza

mn+2

comparaciones,

pero

en vez de m dado que no se evala la expresin m cada vez que se ejecuta

el cuerpo del

for sino una sola vez para toda la ejecucin del comando for.

Se deja como ejercicio proponer una frmula que tenga en cuenta todas las operaciones
que se requieren para ejecutar el

for, incluso la inicializacin y las modicaciones de k.

Si queremos contar el nmero de operaciones que se realizan al ejecutarse el comando


condicional

if b then c else d , debemos considerar dos casos: b verdadero b falso:


ops(if b

then C else D )

Nuevamente convenimos en utilizar

b
b

ops(b)+ops(C)
ops(b)+ops(D)

= verdadero
= falso

para referirnos al valor de la expresin b.

Si queremos contar el nmero de operaciones que se realizan al ejecutarse la asignacin


x:= e, tenemos 2 frmulas dependiendo de si queremos o no contar la asignacin en s
como una operacin. En el primer caso tenemos ops(x:=e) = ops(e)+1 mientras que
en el segundo tenemos simplemente ops(x:=e) = ops(e).
En los casos del comando condicional y de la asignacin, ops(b) y ops(e) representan
los nmeros de operaciones necesarios para evaluar las expresiones b y e.
Para contar el nmero de operaciones que se realizan al ejecutarse un ciclo

do es

necesario establecer el nmero de veces que se ejecutar el cuerpo del ciclo. No siempre
es fcil obtener dicho nmero.

Una vez determinado este nmero, llammosle

obtiene una frmula parecida a la del

ops(do b

n,

se

for,

od)

= ops(b) +

n
X

dk

k=1
donde

dk

es el nmero de operaciones que realiza la

k -sima

ejecucin del cuerpo C del

ciclo y la subsiguiente evaluacin de la condicin o guarda b.


Finalmente, se deja como ejercicio denir las ecuaciones correspondientes a operadores lgicos, relacionales y aritmticos, que seguramente se parecern a las de la
asignacin explicada ms arriba.

Nmero de comparaciones de la ordenacin por seleccin. A modo de ejemplo,


contemos el nmero de comparaciones entre elementos del arreglo a en el algoritmo de ordenacin por seleccin. Ya lo hicimos intuitivamente, ahora utilizaremos las
ecuaciones que denen la funcin ops. Los clculos se encuentran a continuacin.

Por qu se suma 2?

10

APUNTES PARA ALGORITMOS Y ESTRUCTURAS DE DATOS II

ops(selection_sort(a))

P
= n-1
ops(min_pos_from(a,i))
Pi=1
n-1 Pn
= i=1
ops(a[j] < a[minp])
j=i+1
Pn-1 Pn
= i=1
1
j=i+1
P
= n-1
(n-i)
i=1
Pn-1
= i=1 i
=
=

n*(n-1)
2
n2
n

2
2

La expresin que obtenemos -idntica a la obtenida apelando exclusivamente a nuestra


intuicin- indica que el nmero de comparaciones de la ordenacin por seleccin es del
orden de n2 , terminologa que haremos ms precisa pronto. Intuitivamente, el nmero
2
de comparaciones de la ordenacin por seleccin es proporcional a n .

Nmero de intercambios (swaps) de la ordenacin por seleccin. Observemos


que slo se realizan swaps durante la ejecucin del procedimiento selection_sort y no
durante la de la funcin min_pos_from. Por cada valor de i se hace exactamente un
intercambio (swap) entre a[i] y a[minp] al nal del cuerpo del
Como i toma

n1

valores, son

n1

for de selection_sort.

intercambios.

Utilizando las frmulas se obtiene lo mismo


ops(selection_sort(a))

P
= n-1
ops(swap(a,i,minp))
Pi=1
n-1
= i=1 1
= n-1

Es decir que el nmero de intercambios (swaps) de la ordenacin por seleccin

es del

orden de n, es decir, es proporcional a n.


Esto conrma que la operacin de intercambio no es representativa del compor2

tamiento de la ordenacin por seleccin porque se realizan del orden de n, contra n


comparaciones.

Otro ejemplo: ordenacin por insercin. No siempre es posible calcular el nmero


exacto de operaciones, dado que puede depender de los valores de entrada. El siguiente
algoritmo de ordenacin por insercin es un ejemplo de ello.
La ordenacin por insercin, frecuentemente utilizada en juegos de cartas que requieran tener un gran nmero de cartas en la mano ordenadas de manera de que sean
fcilmente localizables, procede de la siguiente manera. Al igual que la ordenacin por
seleccin, mantiene como invariante que un segmento inicial del arreglo a est ordenado,
pero a diferencia de aqulla, sus elementos no son menores a los del segmento restante
de a. Paso a paso se inserta un nuevo elemento en el segmento ordenado hasta que al
nalizar, dicho segmento es la totalidad del arreglo.

APUNTES PARA ALGORITMOS Y ESTRUCTURAS DE DATOS II


a[1,i)

a[i,n] = A[i,n]

ordenado

11

an no insertados

-

a
1

i -1

n-1

En la ordenacin por insercin, no se elige el elemento a insertar, se inserta el primero


que an no se insert (es decir, el que se encuentra en la celda i).
{Pre: n

a = A}

proc insertion_sort (in/out a: array[1..n] of T)


for i:= 2 to n do
{Inv: a[1,i) est ordenado

a es permutacin de A}

insert(a,i)

od
end proc
{Post: a est ordenado y es permutacin de A}
El procedimiento insertion_sort es muy sencillo: simplemente llama repetidamente al
procedimiento insert, que se supone que inserta un elemento (el i-simo) en el segmento
de a[1,i) que ya est ordenado. Luego de esta insercin, el segmento ordenado tendr
un elemento ms, ser a[1,i].
El algoritmo de insercin que damos a continuacin arrastra al nuevo elemento hacia
la izquierda hasta que el mismo alcanza su posicin. Para arrastrarlo lo intercambia
con el de su izquierda tantas veces como sea necesario.

Por lo tanto, el elemento

nuevo -que originariamente se encontraba en la posicin i del arreglo- va cambiando


gradualmente de posicin (hacia la izquierda). La variable j indica en todo momento
dnde se encuentra el elemento nuevo.
{Pre: 0 < i

a = A}

proc insert (in/out a: array[1..n] of T, in i: nat) ret


j
{Inv: a[1 , i] y a[j,i] estn ordenados

j:= i

do

j > 1 a[j] < a[j 1]

a es permutacin de A}

swap(a,j-1,j)
j:= j-1

od
end proc
{Post: a[1,i] est ordenado

a es permutacin de A}

j
Para escribir el invariante del procedimiento insert denotamos por a[1 , i] la secuencia
de celdas de a desde la posicin 1 hasta la i salteando la j-sima (para

1 j i).

Como no se sabe a priori cuntos lugares hacia la izquierda ser necesario arrastrar

do en vez de for.
for del procedimiento insertion_sort comienza desde 2?

el elemento que se est insertando, debemos utilizar un ciclo


Por qu el

Nmero de comparaciones de la ordenacin por insercin. Nuevamente contamos comparaciones entre elementos del arreglo a, que en este caso son de la
forma

a[j] < a[j 1]

y slo se realizan en el procedimiento insert.

Intuitivamente,

cuando i=2 se realiza 1, cuando i=3 se realizan al menos 1 y a lo sumo 2, . . . , cuando


i=n se realizan al menos 1 y a lo sumo n-1. No podemos obtener el nmero exacto de
comparaciones ya que ste depende de cuntos lugares hacia la izquierda es necesario

12

APUNTES PARA ALGORITMOS Y ESTRUCTURAS DE DATOS II

arrastrar al nuevo elemento. En el

mejor caso (cuando el arreglo original ya est or-

denado) cada llamada a insert realiza una sola comparacin (ya que la misma devuelve

do termine) sern 1 + 1 + . . . + 1 = n-1 comparaciones (es


decir, del orden de n comparaciones). En el peor caso, (cuando el arreglo inicial est

falso haciendo que el ciclo

ordenado al revs, de mayor a menor) cada llamada a insert con parmetros a e i realiza
i-1 comparaciones (ya que el nuevo elemento debe arrastrarse i-1 pasos hasta la celda
n2
1). Las comparaciones en este caso sumarn 1 + 2 + . . . + (n-1) =
n2 (es decir,
2
2
del orden de n comparaciones).
Obtener el nmero de comparaciones en el peor caso es importante pues establece
una

cota, una garanta sobre el comportamiento del algoritmo. Cundo se da el peor

caso de la ordenacin por insercin?


Obtener el nmero de comparaciones en el mejor caso no tiene la misma importancia,
slo establece una

posibilidad sobre el comportamiento del algoritmo. Cundo se da

el mejor caso de la ordenacin por insercin?


S es importante establecer el nmero de comparaciones en el caso promedio o caso
medio ya que ste determina el comportamiento del algoritmo en la prctica. Para
calcularlo se necesitan conocimientos de probabilidades. No tiene sentido simplemente
promediar el peor caso con el mejor caso, esto dara siempre algo equivalente al peor
caso.

El nmero de comparaciones de la ordenacin por insercin en el caso promedio

asumiendo que los valores en las celdas del arreglo fueron generados al azar es del orden
2
de n (no lo demostramos porque requiere conocimientos de probabilidades).

Nmero de intercambios de la ordenacin por insercin. Para el conteo del


nmero de swaps del algoritmo de ordenacin por insercin, nuevamente se distinguen
varios casos.

En el mejor de ellos (cuando el arreglo inicial est ordenado), como la

condicin del

do ser siempre falsa no se realizar ninguna llamada al procedimiento

swap. En el peor caso (cuando el arreglo inicial est ordenado al revs), la comparacin
a[j] < a[j-1] ser siempre verdadera y se realizarn i swaps por cada llamada a insert(a,i).
n2
En total sern
n2 (es decir, del orden de n2 ) swaps. El nmero de intercambios
2
en el caso promedio (asumiendo que los valores en las celdas del arreglo fueron ge2
nerados al azar) es del orden de n (nuevamente la prueba requiere conocimientos de
probabilidades).
Este anlisis demuestra que en el caso de la ordenacin por insercin, la operacin
de intercambio es representativa del comportamiento del algoritmo en el peor caso y en
el caso promedio. No as en el mejor caso en que las comparaciones son del orden de n
pero no hay ningn intercambio.

Resolviendo el problema del bibliotecario. Si bien en el peor caso este algoritmo


se comporta igual que el de ordenacin por seleccin, presenta un mejor caso en que su
comportamiento es del orden de n. Por ello, si los expedientes estn casi ordenados, este
algoritmo har del orden de n comparaciones (y casi ningn swap). Con ese nmero
de comparaciones, ordenar 2000 expedientes llevara aproximadamente el doble que
ordenar 1000.
4

Ejercicio: por qu?

APUNTES PARA ALGORITMOS Y ESTRUCTURAS DE DATOS II

13

Sin embargo el enunciado del problema del bibliotecario no asuma que los expedientes se encontraran casi ordenados, por lo que habra que considerar el caso promedio.
2
Dijimos que el algoritmo de ordenacin por insercin es en este caso del orden de n .
Por consiguiente obtenemos la misma respuesta que para el algoritmo de ordenacin
por seleccin: ordenar 2000 expedientes es 4 veces ms trabajo que ordenar 1000 expedientes.
A pesar de ello, en virtud del mejor caso que presenta la ordenacin por insercin,
podramos armar que es preferible frente a la ordenacin por seleccin. Por ejemplo,
si un programa mantiene una base de datos a la que cada tanto se le agregan registros y
cada tanto se la ordena, puede ser muy conveniente utilizar para su ordenacin el algoritmo de ordenacin por insercin, ya que al ser ordenada la base con cierta frecuencia,
su comportamiento se asemejar a su mejor caso. No sera del todo extrao que ste
sea el caso del bibliotecario, ya que las bibliotecas acostumbran mantener sus objetos
(bastante) ordenados.

Anlisis de Algoritmos
El ejemplo ilustra varios aspectos del anlisis del comportamiento de un algoritmo:

casos: no siempre es posible contar exactamente. Se distingue entre mejor caso,


peor caso y caso promedio. El estudio del peor caso permite establecer una cota
superior, una garanta de comportamiento.

El caso medio revela el compor-

tamiento en la prctica, pero es ms difcil de establecer. El mejor caso expresa


apenas un comportamiento posible.

operacin elemental: se elige una operacin elemental y se cuenta el nmero de


veces que la misma se repite durante la ejecucin del algoritmo. sta debe:

ser de duracin constante, es decir, su duracin no debe depender del


tamao de la entrada n,

ser representativa del comportamiento, es decir, toda otra operacin debe


repetirse a lo sumo en forma proporcional a la operacin elemental elegida.
En la ordenacin por seleccin, el intercambio (swap) no era una operacin
representativa.

aproximacin: se ignoran constantes multiplicativas y los trminos que resultan


despreciables cuando n crece. Esto puede justicarse de varias maneras:

la duracin de la operacin elemental depende del hardware, mejor hardware modicara esas constantes.

uno cuenta operaciones, pero no hace precisa la unidad de tiempo, no se


sabe si cuenta segundos, das, microsegundos, aos, etc. No estando determinada la unidad, las constantes multiplicativas pierden sentido.

uno puede querer comparar 2 algoritmos cuyos anlisis fueron hechos eligiendo
operaciones elementales distintas en uno y en otro, sin saber a priori si
dichas operaciones elementales tienen igual o diferente duracin.

Pero hay que tener presente que se est haciendo una aproximacin. Las constantes y trminos que acabamos de llamar despreciables pueden ser signicativos para valores sucientemente bajos de n, o en casos en que se pretende
realizar un anlisis ms no, ms detallado, ms preciso.

14

APUNTES PARA ALGORITMOS Y ESTRUCTURAS DE DATOS II

La Notacin O
Sean

cs (n)
n

el nmero de comparaciones de la ordenacin por seleccin para arreglos

ci (n) el nmero de comparaciones de la ordenacin por insercin para


2
n2
arreglos de tamao n. Vimos que cs (n) =
n2 y n 1 ci (n) n2 n2 .
2
2
Decimos que cs (n) es del orden de n o cuadrtico. Tambin se suele decir que ci (n)
2
es del orden de n , aunque en realidad el nmero de comparaciones de la ordenacin
de tamao

por insercin puede ser signicativamente menor en ciertos casos (puede ser del orden
de

n,

por ejemplo).

Notacionalmente se puede hacer una diferencia para expresar

cuando el orden es exacto y cuando el orden es una cota. Por ejemplo, escribiremos
cs (n) (n2 ) para expresar que el orden de cs (n) es exactamente cuadrtico. Signica
2
que para n sucientemente grande cs (n) es proporcional a n . En cambio, escribiremos
2
ci (n) O(n ) para expresar que el orden de ci (n) es a lo sumo cuadrtico, o sea,
2
que para n sucientemente grande ci (n) es a lo sumo proporcional a n . Tambin

ci (n) (n) para expresar que el orden de ci (n) es como mnimo n o


decir, que para n sucientemente grande ci (n) es como mnimo proporcional

escribiremos
lineal, es
a

n.
A continuacin deniremos formalmente O ,
(n2 ), ci (n)

y para justicar la notacin utilizada


O(n2 ) y ci (n) (n)). Vale la pena observar que estamos interesados en el comportamiento de las funciones para n sucientemente grande. Utilizaremos frecuentemente la expresin para todo n N suciente
mente grande, P(n) o para casi todo n N, P(n), que denotamos n N.P(n) y
es equivalente a n0 N.n N.(n n0 P(n)).
en el prrafo anterior (cs (n)

En particular nos interesa trabajar con funciones que sean no negativas en casi todo

0
su dominio: denotamos por f : N R
que n N.f (n) R .

0
Denicin: Dada f : N R , se dene el conjunto

O(f (n)) = {t : N R0 |c R+ . n N.t(n) cf (n)}

(f (n)) = {t : N R0 |c R+ . n N.t(n) cf (n)}


(f (n)) = O(f (n)) (f (n))
que denotan, respectivamente, el conjunto de todas las funciones que para
temente grande son a lo sumo proporcionales a

f (n),

sucien-

el de las que son como mnimo

f (n), y el de las que son proporcionales a f (n).


n2
n2 O(n2 ), dado
Por ejemplo, regresando a la ordenacin por seleccin, cs (n) =
2
2
2
que cs (n) n para todo n N. Tambin puede comprobarse que cs (n) (n ), y por
2
lo tanto cs (n) (n ).
2
Otro ejemplo interesante lo da la funcin t(n) = 5n + 300n + 15. A pesar de las
2
constantes 5, 300 y 15, se comprueba como en el ejemplo anterior que t(n) (n ).
2
En cambio, para la ordenacin por insercin, puede comprobarse que ci (n) O(n )
2
2
pero ci (n) 6 (n ) y por ello ci (n) 6 (n ). Tambin se obtiene ci (n) (n) pero
proporcionales a

ci (n) 6 O(n)

y por ello

ci (n) 6 (n).

A partir de ahora, concentraremos la atencin solamente en


el ms utilizado de los tres, y que tanto
de

O.

En efecto,

proposicin.

como

est denido en trminos de

O.

Esto se debe a que es

pueden ser expresados en trminos


y

y adems se tiene la siguiente

APUNTES PARA ALGORITMOS Y ESTRUCTURAS DE DATOS II

f (n) O(g(n)) sii g(n) (f (n)).


+
Demostracin: ) Sea c R tal que f (n) cg(n)
1
1
R+ . ) similar.
Entonces, g(n) f (n) y
c
c

15

Proposicin:

Tambin podemos comprobar que la denicin de

se cumple para casi todo

n N.

determina que las constantes

d1 y d2 son posid2 f (n) son proporcionales a t(n) y f (n) respectivamente:


+
Proposicin: Si t(n) O(f (n)), entonces d1 t(n) O(d2 f (n)) para todo d1 , d2 R .
+
Demostracin: Sea c R
tal que t(n) cf (n) se cumple para casi todo n N.
d c
Entonces, d1 t(n) d1 cf (n) = ( 1 )d2 f (n).
d2
multiplicativas sean ignoradas. Esta propiedad es esperada ya que si
tivas,

d1 t(n)

Otra propiedad que expresa la insignicancia de las constantes multiplicativas es que

df (n) O(f (n))

para todo

d > 0.

Sigue de la proposicin anterior y reexividad.

Proposicin: La relacin es a lo sumo del orden de es reexiva y transitiva.


Demostracin: Reexividad: como
tenemos

f (n) O(f (n)).


c1 , c2 R+

Transitividad: Sean

c2 h(n).

n N, f (n) f (n) trivialmente,

tales que

tomando

c=1

n N, f (n) c1 g(n) y n N, g(n)

Los naturales que satisfacen ambas desigualdades siguen siendo todos salvo a
n N, f (n) c1 g(n) (c1 c2 )h(n).

lo sumo un nmero nito. Por lo tanto,


Corolario:

g(n) O(h(n))

sii

O(g(n)) O(h(n)).

Demostracin: El slo si se cumple por transitividad, y el si por reexividad.


Corolario:

f (n) O(g(n)) g(n) O(f (n))

sii

O(f (n)) = O(g(n)).

Observar que esto no signica que la relacin es a lo sumo del orden de sea antisimtrica, de hecho no lo es.

Antisimetra debera establecer que

f = g,

el corolario

slo dice que son proporcionales.


La siguiente proposicin establece que para las funciones interesantes las constantes
aditivas no importan.

f (n) est acotado inferiormente por un nmero positivo ( > 0. n


N.f (n) > ), entonces g(n) O(f (n)) implica g(n) + d O(f (n)) para todo d 0.

Demostracin: Sean , c > 0 tales que n N.f (n) > y n N.g(n) cf (n).
d
d
0

0
Sea c = c + d/. Ahora n N.g(n) + d cf (n) + cf (n) + f (n) c f (n).

Proposicin: Para todo a, b > 1, O(loga n) = O(logb n).


+
Demostracin: Para todo n N , loga n = loga b logb n. Luego, loga b es la constante
(positiva por a, b > 1) que prueba que loga n O(logb n). Igual para logb n O(loga n).
Proposicin: Si

Esto demuestra que en este contexto la base del logaritmo es irrelevante y puede
ignorarse.

Regla del Lmite


La siguiente regla es til para demostrar cundo una funcin es de un cierto orden, y
cundo no lo es.
Proposicin (Regla de Lmite):

f, g N R0 ,

enunciados se cumplen:
(1)
(2)

f (n)
R+ O(f (n)) = O(g(n)).
n g(n)
f (n)
lim
= 0 O(f (n)) O(g(n)).
n g(n)
lim

si

limn

f (n)
existe los siguientes
g(n)

16

APUNTES PARA ALGORITMOS Y ESTRUCTURAS DE DATOS II


(3)

f (n)
= + O(g(n)) O(f (n)).
n g(n)
lim

Observar que en los dos ltimos casos, la inclusin es estricta.


Demostracin:
(1) Alcanza con comprobar que

O(f (n)) O(g(n)),

dado que si

limn

f (n)
g(n)

=l

= 1l R+ . Veamos entonces que f (n) O(g(n)). Sea


R entonces limn fg(n)
(n)
(n)
R+ , se tiene que n N. fg(n)
l < , luego f (n) < ( + l)g(n).
(2) El razonamiento anterior funciona incluso si l = 0. Tenemos pues O(f (n))
+

(3)

O(g(n)). Veamos que esta inclusin es estricta vericando que g(n) 6 O(f (n)).
+

Si g(n) O(f (n)), entonces hay una constante c R , n N, g(n) cf (n).


f
(n)
f
(n)

Entonces n N.
1c , con ello limn g(n) no podra ser menor que 1c .
g(n)
Luego O(f (n)) O(g(n)).
f (n)
Como limn
= + implica que limn fg(n)
= 0, vale por (2).
g(n)
(n)

Corolario:
(1)
(2)
(3)
(4)
(5)
(6)
(7)
(8)

x < y O(nx ) O(ny ),


x R+ O(log n) O(nx ),
x R0 O(nx ) O(log n),
c > 1 O(nx ) O(cn ).
0 c < 1 O(cn ) O(nx ).
c > d 0 O(dn ) O(cn ).
d 0 O(dn ) O(n!).
O(n!) O(nn ).

Demostracin:
(1)
(2)

(3)

(4)

(5)
(6)
(7)

(8)

1
limn nny = limn nyx
= 0.
x
Como limn loga n = + = limn n , por la Regla de L'Hpital tenemos
1
1
n ln a
limn lognax n = limn xn
x1 = limn xnx ln a = 0.
n
limn log
= limn log1 n = limn log n = +. Luego, O(n0 ) O(log n).
n0

x
0
Para todo x R , O(n ) O(n ) O(log n).
nk
Demostraremos por induccin que para todo k N, limn n = 0. Para k = 0
c
nk
la prueba es trivial. Asumimos como hiptesis inductiva que limn n = 0.
c
k
(k+1)nk
nk+1
k+1
Por la Regla de L'Hpital, limn
= limn cn log c = log c limn ncn = 0.
cn
e
e
k
n
Por lo tanto, para todo k N, O(n ) O(c ). Sea x R. Sea k N tal que
x
k
n
x k . Se obtiene O(n ) O(n ) O(c ).
nk
Similar al anterior, demostrando que para todo k N, limn n = +.
c
cn
c n
c
Sigue de limn n = limn ( ) = +, que vale pues
>
1
.
d
d
d
n
2
Sea c > d. Para todo n 2c se puede ver que n! n(n 1) . . . (n b c)
2
n
n
d
e
d n2 ed 2 e c2 2 cn . Por lo tanto, cn O(n!) que implica O(cn ) O(n!). Por
n
el inciso anterior, tenemos O(d ) O(n!).
n!
2...n
Para todo n 2,
= n1 n...n
n1 n...n
= n1 . Luego, limn nn!n
nn
n...n
limn n1 = 0 y O(n!) O(nn ).

APUNTES PARA ALGORITMOS Y ESTRUCTURAS DE DATOS II

17

Los incisos 3 y 5 demostrados son de poco inters ya que no es esperable tener programas cuyo nmero de comparaciones decrece a medida que

crece. Se enunciaron para

proporcionar una visin un poco ms completa de la jerarqua de funciones determinada

O.
g(n) O(f (n)) O(f (n) + g(n)) = O(f (n)).
+

Demostracin: Para c R , n N.g(n) cf (n). Luego, f (n)+g(n) (1+c)f (n).

g(n)
0
= 0, entonces f (n) g(n) : N
Proposicin: Sean f, g : N R . Si limn
f (n)
R0 y O(f (n) g(n)) = O(f (n)).
g(n)

Demostracin: n N.
< 12 . As, g(n) < 21 f (n) y 12 f (n) f (n) g(n) f (n).
f (n)
k
k
Corolario: O(ak n + . . . + a1 n + a0 ) = O(n ), si ak 6= 0.
por la notacin
Proposicin:

Demostracin: Usando las dos ltimas proposiciones.

O(f (n) + g(n)) = O(max(f (n), g(n))).


Demostracin: f (n) + g(n) 2 max(f (n), g(n)) y max(f (n), g(n)) f (n) + g(n).

Proposicin: Si n N.f (n) > 0, entonces O(g(n)) O(h(n)) sii O(f (n)g(n))
O(f (n)h(n)).

Demostracin: Fcil pues n N, se verica g(n) ch(n) sii f (n)g(n) cf (n)h(n).


Proposicin: Si f (n) O(g(n)) y limn h(n) = , entonces f (h(n)) O(g(h(n))).

Demostracin: Si n N.f (n) cg(n), como a partir de cierto n, h(n) es sucien


temente grande, n N.f (h(n)) cg(h(n)).

1/2
O(n) y log(n) tiende a innito, log n = log1/2 n O(log n).
Ejemplo: Como n
Denicin: Si O(t(n)) = O(1), t se dice constante. Si O(t(n)) = O(log n), t se
2
dice logartmico. Si O(t(n)) = O(n), t se dice lineal. Si O(t(n)) = O(n ), t se
3
k
dice cuadrtica. Si O(t(n)) = O(n ), t se dice cbica. Si O(n ) O(t(n))
k+1
O(n ) para algn k N, t se dice polinomial. Si O(t(n)) = O(cn ), para algn
c > 1, t se dice exponencial. Si t(n) expresa la eciencia de un algoritmo, ste se
Proposicin:

dice, respectivamente, constante, logartimico, lineal, cuadrtico, cbico, polinomial,


exponencial.
Proposicin:
(1)
(2)

f (n) (g(n)) sii g(n) O(f (n)) sii O(g(n)) O(f (n)) sii (f (n)) (g(n))
f (n) (g(n)) sii g(n) (f (n)) sii O(f (n)) = O(g(n)) sii (f (n)) = (g(n))

Ejemplos. A continuacin se ven dos ejemplos de algoritmos de bsqueda: bsqueda


lineal y bsqueda binaria.
El de bsqueda lineal es el siguiente, que recorre el arreglo de izquierda a derecha
buscando la primer ocurrencia de x.
{Pre: n

0}

fun linear_search (a: array[1..n] of T, x:T) ret i:nat


i:= 1

do i
end fun

{Inv: x no est en a[1,i)}


n

a[i]

6=

{Post: x est en a sii i

i:= i+1

od

x=a[i]}

18

APUNTES PARA ALGORITMOS Y ESTRUCTURAS DE DATOS II


Para este algoritmo es sencillo analizar el mejor caso, el peor caso y el caso medio.

Sean

t1 (n), t2 (n)

t3 (n)

las funciones que cuentan la cantidad de comparaciones con x

en el mejor caso, peor caso y caso medio respectivamente.

mejor caso: Ocurre cuando x se encuentra en la primera posicin del arreglo. Se

t1 (n) = 1 (1).

realiza una comparacin:

peor caso: Ocurre cuando x no se encuentra en el arreglo. Se realizan n comparaciones:

t2 (n) = n (n).

caso medio: A priori, depende de la probabilidad de que x est en el arreglo y, en


caso de que est, la probabilidad de que est en cada posicin. Si consideramos
el caso en que est, y si consideramos equiprobable que est en una u otra
posicin, el promedio del nmero de comparaciones que har falta en cada caso
1+2+...+(n1)+n
n(n+1)
es t3 (n) =
. Simplicando: t3 (n) =
= n+1
(n).
n
2n
2

t1 (n), t2 (n) y t3 (n) cuentan el nmero de comparaciones en diferentes


casos. Si deniramos t(n) como el nmero de comparaciones que realiza el algoritmo
para arreglos de tamao n (sin especicar los diferentes casos), obtendramos t(n)
O(n) y t(n) (1). Es similar al anlisis que hicimos del algoritmo de ordenacin por
Observar que

insercin.
Ejercicio: Cmo puede modicarse la bsqueda lineal si se asume que el arreglo est
ordenado? De qu orden seran

t1 (n), t2 (n)

t3 (n)

en tal caso?

El segundo ejemplo es el de bsqueda binaria, que requiere que el arreglo est ordenado de menor a mayor. Es similar a cuando uno busca una palabra en un diccionario:
uno lo abre al medio y (a menos que tenga la suerte de encontrarla justo donde abri el
diccionario, en cuyo caso la bsqueda termina) si la palabra que uno busca es anterior a
las que se ven donde se abri el diccionario uno limita la bsqueda a la parte izquierda
(abriendo nuevamente al medio, etc), si en cambio la palabra que uno busca es posterior
a las que se ven, uno limita la bsqueda a la parte derecha, etc.
{Pre: n

a ordenado}

fun binary_search (a: array[1..n] of T, x:T) ret i:nat


var izq,med,der: nat
izq:= 1
der:= n
i:= 0

{Inv: (x est en a sii x est en a[izq,der])

do izq

der

x
x

<
=
>

(i

6=

x = a[i])}

i = 0

med:= (izq+der)

if x

a[med]
a[med]
a[med]

der:= med-1
i:= med
izq:= med+1


od
end fun
{Post: (x est en a sii i

6=

0)

(i

6=

x = a[i]))}

La complejidad del algoritmo est dada por la cantidad de veces que se ejecuta el ciclo,
dado que cada ejecucin del ciclo insume tiempo constante (a lo sumo 2 comparaciones).
En cada ejecucin del ciclo, o bien se encuentra x, o bien el espacio de bsqueda se

APUNTES PARA ALGORITMOS Y ESTRUCTURAS DE DATOS II

19

reduce a la mitad. En efecto, si izqj y derj denotan los valores de izq y der despus de
la j -sima ejecucin del ciclo, sabemos que dj = derj izqj + 1 son la cantidad de celdas
que nos quedan por explorar para encontrar x. Se puede ver que si

dj > 1,

entonces

dj+1 dj /2.

Como d0 = n y el ciclo termina cuando dj = 1, esto pasar -en el peor


j = dlog2 ne. Por lo tanto, si t(n) es el nmero de comparaciones que se
realizan en el peor caso, t(n) O(log n).
De la misma forma puede obtenerse una cota inferior de t(n) (es decir, del peor caso),
por ejemplo, demostrando que t(n) blog4 nc partiendo de que dj+1 dj /4. Por ello,
se tiene que t(n) (log n) y por lo tanto t(n) (log n).
caso- cuando

Puede notarse que este algoritmo se comporta mucho mejor que el anterior para
grandes valores de
1.000.000.
dado que

n.

Supongamos una bsqueda lineal sobre un arreglo de tamao

Si ahora repitiramos la bsqueda lineal sobre uno de tamao 2.000.000,

t2 (n) O(n),

nos llevara el doble de tiempo. En cambio, si estuviramos

t(n) O(log n), la diferencia de tardanza sera casi


log(2n) = log 2+log n = 1+log n. En efecto, se puede observar

utilizando bsqueda binaria, como


imperceptible, dado que

que el algoritmo hace slo una comparacin ms.

Recurrencias
El algoritmo de bsqueda binaria que acabamos de dar, puede tambin denirse por
recursin:
{Pre: 0

izq

der

n}

fun binary_search_rec (a: array[1..n] of T, x:T, izq, der : nat) ret i:nat
var med: int
if izq > der i = 0
izq

der

med:= (izq+der)

if x
x
x

<
=
>

i:= binary_search_rec(a, x, izq, med-1)

a[med]

6=

0)

a[med]
a[med]

i:= med
i:= binary_search_rec(a, x, med+1,der)



end fun
{Post: (x est en a[izq,der] sii i

(i

6=

x = a[i])}

Hace falta una funcin principal:


{Pre: n

0 }

fun binary_search (a: array[1..n] of T, x:T) ret i:int


i:= binary_search_rec(a, x, 1, n)

end fun
{Post: (x est en a sii i

6=

0)

(i

6=

x = a[i]))}

El algoritmo es esencialmente el mismo que en la versin iterativa, veremos a continuacin cmo calcular el orden de algoritmos recursivos como ste. Sea

t(n) el nmero de

comparaciones entre elementos de a que realiza binary_search_rec(a,x,izq,der) cuando

es igual a der - izq + 1. Por simplicidad, consideraremos que evaluar las condiciones

del

if requiere una comparacin (frecuentemente es posible implementarlo de esa forma).

20

APUNTES PARA ALGORITMOS Y ESTRUCTURAS DE DATOS II

Entonces, a menos que izq sea mayor que der en cuyo caso
ciones entre elementos de a (t(0)

= 0),

n es cero y no hay compara-

la ejecucin de binary_search_rec(a,x,izq,der)

realiza una comparacin y:

= a[med], en cuyo caso el algortimo termina,


< a[med], en cuyo caso hay una llamada recursiva,
o bien x > a[med], en cuyo caso tambin hay una llamada recursiva,
Sabiendo que t(n) es el nmero de comparaciones entre elementos de a que
binary_search(a,x,izq,der) cuando n es igual a der - izq +1, tenemos:

si n = 0
0
1
si n 6= 0 x = a[med]
t(n) =
1 + t(n 2)
caso contrario
o bien x
o bien x

realiza

t(n) = 1 + t(n 2) = 1 + 1 + t(n 4) = . . . = j + t(n 2j ).


j = log2 n obtenemos t(n) = j + t(1) = j = log2 n.

En el peor caso,

Para

Ordenacin por intercalacin. Recordemos el problema formulado en una clase


anterior:

Pregunta 4: Un bibliotecario demora 1 da en ordenar alfabticamente una biblioteca con 1000 expedientes. Cunto demorar en ordenar una con 2000 expedientes?
Le proponemos al bibliotecario que ordene la primera mitad de la biblioteca (tarea A

1.000.000 de comparaciones = 1 da de trabajo), luego la segunda mitad (tarea B

1.000.000 de comparaciones = 1 da de trabajo), y luego efecte la intercalacin obvia


entre las 2 bibliotecas ordenadas (tarea C

2.000 comparaciones = unos minutos de

trabajo). Esto le llevar mucho menos que los 4 das que le llevara hacer todo con el
algoritmo de ordenacin por seleccin.
Esto a su vez puede mejorarse: la tarea A puede desdoblarse en ordenar 500 expedientes (tarea AA
AB

250.000 comparaciones), ordenar los otros 500 expedientes (tarea

250.000 comparaciones), e intercalar (tarea AC 1.000 comparaciones). Similar 1.004.000 comparaciones. Poco

mente para la tarea B. En total, tendramos ahora

ms de 1 da para ordenar los 2.000 expedientes! La idea puede mejorarse an ms: en


vez de ordenar grupos de 500, sern grupos de 250 125 68 34 . . .
Cun chicos pueden ser estos grupos? Una biblioteca de 1 solo expediente es trivial,
no requiere ordenacin. Una de 2 expedientes ya puede subdivirse en 2 partes de un
expediente cada una, ordenar cada parte (trivial) e intercalar.
De esta manera, la utilizacin de la ordenacin por seleccin nalmente queda eliminada: toda la ordenacin se realiza dividiendo la biblioteca en 2 ordenando (utilizando
recursivamente esta idea) e intercalando.
El algoritmo de ordenacin que acabamos de explicar se llama mergeSort (ordenacin
por intercalacin). En un estilo funcional se puede escribir as:
merge_sort :: [T]

[ T]

merge_sort [] = []
merge_sort [t] = [t]
merge_sort ts = merge sts1 sts2

APUNTES PARA ALGORITMOS Y ESTRUCTURAS DE DATOS II

21

where
sts1 = merge_sort ts1
sts2 = merge_sort ts2
(ts1,ts2) = split ts
split :: [T]

([T],[T])

split ts = (take n ts, drop n ts)

where n = length ts
merge :: [T]

[ T]

[T]

merge [] sts2 = sts2


merge sts1 [] = sts1
merge (t1:sts1) (t2:sts2) =

if t1 t2
then t1:merge sts1 (t2:sts2)
else t2:merge (t1:sts1) sts2

donde split divide ts en 2 partes de igual longitud ( 1) y merge realiza la intercalacin.


La administracin de la memoria en estilo funcional se deja en manos del compilador.
Eso facilita su denicin. En estilo imperativo la principal dicultad es justamente la de
realizar la intercalacin: o bien es necesario complicar signicativamente el algoritmo, o
bien utilizar arreglos auxiliares para realizarla. Volviendo a nuestro ejemplo motivador,
se puede ver que no es fcil intercalar 2 bibliotecas (cada una de ellas ordenada) llenas
de expedientes si el resultado de la intercalacin debe quedar en las mismas bibliotecas,
a menos que se cuente con una biblioteca vaca donde poner los expediente temporariamente.

Tomando b y c como arreglos temporarios para realizar la intercalacin

(en realidad slo uno es necesario, incluimos el otro para simplicar el algoritmo de
intercalacin) se lo puede escribir de la siguiente manera:
{Pre: n

der

izq

>

a = A}

proc merge_sort_rec (in/out a: array[1..n] of T, in izq,der: nat)


var b,c: array[1..n] of T
var med: nat
if der > izq med:= (der+izq) 2
merge_sort_rec(a,izq,med)
{a[izq,med] permutacin ordenada de A[izq,med]}
merge_sort_rec(a,med+1,der)
{a[med+1,der] permutacin ordenada de A[med+1,der]}

for i:= izq to med do b[i-izq+1]:=a[i] od


{b[1,med-izq+1] = a[izq,med]}

for j:= med+1 to der do c[j-med]:=a[j] od


{c[1,der-med] = a[med+1,der]}
intercalar(a,b,c,izq,med,der)
{a[izq,der] permutacin ordenada de A[izq,der]}


end proc

22

APUNTES PARA ALGORITMOS Y ESTRUCTURAS DE DATOS II

{Post: a permutacin de A

a[izq,der] permutacin ordenada de A[izq,der]}

En realidad, los arreglos b y c no necesitan ser tan largos, pero por simplicidad se
declaran de n celdas, igual que el arreglo a.
La ejecucin de la ordenacin por intercalacin comienza llamando al procedimiento
con izq igual a 1 y der igual a n:
{Pre: n

a = A}

proc merge_sort (in/out a: array[1..n] of T)


merge_sort_rec(a,1,n)

end proc
{Post: a est ordenado y es permutacin de A}
El procedimiento merge_sort_rec utiliza el procedimiento intercalar que puede programarse de la siguiente manera.
{Pre: n

der

>

med

izq

>

a = A

B[1,med-izq+1] = A[izq,med]

b = B

c = C

C[1,der-med] = A[med+1,der]

B[1,med-izq+1] y C[1,der-med] ordenados}

proc intercalar (in/out a: array[1..n] of T, in b, c: array[1..n] of T, in izq,med,der: nat)


var i,j,maxi,maxj: nat
i:= 1
j:= 1
maxi:= med-izq+1
maxj:= der-med
{Inv: a[izq,k) = intercalacin de b[1,i) con c[1,j)}

for k:= izq to der do


if i maxi (j > maxj

b[i]

c[j])

then a[k]:= b[i]


i:=i+1

else a[k]:= c[j]


j:=j+1


od
end proc {a permutacin de A

a[izq,der] permutacin ordenada de A[izq,der]}

Esta tcnica para resolver un problema, consistente en dividir el problema en problemas menores (de idntica naturaleza pero de menor tamao), asumir los problemas
menores resueltos y utilizar dichos resultados para resolver el problema original, se
conoce por divide and conquer, es decir, divide y vencers, o divide y reinars.
Justamente el hecho de que los problemas menores sean de igual naturaleza que el
original, es lo que permite que el mismo algoritmo pueda aplicarse recursivamente para
resolver los problemas menores.

Los casos ms sencillos (como el del fragmento de

arreglo de longitud menor o igual que 1, para el problema de ordenacin) deben resolverse aparte. Estos casos frecuentemente son triviales. Usualmente el trmino divide
y vencers se aplica a aquellos casos en que el problema se subdivide en problemas
menores fraccionando el tamao de la entrada.

Nmero de Comparaciones. Sea t(n) el nmero de comparaciones entre elementos de T que realiza el procedimiento merge_sort_rec con una entrada de tamao n

APUNTES PARA ALGORITMOS Y ESTRUCTURAS DE DATOS II


en el peor caso.

t(1) = 0.

23

Cuando la entrada es de tamao 1, no hace ninguna comparacin,

Cuando la entrada es de tamao mayor a 1, hace todas las comparaciones que

hace la primera llamada recursiva al procedimiento merge_sort_rec, esto es,

t(dn/2e),

ms todas las comparaciones que hace la segunda llamada recursiva al procedimiento

t(bn/2c), ms todas las comparaciones que hace el proceso de


caso, esto es, n 1. Tenemos entonces:

merge_sort_rec, esto es,


intercalacin en el peor

t(n) = t(dn/2e) + t(bn/2c) + n 1.


Esto es una recurrencia, es decir, se dene la funcin
obtener explcitamente el orden de

t(n)?

t en trminos de s misma.
t(n) est acotada:

Cmo

Usaremos que

t(n) t(dn/2e) + t(bn/2c) + n


t(n) t(dn/2e) + t(bn/2c) + n2
n para los que
d e y b c pueden ser ignoradas: potencias de 2.
m
Sea entonces n = 2 . A partir de la primera desigualdad, para m > 0 tenemos
m
m1
m1
t(2 ) t(2
) + t(2
) + 2m = 2t(2m1 ) + 2m de donde se obtiene dividiendo por
2m ,
Para simplicar el clculo, consideramos primero aquellos valores de

las operaciones

t(2m1 )
t(2m )

+ 1.
2m
2m1
Iterando este proceso, se llega a

t(2m1 )
t(2m2 )
t(2mk )
t(20 )
t(1)
t(2m )

+1

+2

+k

+m =
+m = 0+m = m.
m
m1
m2
mk
0
2
2
2
2
2
1
t(2m ) 2m m. Como n = 2m , sabemos que m = log2 n. Reemplazando
queda t(n) n log2 n, para todo n que sea potencia de 2. Podemos concluir entonces
que t(n) O(n log n|n potencia de 2).
1
Anlogamente, usando la segunda desigualdad obtenemos t(n)
n log n y con2
cluimos que t(n) (n log n|n potencia de 2) y nalmente de ambas conclusiones,
obtenemos que t(n) (n log n|n potencia de 2).
Resta ver cul es la cantidad de comparaciones para el resto de los valores de n.
Primero se puede ver que t(n) es creciente. Por induccin en n, t(n + 1) > t(n). El
caso base es sencillo, t(2) = t(1)+t(1)+21 = 1 > 0 = t(1). Supongamos que para todo
1 k < n, t(k+1) > t(k). Entonces, t(n+1) = t(d(n+1)/2e)+t(b(n+1)/2c)+n+11 >
t(dn/2e) + t(bn/2c) + n 1 = t(n).
k
k+1
Para n sucientemente grande, sea k 1 tal que 2 n < 2
. Esto implica
k+1
que k log2 n < k + 1. Como t es creciente, tenemos t(n) < t(2
) 2k+1 (k + 1) =
2(2k k + 2k ) 2(2k k + 2k k) = 4 2k k 4 2log2 n log2 n = 4n log2 n. Por lo tanto t(n)
O(n log n). Tambin por t creciente, se tiene que t(n) t(2k ) 21 2k k 18 2k+1 (k + 1) >
1 log2 n
2
log2 n = 18 n log2 n. Entonces t(n) (n log n), o sea, t(n) (n log n).
8
Despejando,

Al analizar el procedimiento merge_sort_rec utilizamos intuitivamente la siguiente


notacin.

24

APUNTES PARA ALGORITMOS Y ESTRUCTURAS DE DATOS II


Denicin: Dada

f : N R0 ,

se denen los conjuntos

O(f (n)|P (n)) = {t : N R0 |c R+ . n N.P (n) t(n) cf (n)}

+
(f (n)|P (n)) = {t : N R0 |c
T R . n N.P (n) t(n) cf (n)}
(f (n)|P (n)) = O(f (n)|P (n)) (f (n)|P (n))
El razonamiento hecho para el anlisis del procedimiento merge_sort_rec puede generalizarse:

f : N R0 , se dice que f es asintticamente o eventualmente


no decreciente si n N, f (n) f (n + 1). Sea i N2 , f es i-suave o i-uniforme
+

si es eventualmente no decreciente y d R , n N, f (in) df (n). Finalmente, f


2
es suave o uniforme si es i-suave para todo i N .
Ejemplos: En el anlisis del procedimiento merge_sort_rec, t(n) es eventualmente
no decreciente. La funcin n log2 n es eventualmente no decreciente (ya que es producto
de dos funciones que lo son). Tambin es 2-suave ya que para todo n 2 se cumple
2n log2 (2n) = 2n(log2 2 + log2 n) = 2n + 2n log2 n 2n log2 n + 2n log2 n = 4n log2 n (en
Denicin: Dada

realidad, tambin podemos concluir que es 2-suave por ser producto de dos funciones
que son 2-suave). Del resultado que sigue se desprende que
Lema:

es

i-suave

si y slo si

n log2 n

es tambin suave.

es suave.

Demostracin: El si es trivial, demostramos el slo si suponiendo que


y demostrando que entonces tambin es

dlogi je

f (jn) f (i
n)
dlogi je
d
f (n)
por lo tanto,

es

j -suave

f
f

j -suave.

Sea

es i-suave

sucientemente grande,

es eventualmente no decreciente y
es

jn idlogi je n

i-suave

(con constante

ddlogi je ).

Los siguientes son ejemplos de funciones suaves:


nlog n , 2n o n! son ejemplos de funciones no suaves.

Regla de la suavidad o uniformidad: Sea f : N

nk , log n, nk log n,

mientras que

R0

t : N R0 evenb), entonces t(n) O(f (n)).


suave y

t(n) O(f (n)|n potencia de


y .
Sea n sucientemente grande, y sea m tal

tualmente no decreciente, si
Anlogamente para
Demostracin:

que

bm n < bm+1 .

En-

tonces

t(n) t(bm+1 )
t es eventualmente no decreciente
cf (bm+1 )
t(n) O(f (n)|n potencia de b)
m
cdf (b )
f es b-suave
cdf (n)
f es eventualmente no decreciente
por lo tanto t(n) O(f (n)).
Ejemplo: sea t(n) el nmero de comparaciones que realiza el procedimiento merge_sort_rec:
t(n) es eventualmente no decreciente, t(n) (n log n|n potencia de 2) y n log n es
suave. Luego t(n) (n log n). Volviendo al problema del bibliotecario, ordenar 1000
expedientes requiere 1000*10 = 10000 comparaciones.
cambio, requiere 2000*11 = 22000 comparaciones.

Ordenar 2000 expedientes, en

Por lo tanto, si 1000 expedientes

requirieron 1 da de trabajo, 2000 requerir poco ms de 2 das.


Al estudiar las recurrencias en forma general, se desarrollarn tcnicas generales para
la resolucin de recurrencias, de manera de no verse uno siempre obligado a realizar una
prueba detallada como la que hicimos en el caso del procedimiento merge_sort_rec.

APUNTES PARA ALGORITMOS Y ESTRUCTURAS DE DATOS II

25

Recurrencias/relaciones divide y vencers (divide and conquer). (Introduction to Algorithms: A Creative Approach, pginas 50 y 51)
(1) comprobar que

t(n)

es eventualmente no decreciente.

t(n) = at(n/b) + g(n) para


b. Observar que t(n) puede
pero para el caso de n potencia de b,

(2) llevar la recurrencia a una ecuacin de la forma


b N2 , g(n) (nk ), k N y n potencia de
tener una expresin general ms compleja,

puede reducirse como la ecuacin mencionada.


k
k
k
(3) segn sea a > b , o a = b o a < b , se obtienen los siguientes resultados para
potencia de

b:

(nlogb a )
(nk log n)
t(n)

(nk )

si
si
si

a > bk
a = bk
a < bk

t(n)
at(n/b)+g(n) (resp. t(n) at(n/b)+g(n)) para n potencia de b, g(n) O(nk )
k
(resp. g(n) (n )) se obtiene el mismo resultado en los 3 casos, slo que escribiendo O (resp. ) en vez de .

(4) Si la ecuacin inicial no contiene una igualdad sino slo una cota:

Ejemplos de recurrencias divide y vencers son el nmero de comparaciones que realiza el procedimiento merge_sort, o el nmero de comparaciones que realiza la bsqueda
binaria en el peor caso. El primer ejemplo fue desarrollado en detalle recientemente.
Para la bsqueda binaria hicimos las cuentas detalladamente, pero ahora podemos
volver a hacerla utilizando recurrencias. Si pensamos en el nmero de comparaciones
que hacen falta para buscar en un arreglo de longitud n, observamos que es 1 ms el
nmero de comparaciones que hacen falta para buscar en un arreglo de longitud

t(n) = t(bn/2c) + 1.

bn/2c.

Aplicando el mtodo presentado ms arriba, como t(n)


k
es eventualmente no decreciente y a = 1, b = 2 y k = 0, tenemos a = b y por ello
Eso nos d

t(n) (log n).

26

APUNTES PARA ALGORITMOS Y ESTRUCTURAS DE DATOS II

Recurrencias lineales homogneas. (Fundamentos de Algoritmia, pginas 135 a


140)
(1) llevar la recurrencia a una

ecuacin caracterstica de la forma

ak tn + . . . + a0 tnk = 0,
(2) considerar el

polinomio caracterstico asociado

(3) determinar las races

m1 , . . . , m j

r1 , . . . , rj

ak x k + . . . + a0 ,

del polinomio caracterstico, de multiplicidad

respectivamente (se tiene

mi 1

m1 + . . . + mj = k ),

(4) considerar la forma general de las soluciones de la ecuacin caracterstica:

t(n) = c1 r1n + c2 nr1n + . . . + cm1 nm1 1 r1n +


+ cm1 +1 r2n + cm1 +2 nr2n + . . . + cm1 +m2 nm2 1 r2n +
.
.
.

.
.
.

.
.
.

+ cm1 +...+mj1 +1 rjn + cm1 +...+mj1 +2 nrjn + . . . + cm1 +...+mj nmj 1 rjn
m1 + . . . + mj = k , tenemos k incgnitas: c1 , . . . , ck ,
k condiciones iniciales tn0 , . . . , tn0 +k1 (n0 es usualmente 0 1) plantear
sistema de k ecuaciones con k incgnitas:

como

(5) con las


un

t(n0 ) = tn0
t(n0 + 1) = tn0 +1
.
.
.

.
.
.

.
.
.

t(n0 + k 1) = tn0 +k1


(6) obtener de este sistema los valores de
(7) escribir la
de
(8)

t(n)

solucin nal de la forma

reemplazando

ci

ri

c1 , . . . , c k ,
tn = t0 (n),

donde

t0 (n)

se obtiene a partir

por sus valores y simplicando la expresin nal.

corroborar que efectivamente

t(n0 + k) = tn0 +k ,

donde

tn0 +k

puede obtenerse

utilizando la ecuacin caracterstica.


Un ejemplo puede darse contando

tn ,

el nmero de veces que se ejecuta la accin A

en el siguiente procedimiento cuando se llama con parmetro

n:
{pre : n 0}

proc p (in n: int)


if n = 0 skip
n
n

=
>

1
1

A
p(n-1)
p(n-2)


end proc
Al calcular

tn ,

obtenemos

0
1
tn =
t
n1 + tn2

n=0
n=1

que es la secuencia de bonacci.


Apliquemos el mtodo presentado para las recurrencias homogneas:

tn tn1 tn2 = 0, k = 2.
2
caracterstico asociado x x 1.

(1) ecuacin caracterstica


(2) polinomio

APUNTES PARA ALGORITMOS Y ESTRUCTURAS DE DATOS II

27

1+ 5
1 5
de multiplicidad m1 = 1 y r2 =
de multiplicidad
2
2

1+ 5 n
1 5 n
(4) forma general t(n) = c1 (
) + c2 ( 2 ) .
2
(5) condiciones iniciales t0 = 0 y t1 = 1. Sistema de ecuaciones:
(3) races

r1 =

m2 = 1.

c1 +c2 = 0
(t(0) = t0 )

1 5
1+ 5
(t(1) = t1 )
c1 ( 2 ) + c2 ( 2 ) = 1
1
1
(6) despejando, c1 = y c2 = .
5
5

1 1+ 5 n
1 ( 1 5 )n .
)

(7) solucin nal tn = (


2
2
5
5
(8) efectivamente, con la solucin nal t2 = 1 coincidiendo con el resultado obtenido
para calcular

t2

usando la recurrencia original.

El siguiente ejemplo no proviene de un algoritmo. Calcular explcitamente


tn =

n
5tn1 8tn2 + 4tn3

tn

donde

n = 0, 1, 2

usando la tcnica presentada.

tn 5tn1 + 8tn2 4tn3 = 0, k = 3.


x3 5x2 + 8x 4.
races r1 = 1 de multiplicidad m1 = 1 y r2 = 2 de multiplicidad m2 = 2.
n
n
n
n
n
forma general t(n) = c1 1 + c2 2 + c3 n2 , simplicando t(n) = c1 + c2 2 + c3 n2 .
condiciones iniciales t0 = 0, t1 = 1 y t2 = 2. Sistema de ecuaciones:

(1) ecuacin caracterstica

(2) polinomio caracterstico asociado


(3)
(4)
(5)

c1 + c2 = 0
c1 + 2c2 + 2c3 = 1
c1 + 4c2 + 8c3 = 2

(t(0) = t0 )
(t(1) = t1 )
(t(2) = t2 )

c1 = 2, c2 = 2 y c3 = 1/2.
1
n
n
n+1
solucin nal tn = 2 + 2 2 n2 , simplicando tn = 2
n2n1 2.
2
efectivamente, con la solucin nal t3 = 2 coincidiendo con el resultado obtenido
para calcular t3 usando la recurrencia original.

(6) despejando,
(7)
(8)

En clase resolvimos tambin el siguiente ejemplo:

0
8
tn =
7t
n1 18tn2 + 20tn3 8tn4

n = 0, 1, 2
n=3

28

APUNTES PARA ALGORITMOS Y ESTRUCTURAS DE DATOS II

Recurrencias no homogneas. (Fundamentos de Algoritmia, pginas 140 a 148)


(1) llevar la recurrencia a una ecuacin caracterstica de la forma
ak tn + . . . + a0 tnk = bn p(n), donde p(n) es un polinomio no nulo de grado
k
d+1
(2) considerar el polinomio caracterstico asociado (ak x + . . . + a0 )(x b)
,

r1 , . . . , rj

(3) determinar las races

m1 , . . . , m j

d.

del polinomio caracterstico, de multiplicidad

respectivamente (se tiene

mi 1

m1 + . . . + mj = k + d + 1),

(4) considerar la forma general de las soluciones de la ecuacin caracterstica:

t(n) = c1 r1n + c2 nr1n + . . . + cm1 nm1 1 r1n +


+ cm1 +1 r2n + cm1 +2 nr2n + . . . + cm1 +m2 nm2 1 r2n +
.
.
.

.
.
.

.
.
.

+ cm1 +...+mj1 +1 rjn + cm1 +...+mj1 +2 nrjn + . . . + cm1 +...+mj nmj 1 rjn
m1 + . . . + mj = k + d + 1, tenemos k + d + 1 incgnitas: c1 , . . . , ck+d+1 ,
a partir de las k condiciones iniciales tn0 , . . . , tn0 +k1 (n0 es usualmente 0 1),
obtener usando la ecuacin caracterstica, los valores de tn0 +k , . . . , tn0 +k+d ,
con los k+d+1 valores tn0 , . . . , tn0 +k+d plantear un sistema de k+d+1 ecuaciones
con k + d + 1 incgnitas:
como

(5)
(6)

t(n0 ) = tn0
t(n0 + 1) = tn0 +1
.
.
.

.
.
.

.
.
.

t(n0 + k + d) = tn0 +k+d


(7) obtener de este sistema los valores de
(8) escribir la
de
(9)

t(n)

solucin nal de la forma

reemplazando

ci

ri

c1 , . . . , ck+d+1 ,
tn = t0 (n), donde t0 (n)

se obtiene a partir

por sus valores y simplicando la expresin nal.

corroborar que efectivamente

t(n0 + k + d + 1) = tn0 +k+d+1 ,

donde

tn0 +k+d+1

puede obtenerse utilizando la ecuacin caracterstica.


Ejemplo: calcular explcitamente

tn

donde

tn =

0
2tn1 + n

n=0

usando la tcnica presentada para las recurrencias no homogneas.

tn 2tn1 = n, k = 1, b = 1, p(n) = n, d = 1.
2
polinomio caracterstico asociado (x 2)(x 1) .
races r1 = 2 de multiplicidad m1 = 1 y r2 = 1 de multiplicidad m2 = 2.
n
n
n
n
forma general t(n) = c1 2 + c2 1 + c3 n1 , simplicando t(n) = c1 2 + c2 + c3 n.
condiciones iniciales t0 = 0. Tambin podemos obtener usando la recurrencia
t1 = 2t0 + 1 = 1 y t2 = 2t1 + 2 = 4.

(1) ecuacin caracterstica


(2)
(3)
(4)
(5)

(6) sistema de ecuaciones:

(7)
(8)
(9)

c1 + c2 = 0
(t(0) = t0 )
2c1 + c2 + c3 = 1
(t(1) = t1 )
4c1 + c2 + 2c3 = 4
(t(2) = t2 )
despejando, c1 = 2, c2 = 2 y c3 = 1.
n
n+1
solucin nal tn = 2 2 2 n, simplicando tn = 2
n 2.
efectivamente, con la solucin nal t3 = 11 coincidiendo con el resultado obtenido
para calcular t3 usando la recurrencia original.

APUNTES PARA ALGORITMOS Y ESTRUCTURAS DE DATOS II

29

Ordenacin rpida (quick_sort). La ordenacin por intercalacin es la ms rpida


que hemos visto, realiza apenas

(n log n) comparaciones.

El inconveniente que tiene es

la necesidad de un arreglo auxiliar para realizar la intercalacin. La ordenacin rpida


logra evitar este problema. La idea es descomponer el problema en 2 subproblemas de
manera que no requieran intercalacin. Cmo? Separando el arreglo en 2 fragmentos
de manera de que todos los que se encuentren en el primer fragmento sean menores que
todos los que se encuentren en el segundo fragmento. Para ello se elige un

pivote, es

decir, un elemento que se utilizar para separar ambos fragmentos. Aquellos elementos
que sean menores o iguales al pivote pertenecern al primer fragmento, aqullos que
no, al segundo. Llamaremos pivot al procedimiento que realiza dicha fragmentacin.
El procedimiento quick_sort invocar al procedimiento pivot.
{Pre: 1

izq

<

der

a = A}

proc pivot (in/out a: array[1..n] of elem, in izq, der: nat, out piv: nat)
var i,j: nat
piv:= izq
i:= izq+1
j:= der

do i

j
{piv

<i

j+1

todos los elementos en a[izq,i) son

{ todos los elementos en a(j,der] son

if a[i]
a[j]
a[i]

a[piv] i:= i+1


> a[piv] j:= j-1
> a[piv] a[j] a[piv]

>

que a[piv]}

>

que a[piv]}

que a[piv]}

swap(a,i,j)
i:= i+1
j:= j-1


od

{i

j+1, por eso todos los elementos en a[izq,j] son


{ todos los elementos en a[i,der] son

swap(a,piv,j)
piv:= j

que a[piv]}

{dejando el pivote en una posicin ms central}


{sealando la nueva posicin del pivot}

end proc

a(der,n] = A(der,n] a[izq,der] permutacin de A[izq,der]


izq piv der todos los elementos de a[izq,piv] son que a[piv]
todos los elementos de a(piv,der] son > que a[piv]}

{Post: a[1,izq) = A[1,izq)

Este procedimiento elige un pivote (se elige arbitrariamente a[izq]) y lo utiliza para
clasicar los elementos que se encuentran entre las posiciones izq y der: por un lado
los que son menores o iguales al pivote, y por el otro los que son mayores a l.

El

procedimiento modica el arreglo y la variable piv de forma que los primeros queden
entre izq y piv y los segundos entre piv+1 y der. El ndice i se utiliza para recorrer desde
la posicin izq+1 hacia la derecha, avanzando mientras encuentre elementos menores
o iguales al pivote. El ndice j se utiliza para recorrer desde la posicin der hacia la
izquierda, retrocediendo mientras encuentre elementos mayores al pivote.
no puede avanzar ni j retroceder es porque a[i]>a[piv] y a[j]a[piv].

Cuando i

En ese caso, se

intercambian los contenidos de a[i] y a[j], tras lo cual i puede avanzar y j retroceder.

30

APUNTES PARA ALGORITMOS Y ESTRUCTURAS DE DATOS II

Cuando termina el ciclo, i es j+1, todos los anteriores a i son menores o iguales que el
pivote y todos los posteriores a j son mayores que el pivote. Para nalizar, se ubica el
pivote al nal del primer fragmento y se asigna a piv esa nueva posicin.
Para ordenar el fragmento del arreglo a que va de las posiciones izq a der, el algoritmo
de ordenacin primero realiza la clasicacin que se acaba de explicar, y luego ordena
recursivamente los dos segmentos: el anterior al pivote y el posterior al pivote.
{Pre: 0

der

izq

n+1

izq-1

der

a = A}

proc quick_sort_rec (in/out a: array[1..n] of elem, in izq, der: nat)


var piv: nat
if der > izq pivot(a,izq,der,piv)
{permuta los elementos de a[izq,der]}
{devuelve piv tal que izq
{ todos los elementos en a[izq,piv] son
{ todos los elementos en a(piv,der] son

piv der}
que a[piv]}
> que a[piv]}

quick_sort_rec(a,izq,piv-1)
quick_sort_rec(a,piv+1,der)


end proc
{Post: a[1,izq) = A[1,izq)

a(der,n] = A(der,n]

a[izq,der] permutacin ordenada de A[izq,der]}

El programa principal que dispara la ordenacin rpida es simplemente


{Pre: n

a = A}

proc quick_sort (in/out a: array[1..n] of T)


quick_sort_rec(a,1,n)

end proc
{Post: a est ordenado y es permutacin de A}
El algoritmo de ordenacin rpida es muy utilizado en la prctica ya que su comportamiento en el caso medio es eciente. En efecto, asumiendo que piv siempre quede en
el medio entre izq y der, su nmero de comparaciones estara dado por la recurrencia

t(n) = 2t(n/2) + (n).


es decir, una recurrencia divide y vencers con a=2, b=2 y k=1.

(n log n).

Por ello

t(n)

En la prctica, asumiendo arreglos con informacin aleatoria, obtenemos el

mismo resultado. Esto se debe a que piv tiene una alta probabilidad de quedar cerca
del medio entre izq y der. Por ello, si tomamos t(n) como el nmero de comparaciones
que realiza la ordenacin rpida en el caso medio obtenemos tambin

t(n) (n log n).

De todas formas, no siempre es razonable asumir que la informacin de los arreglos


es aleatoria. En numerosas aplicaciones, uno ordena un arreglo, realiza un nmero de
modicaciones y luego vuelve a ordenarlo.

La segunda vez que se ordena, el arreglo

estar casi ordenado. En ese caso utilizar esta versin de ordenacin rpida puede ser
muy ineciente.
En efecto, pensemos qu pasara si aplicamos quick_sort a un arreglo perfectamente
ordenado.

La primera vez que se ejecute el procedimiento pivot, piv va a quedar en

la posicin 1 despus de haber realizado n-1 comparaciones. Ordenar los anteriores a


piv, evidentemente, ser trivial. Ordenar los posteriores a piv, en cambio, determinar

APUNTES PARA ALGORITMOS Y ESTRUCTURAS DE DATOS II

31

un nuevo piv, esta vez en la posicin 2, luego de haber realizado n-2 comparaciones.
Iterando esto hasta terminar, vemos que el algoritmo realiza (n-1) + (n-2) + . . . + 1
comparaciones, es decir, es cuadrtico. Qu ocurri? Ocurri que piv qued siempre
en un extremo del segmento de arreglo a ordenar.
Podemos verlo tambin utilizando recurrencias. En el caso de aplicar quick_sort a
un arreglo ordenado obtenemos

t(n) = t(0) + t(n 1) + n 1 = t(n 1) + n 1


ya que

t(0) = 0

comparaciones. Esto nos da una recurrencia no homognea con b=1 y


2
3
d=1. El polinomio caracterstico resultante es (x 1)(x 1) , o sea, (x 1) . Tenemos
una sola raz, 1, de multiplicidad 3. Por ello, obtendremos nalmente

t(n) = c1 1n + c2 n1n + c3 n2 1n = c1 + c2 n + c3 n2
para

c1 , c2

c3

y por lo tanto,

que no nos molestamos en calcular ac. Es fcil comprobar que

c3 6= 0

t(n) (n2 ).

Exactamente lo mismo ocurre al aplicar quick_sort a un arreglo ordenado al revs.


Conclusin: quick_sort es un algoritmo que se comporta como

n log n en el caso medio

pero es cuadrtico en el peor caso. Para conar en el caso medio, es necesario comprobar
que los datos a ordenar son aleatorios.
Sin embargo, existen modicaciones muy sencillas a la versin presentada ac que
permiten conar en el caso medio an si se aplica a un arreglo ordenado. La ms sencilla
consiste en elegir (pseudo)aleatoriamente el pivote, en vez de tomar siempre el primero
del segmento.

Potrebbero piacerti anche