Está en la página 1de 15

Divide y Vencerás

Técnica de diseño:

•Descomponer el caso a resolver en un número de subcasos más pequeños del mismo problema.

•Resolver independientemente cada subcaso.

•Combinar los resultados para construir la solución del caso original.

Este proceso se debe aplicar recursivamente.

La eficiencia de esta técnica se debe a cómo se resuelvan los subcasos.

Programación III

Divide y Vencerás

Esquema genérico

función divide_y_vencerás (c: tipocaso) : tiposolución;

, si c es suficientemente simple entonces devolver solución_simple(c) sino descomponer c en x 1 , para i1 hasta k hacer y i divide_y_vencerás (x i )

var x 1 ,

x k : tipocaso; y 1 ,

,y k : tiposolución;

,

x

k

fpara devolver combinar_solucion_subcasos(y 1 ,

fsi

ffunción

, y k )

Programación III

Divide y Vencerás

•El número de subejemplares, k, suele ser pequeño e independiente de del caso particular a resolver.

•Cuando k = 1, el esquema divide_y_vencerás pasa a llamarse reducción simplificación). En estos casos se trata de reducir la solución de un caso muy grande a la de uno más pequeño.

•Algunos algoritmos de divide_y_vencerás pueden necesitar que el primer subejemplar esté resuelto antes de formular el segundo subejemplar.

Programación III

Divide y Vencerás

Para que el enfoque divide_y_vencerás merezca la pena, se deben cumplir las siguientes condiciones:

•Debe ser posible descomponer el caso a resolver en subcasos.

•Se debe poder componer la solución a partir de las soluciones de los subcasos de un modo eficiente.

•Los subejemplares deben ser, en la medida de lo posible, aproximadamente del mismo tamaño.

Programación III

Divide y Vencerás

Cálculo de la complejidad para el caso general

Sea n el tamaño de nuestro caso original.

Y sea k el número de subcasos.

Supongamos que existe una constante b t.q. el tamaño de los k subcasos es aprox. n/b .

Si g(n) es el tiempo requerido por el algoritmo divide_y_vencerás

en casos de tamaño n, sin contar el tiempo necesario para realizar las

llamadas recursivas

t(n) = k*t(n ÷ b) + g(n)

Donde t(n) es el tiempo total requerido por el algoritmo divide_y_vencerás, siempre que n sea lo suficientemente grande.

Programación III

Divide y Vencerás

Si existe un entero p t.q. g(n) ∈ Θ(n p ), entonces podemos concluir

que:

T(n)

Θ(n

Θ(n p log n)

p )

Θ(n log b k )

si k < b p si k = b p si k > b p

Esta solución la hemos hallado utilizando la solución del ejemplo 4.7.13 del libro “Fundamentos de Algoritmia”.

Programación III

Divide y Vencerás

Falta por ver cuándo se deja de dividir el caso en subcasos y se resuelve con un algoritmo básico, es decir, cómo se determina el umbral.

•El umbral será un n 0 t.q. cuando el tamaño del caso a tratar sea menor o igual, se resuelva con un algoritmo básico, es decir, no se generen más llamadas recursivas.

•La determinación del umbral óptimo, es un problema complejo. Para un mismo problema puede variar con la implementación concreta o con el tamaño de n.

•Dada una implementación particular, puede calcularse empíricamente. Este sistema puede requerir unas cantidades notables de tiempo de computadora y humano.

Programación III

Divide y Vencerás

•También puede emplearse una técnica híbrida para su cálculo:

Primero se deben obtener las ecuaciones de recurrencia de nuestro algoritmo (estudio teórico).

Determinar empíricamente los valores de las constantes que se utilizan en dichas ecuaciones para la implementación concreta que estamos empleando.

El umbral óptimo se estima hallando el tamaño n del caso para el cual no hay diferencia entre aplicar directamente el algoritmo clásico o pasar a un nivel más de recursión.

