Facultad de Ingeniería de Sistemas e Informática Divide y Vencerás Métodos para el algoritmo de ordenación por mezcla Se tiene el siguiente conjunto de elementos para ordenar con clasificación por mezcla: • 1ra. Pasada . 14 07 03 12 09 11 06 02 Como estamos usando divide y vencerás para ordenar, tenemos que decidir cómo se van a ver nuestros subproblemas. • El problema completo es ordenar todo un arreglo. Digamos que un subproblema es ordenar un subarreglo. • En particular, vamos a pensar que un subproblema es ordenar el subarreglo que empieza en el índice p y va hasta el índice r. • Será conveniente tener una notación para un subarreglo, así que digamos que array[p..r]. denota este subarreglo de array. En términos de nuestra notación, para un arreglo de n elementos, podemos decir que el problema original es ordenar array[0..n-1]. Aquí está cómo el ordenamiento por mezcla utiliza divide y vencerás: • • • Divide al encontrar el número q de la posición a medio camino entre p y r. Haz este paso de la misma manera en que encontramos el punto medio en la búsqueda binaria: suma p y r, divide entre 2 y redondea hacia abajo. Vence al ordenar de manera recursiva los subarreglos en cada uno de los dos subproblemas creados por el paso de dividir. Es decir, ordena de manera recursiva el subarreglo array[p..q] y ordena de manera recursiva el subarreglo array[q+1..r]. Combina al mezclar los dos subarreglos ordenados de regreso en un solo subarreglo ordenado array[p..r]. Necesitamos un caso base. El caso base es el subarreglo que contiene menos de dos elementos, es decir, cuando p ≥ r , ya que un subarreglo sin elementos o con solo un elemento ya está ordenado. Así que vamos a dividir-vencer-combinar solo cuando p < r. Veamos un ejemplo. Vamos a empezar con array que contiene a [14, 7, 3, 12, 9, 11, 6, 2], de modo que el primer subarreglo es en realidad el arreglo completo, array[0..7] (p=0 y r=7). Este subarreglo tiene por lo menos dos elementos, así que no es un caso base. ➢ En el paso de dividir, calculamos q=3. ➢ El paso de vencer nos hace ordenar los dos subarreglos array[0..3], que contiene a [14, 7, 3, 12], y array[4..7], que contiene a [9, 11, 6, 2]. o Cuando regresamos del paso de vencer, cada uno de los dos subarreglos está ordenado: array[0..3] contiene a [3, 7, 12, 14] y array[4..7] contiene a [2, 6, 9, 11], de modo que el arreglo completo es [3, 7, 12, 14, 2, 6, 9, 11]. ➢ Por último, el paso de combinar mezcla los dos subarreglos en la primera y la segunda mitad, para producir el arreglo final ordenado [2, 3, 6, 7, 9, 11, 12, 14]. ¿Cómo se ordenó el subarreglo array[0..3]? Del mismo modo. Tiene más de dos elementos, así que no es un caso base. Lic. Jorge Luis Chávez Soto 1 Facultad de Ingeniería de Sistemas e Informática ➢ Con p=0 y r=, calcula q=1, ordena recursivamente array[0..1] ([14, 7]) y array[2..3] ([3, 12]), cuyo resultado es array[0..3] que contiene a [7, 14, 3, 12], y ➢ mezcla la primera mitad con la segunda mitad, para producir [3, 7, 12, 14]. ¿Cómo se ordenó el subarreglo array[0..1]? Con p=0 p y r=1, calcula q=0, ordena recursivamente array[0..0] ([14]) y array[1..1] ([7]), cuyo resultado es array[0..1] que sigue conteniendo a [14, 7], y mezcla la primera mitad con la segunda mitad, para producir [7, 14]. Los subarreglos array[0..0] y array[1..1] son casos base, ya que cada uno contiene menos de dos elementos. Aquí está cómo se desarrolla todo el algoritmo del ordenamiento por mezcla: PROCEDURE Mezcla(VAR a,b:vector;prim,ult:CARDINAL); (* utiliza el vector b como auxiliar para realizar la mezcla *) VAR mitad:CARDINAL; BEGIN IF prim<ult THEN mitad:=(prim+ult)DIV 2; Mezcla(a,b,prim,mitad); Mezcla(a,b,mitad+1,ult); Combinar(a,b,prim,mitad,mitad+1,ult) Lic. Jorge Luis Chávez Soto 2 Facultad de Ingeniería de Sistemas e Informática END END Mezcla; PROCEDURE Combinar(VAR a,b:vector;p1,u1,p2,u2:CARDINAL); (* mezcla ordenadamente los subvectores a[p1..u1] y a[p2..u2] suponiendo que estos estan ya ordenados y que son consecutivos (p2=u1+1), utilizando el vector auxiliar b *) VAR i1,i2,k:CARDINAL; BEGIN IF (p1>u1) OR (p2>u2) THEN RETURN END; FOR k:=p1 TO u2 DO b[k]:=a[k] END; (* volcamos a en b *) i1:=p1;i2:=p2; (* cada indice se encarga de un subvector *) FOR k:=p1 TO u2 DO IF b[i1]<=b[i2] THEN a[k]:=b[i1]; IF i1<u1 THEN INC(i1) ELSE b[i1]:=MAX(INTEGER) END ELSE a[k]:=b[i2]; IF i2<u2 THEN INC(i2) ELSE b[i2]:=MAX(INTEGER) END END END END Combinar; LA SUBSECUENCIA DE SUMA MÁXIMA Dados n enteros cualesquiera a1,a2,...,an, necesitamos encontrar el valor de la expresión: que calcula el máximo de las sumas parciales de elementos consecutivos. Como ejemplo, dados los 6 enteros (–2,11,–4,13,–5,–2) la solución al problema es 20 (suma de a2 hasta a4). Deseamos implementar un algoritmo Divide y Vencerás de complejidad nlogn que resuelva el problema. ¿Existe algún otro algoritmo que lo resuelva en menor tiempo? Solución Lic. Jorge Luis Chávez Soto 3 Facultad de Ingeniería de Sistemas e Informática Existe una solución trivial a este problema, basada en calcular todas las posibles sumas y escoger la de valor máximo (esto es, mediante un algoritmo de “fuerza bruta”) cuyo orden de complejidad es O(n3). Esto lo hace bastante ineficiente para valores grandes de n: PROCEDURE Sumamax(VAR a:vector;prim,ult:CARDINAL):CARDINAL; VAR izq,der,i:CARDINAL; max_aux,suma:INTEGER; BEGIN max_aux:=0; FOR izq:=prim TO ult DO FOR der:=izq TO ult DO suma:=0; FOR i:=izq TO der DO suma:=suma+a[i] END; IF suma>max_aux THEN max_aux:=suma END END END; RETURN max_aux END Sumamax; Una mejora inmediata para el algoritmo es la de evitar calcular la suma para cada posible subsecuencia, aprovechando el valor ya calculado de la suma de los valores de a[izq..der–1] para calcular la suma de los valores de a[izq..der]. Esto da lugar a la siguiente función PROCEDURE Sumamax2(VAR a:vector;prim,ult:CARDINAL):CARDINAL; VAR izq,der:CARDINAL; max_aux,suma:INTEGER; BEGIN max_aux:=0; FOR izq:=prim TO ult DO suma:=0; FOR der:=izq TO ult DO suma:=suma+a[der]; (* suma contiene la suma de a[izq..der] *) IF suma>max_aux THEN max_aux:=suma END END END; RETURN max_aux END Sumamax2; cuya complejidad es de orden O(n2), lo cual mejora sustancialmente al anterior, pero que aún no consigue la complejidad pedida. Utilizaremos ahora la técnica de Divide y Vencerás para intentar mejorar la eficiencia de los algoritmos anteriores, y lo haremos dividiremos el problema en tres subproblemas más pequeños, sobre cuyas soluciones construiremos la solución total. En este caso la subsecuencia de suma máxima puede encontrarse en uno de tres lugares. O está en la primera mitad del vector, o en la segunda, o bien contiene al punto medio del vector y se encuentra en ambas mitades. Lic. Jorge Luis Chávez Soto 4 Facultad de Ingeniería de Sistemas e Informática Las tres soluciones se combinan mediante el cálculo de su máximo para obtener la suma pedida. Los dos primeros casos pueden resolverse recursivamente. Respecto al tercero, podemos calcular la subsecuencia de suma máxima de la primera mitad que contenga al último elemento de esa primera mitad, y la subsecuencia de suma máxima de la segunda mitad que contenga al primer elemento de esa segunda mitad. Estas dos secuencias pueden concatenarse para construir la subsecuencia de suma máxima que contiene al elemento central de vector. Esto da lugar al siguiente algoritmo: PROCEDURE Sumamax3(VAR a:vector;prim,ult:CARDINAL):CARDINAL; VAR mitad,i:CARDINAL; max_izq,max_der,suma,max_aux:INTEGER; BEGIN (* casos base *) IF prim>ult THEN RETURN 0 END; IF prim=ult THEN RETURN Max2(0,a[prim]) END; mitad:=(prim+ult)DIV 2; (* casos 1 y 2 *) max_aux:=Max2(Sumamax3(a,prim,mitad),Sumamax3(a,mitad+1,ult)); (* caso 3: parte izquierda *) max_izq:=0; suma:=0; FOR i:=mitad TO prim BY -1 DO suma:=suma+a[i]; max_izq:=Max2(max_izq,suma) END; (* caso 3: parte derecha *) max_der:=0; suma:=0; FOR i:=mitad+1 TO ult DO suma:=suma+a[i]; max_der:=Max2(max_der,suma) END; (* combinacion de resultados *) RETURN Max2(max_der+max_izq,max_aux) END Sumamax3; donde la función Max2 utilizada es la que calcula el máximo de dos números enteros. El procedimiento Sumamax3 es de complejidad O(nlogn), puesto que su tiempo de ejecución T(n) viene dado por la ecuación en recurrencia T(n) = 2T(n/2) + An con la condición inicial T(1) = 7, siendo A una constante. Obsérvese además que este análisis es válido puesto que hemos añadido la palabra VAR al vector a en la definición del procedimiento. Si no, se producirían copias de a en las invocaciones recursivas, lo que incrementaría el tiempo de ejecución del procedimiento. Lic. Jorge Luis Chávez Soto 5 Facultad de Ingeniería de Sistemas e Informática Respecto a la última parte del problema, necesitamos encontrar un algoritmo aún mejor que éste. La clave va a consistir en una modificación a la idea básica del algoritmo anterior, basada en un algoritmo del tipo “en línea” Supongamos que ya tenemos la solución del problema para el subvector a[prim..i–1]. ¿Cómo podemos extender esa solución para encontrar la solución de a[prim..i]? De forma análoga al razonamiento que hicimos para el algoritmo anterior, la subsecuencia de suma máxima de a[prim..i] puede encontrarse en a[prim..i–1], o bien contener al elemento a[i]. Esto da lugar a la siguiente función: PROCEDURE Sumamax4(VAR a:vector;prim,ult:CARDINAL):CARDINAL; VAR i:CARDINAL; suma,max_anterior,max_aux:INTEGER; BEGIN max_anterior:=0; max_aux:=0; FOR i:=prim TO ult DO max_aux:=Max2(max_aux+a[i],0); max_anterior:=Max2(max_anterior,max_aux) END; RETURN max_anterior; END Sumamax4. Ejemplo: -2 11 -4 13 -5 2 La suma de a2 hasta a4 es la subsecuencia máxima a 20. Lic. Jorge Luis Chávez Soto 6