Programación III

PROBLEMA: Búsqueda binaria

Sea T[1

T[j] T[i] siempre que n j i 1

Sea x un elemento. El problema consiste en buscar x en la matriz T, si es que está, es decir, queremos hallar un i t.q.

n]

una matriz t.q.:

n i

1 y T[i] x > T[i-1]

T[0] = -

T[n + 1] = +

La aproximación evidente a este problema consiste en examinar secuencialmente todos los elementos de T, hasta que encontremos el elemento buscado o lleguemos al final de la matriz.

Programación III

PROBLEMA: Búsqueda binaria

función secuencial(T[1 n],x)

{Búsqueda secuencial de x en una matriz}

para i 1 hasta n hacer

si T[i] x entonces

fsi

fpara

devolver n+1

ffunción

devolver i

Programación III

PROBLEMA: Búsqueda binaria

PROBLEMA: Búsqueda binaria Es fácil ver que este algoritmo secuencial de búsqueda estará en el orden

Es fácil ver que este algoritmo secuencial de búsqueda estará en el orden de Θ (n) (el caso peor es de orden (n) y el mejor Ο(1)).

Para acelerar la búsqueda podemos aprovechar la propiedad de que la matriz esté ordenada. De este modo podremos dividir la matriz en dos mitades y buscar el elemento x, allí donde estén los elementos mayores o iguales que él.

x = 12

1

2

3

4

5

6

7

8

9

10

11

-5

-2

0

3

8

8

9

12

12

26

31

T[k] x ?

i

k

j

no

 

i

k

j

i

k

j

ik

j

no

ij

i = j alto

Programación III

j no   i k j sí i k j sí ik j no ij i

PROBLEMA: Búsqueda binaria

Identificación del problema con el esquema

• El problema se puede descomponer en subproblemas de menor tamaño

Se reduce el espacio de búsqueda a la mitad en cada paso

• No hay soluciones parciales, la solución es única

(la solución de un nuevo subproblema es la solución del problema original)

• La descomposición en subproblemas continúa hasta llegar a la solución (encontrar el elemento o determinar que no está )

Programación III

PROBLEMA: Búsqueda binaria

Pasos del algoritmo de búsqueda binaria :

1. Se compara el valor a buscar x con el que se encuentra en la posición mitad del vector

k =

n / 2

2. Si x T[k] la búsqueda se restringe al subvector: T[1

k]

Si x > T[k] la búsqueda se restringe al subvector: T[k+1

n]

3. Los pasos 1 y 2 continuarán hasta:

- localizar x

- determinar que no está en el vector

Programación III

 

PROBLEMA: Búsqueda binaria

función búsquedabin(T[1 n],x) si n = 0 o x > T[n] entonces devolver n + 1 sino devolver binrec(T[1 n],x) fsi

ffunción

función binrec (T[i j],x) {Búsqueda binaria de x en la submatriz T[i si i = j entonces devolver i

j]

con la seguridad de que T[i-1] < x <= T[j]}

 

fsi k (i + j)

÷ 2

si x <= T[k] entonces devolver binrec(T[i k],x)

sino devolver binrec(T[k + 1 j],x) fsi

 

ffunción

Programación III

 

PROBLEMA: Búsqueda binaria

Coste El tiempo requerido por busqueda_binaria () sería:

{ para aplicar las ecuaciones conocidas g(n) ∈ Θ (n k )}

t(n)

g(n) O(1) = O(m 0 ), Θ (n k ) Θ (n k log n)

Θ

(n log b l )

k = 0 si si si

l < b k l = b k l > b k

No combinamos soluciones: hay 1 caso l=1

En cada paso dividimos el tamaño del problema a la mitad b = 2

l = b k

{ 1 = 2 0 } t(n) ∈ Θ (n k log n)t(n) ∈ Θ (log n)

Programación III

PROBLEMA: Búsqueda binaria

Versión iterativa:

función biniter(T[1 n],x) {Búsqueda binaria iterativa de x en la matriz T} si x > T[n] entonces devolver n + 1

fsi i 1; j n; mientras i < j hacer {T[i –1] < x <= T[j]} k (i + j) ÷ 2 si x <= T[k] entonces j k sino i k + 1 fsi fmientras devolver i

ffunción

Programación III

PROBLEMA: Ordenación

Sea T[1

n]

una matriz de n elementos. Nuestro problema

es ordenar estos elementos por orden ascendente.

Nosotros conocemos varios métodos de ordenación: por selección, por inserción y por montículo. Éste último método tiene un orden de tiempo de Θ( n log n).

Hay varios algoritmos clásicos de ordenación que siguen el esquema de divide y vencerás. Nosotros vamos a estudiar dos de ellos. Ordenar por fusión (mergesort) y ordenación rápida (quicksort)

Programación III

Ordenación por fusión

El enfoque de esta técnica es el siguiente:

•Dividir el vector en dos mitades

•Ordenar esas dos mitades recursivamente

•Fusionarlas en un solo vector ordenado.

Para hacer esto, necesitaremos un algoritmo eficiente para fusionar dos matrices ordenadas U y V en una única matriz T cuya longitud sea la suma de las longitudes de U y V.

Programación III

Ordenación por fusión

procedimiento fusionar(U[1

{Fusionar las matrices ordenadas U[1

T[1

m+1],

V[1

n+1],

m]

y V[1

T[1

n]

m+n])

almacenándolas en

m+n],U[m+1]

y V[n+1] se utilizan como centinelas}

i, j 1

U[m+1], V[n+1] ← ∞

para k 1 hasta m+n hacer

si U[i] < V[j]

entonces T[k] U[i];

i i + 1

sino T[k] V[j];

j j + 1

fsi

fpara

fprocedimiento

Programación III

 

Ordenación por fusión

procedimiento ordenarporfusión (T[1 n]) si n es suficientemente pequeño entonces insertar(T) sino

 

matriz U[1

1

+ n / 2], V[1

1

+ n / 2],

U[1

n / 2] T[1

n / 2]

V[1

n / 2] T[1 + n / 2

 

n]

ordenarporfusión(U[1

n / 2] ) n / 2] )

ordenarporfusión(V[1

fusionar(U,V,T)

 

fsi

fprocedimiento

Programación III

Ordenación por fusión

Ordenación por fusión Ejemplo: Matriz que hay que ordenar 3141592653589 La matriz se divide en dos

Ejemplo:

Matriz que hay que ordenar

3141592653589
3141592653589

La matriz se divide en dos mitades

314159
314159

314159

314159
314159
314159
2653589
2653589

2653589

2653589
2653589
2653589
2653589

Una llamada recursiva a ordenar por fusión para cada mitad

113459
113459

113459

113459
113459
113459
2355689
2355689

2355689

2355689
2355689
2355689
2355689

Una llamada a fusionar

1123345556899
1123345556899

Programación III

ordenar por fusión para cada mitad 113459 2355689 Una llamada a fusionar 1123345556899 Programación III
 

Ordenación por fusión

Coste de ordenación por fusión

 
 

El tiempo requerido por mergesort (C[n]) sería:

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

g(n) ∈ Θ (n k )

k = 1

 

Combinamos 2 soluciones: l = 2

 

En cada paso dividimos el tamaño del problema a la mitad b = 2

l = b k

{ 2 = 2 1 },

t(n) ∈ Θ (n k log n)t(n) ∈ Θ (n log n)

 

(Está despreciando el tiempo requerido por insertar() ya que es con un n suficientemente pequeño)

Programación III

 

Ordenación rápida

•El primer paso es escoger un elemento de la matriz a ordenar como pivote.

•A continuación la matriz se parte a ambos lados del pivote. Los elementos mayores que el pivote se almacenan a la derecha del mismo y los demás a la izquierda.

•La matriz se ordena mediante llamadas recursivas a este algoritmo.

Programación III

Ordenación rápida

Uso del algoritmo

Se suele seleccionar como pivote el elemento que ocupa la posición central del vector o subvector a ordenar ya que:

Si todos los elementos tienen igual probabilidad, ese es tan bueno como cualquier otro

- Si el vector está semi-ordenado u ordenado esta elección será menos costosa

Elección ideal del pivote

La mejor elección sería que el pivote fuera la mediana

mediana: dado un vector T, el elemento que tiene tantos elementos menores como mayores que él en T

Su cálculo tiene un coste lineal en el caso peor

Programación III

Ordenación rápida

procedimiento pivote (T[i

j];

var l)

{Permuta los elementos de la matriz T[i

j]

y proporciona un valor l tal que, al final, i <= l <= j; T[k] <= p

para todo i <= k < l, T[l] = p, y T[k] > p para todo l< k <= j, en donde p es el valor inicial de T[i]}

p

T[i]

i; l j + 1 repetir

k

k

k + 1

hasta que T[k] > p o k >= j frepetir

repetir

l

l – 1

hasta que T[l] <= p frepetir

mientras k < l hacer intercambiar T[k] y T[l] repetir

k k + 1 hasta que T[k] > p frepetir repetir

l l – 1 hasta que T[l] <= p frepetir

fmientras intercambiar T[i] y T[l] fprocedimiento

Programación III

Ordenación rápida

procedimiento ordenacion_rapida(T[i j])

{Ordena la submatriz T[i

j]

por orden no decreciente}

si j – i es suficientemente pequeño entonces insertar (T[i j]) sino pivote(T[i j],l); ordenacion_rapida (T[i l-1]) ordenación_rapida (T[l+1 j])

fsi

fprocedimiento

Programación III

Ordenación rápida

Coste de ordenación rápida

1. Si suponemos que:

el orden inicial del vector es aleatorio

los elementos de T son diferentes en su mayoría

todas las n! permutaciones de sus elementos son igualmente probables

los subproblemas estarían equilibrados

El orden sería: O(n log n)

2. Si los subproblemas no están del todo equilibrados se suele

asumir que el coste es

O(n log n)

Programación III

Ordenación rápida

Coste de ordenación rápida

3. Los casos peores:

el vector está inicialmente ordenado

todos los elementos son iguales

l = i

en todos los casos y se generan 2

subproblemas:

uno de tamaño 0

el segundo de tamaño j-i + 1

El coste sería: O(n 2 )

En general se suele considerar el caso promedio: O(n log n)

Programación III

 

Ordenación rápida

Coste de ordenación rápida

 

Si partimos de la hipótesis de que:

todos los elementos de T son diferentes

cada una de las n! permutaciones tienen la misma probabilidad

t(m)

tiempo medio de ordenacion_rapida(T[i j])

m = j - i + 1

{nº de elementos en T[i j]}

El valor l devuelto por pivote(T[1 con probabilidad 1/n

n])

será un entero entre 1 y n

pivote(T[1

n])

requiere un tiempo g(n) ∈ Θ(n)

El tiempo medio para ejecutar las llamadas recursivas

generadas será:

t(l-1) + t(n -l)

 

Programación III

Ordenación rápida

Coste de ordenación rápida

El tiempo será por tanto:

t(n) ∈ Θ(n) + 1/n (t(l-1) + t(n-l)) (1)

sabemos que: g(n) dn

sabemos que hay un valor k tal que 0 k n -1 que aparece 2 veces en (1):

t(n) dn + 2/n t(k)

para n suficientemente grande

No se pueden aplicar las recurrencias lineales vistas anteriormente

Por analogía con otros métodos de ordenación es razonable

esperar que :

t(n) = O(n log n)

Programación III