5.6-5.9

Anuncio
Ejercicio 5.6. Moda de una secuencia. La moda de la secuencia
x1 , ..., xn es el elemento que más se repite. Puede haber varios elementos que se
repiten la misma cantidad (máxima) de veces, en tal caso cualquiera de ellos es
considerado una moda. Se debe desarrollar un algoritmo que compute la moda
de una secuencia dada en un arreglo. Utilice la idea proveniente del quicksort de
separar según un pivot y luego resolver el problema para las dos subsecuencias
más chicas. Note que si se parte un conjunto en partes disjuntas entonces es fácil
recuperar la moda del conjunto en función de las modas de las partes. Calcule
de manera detallada el orden del ”peor caso” de tal algoritmo.
Vamos a aplicar la estrategia divide y vencerás para reducir el problema de
hallar la moda de la secuencia s = x1 , ..., xn en varias instancias que consistan en hallar la moda de partes más chicas de la secuencia. Claramente si la
secuencia se parte en subsecuencias disjuntas s1 , s2 , s3 con modas m1 , m2 y
m3 respectivamente, entonces la moda de s será la de mayor frecuencia entre
estas tres modas. Debemos entonces generalizar el problema pidiendo calcular
no sólo la moda, sino además su frecuencia. Ası́, si (m1 , f1 ), (m2 , f2 ) y (m3 , f3 )
son las modas con sus frecuencias de la subsecuencias s1 , s2 y s3 resp., entonces
la moda y su frecuencia de s será el par (m, f ) definido
f = max(f1 , f2 , f3 ),
m = mi tal que f = fi , donde i ∈ {1, 2, 3}.
Para resolver el problema sólo nos resta determina como efectuar la partición de la secuencia original en varias subsecuencias disjuntas. La versión 2
del pivot es una buena opción. Si la secuencia viene dada en un arreglo X[0, n),
la aplicación de pivot con pivot p parte el arreglo en tramos X[0, i), X[i, j)
y X[j, n) tales que los elementos del primer tramo son menores que p, los del
segundo tramo son todos iguales a p, y los del último tramo son mayores que
p. Luego si nos aseguramos que p sea un elemento de la secuencia original (por
ejemplo p = X[0]), entonces pivot parte el arreglo en tres tramos de longitud estrictamente menor que n. Note que no es necesario hacer una llamada recursiva
para el tramo del medio, ya que obviamente su moda es p y su frecuencia j − i.
Dejamos al lector como ejercicio el escribir en detalle el algoritmo recursivo. En
este caso, al igual que en quicksort, no se obtiene tan fácilmente una versión
iterativa, ya que al ser una recursión múltiple (o sea con más de una llamada
recursiva), necesitamos una pila para guardar las tareas pendientes.
En el peor caso el algoritmo tiene tiempo de ejecución t(n) ∈ O(n2 ), que
corresponde con malas elecciones del pivot: tomar como pivot el elemento más
grande, o el más chico. En efecto, la ecuación recursiva asintótica es
t(n) ≤ t(n − 1) + n.
Como en el caso de quicksort, el tiempo de ejecución esperado es O(n log(n)).
Ejercicio 5.9: Orden Cı́clico. Una secuencia x1 , ..., xn se dice que tiene
orden cı́clico si existe un xi tal que la secuencia xi , xi+1 , ..., xn , x1 , ..., xi−1 es
1
estrictamente creciente. Supongamos que se recibe una secuencia con orden
cı́clico almacenada en un arreglo X definido en [1, n]. Utilice una idea similar a
la de búsqueda binaria para diseñar e implementar un algoritmo que encuentre el
menor elemento de la secuencia en tiempo O(log n). Analice en forma detallada
la complejidad.
Primero analicemos los casos en los que la solución del problema es trivial.
Claramente si n = 1 entonces la respuesta es X[1]. Por otro lado, si X[1] < X[n],
entonces el arreglo está odenado y por lo tanto X[1] es la solución. Supongamos
ahora que X[1] > X[n]. Imitando la idea de búsqueda binaria, vamos a ver que
si m es el lugar del medio del arreglo, m = (n + 1) ÷ 2, entonces el valor X[m]
nos permite decidir si el mı́nimo se encuentra en X[1, m] o en X[m + 1, n]. En
efecto, si el mı́nimo se encuentra en el lugar i perteneciente al intevalo [m+1, n],
entonces se tiene
X[i] < X[i + 1] < ... < X[n] < X[1] < ... < X[m] < ...
Luego X[n] < X[m]. Apelando a la afirmación contrarecı́proca tenemos que si
X[m] ≤ X[n] entonces el mı́nimo se encuentra en X[1, m]. Por otro lado, si el
mı́nimo está en el lugar i perteneciente al intevalo [1, m], entonces se tiene
X[i] < X[i + 1] < ... < X[m] < ... < X[n] < ...
Luego X[m] < X[n]. Por lo tanto si tenemos que X[n] ≤ X[m] entonces
deducimos que el mı́nimo se encuentra en X[m + 1, n]. En resumen concluimos:
CASO X[m] ≤ X[n] : el mı́nimo está en X[1, m].
CASO X[n] ≤ X[m] : el mı́nimo está en X[m + 1, n].
Ya tenemos entonces esbozado un algoritmo recursivo que soluciona el problema. Para programarlo en pseudocódigo generalizamos reemplazando 1, n por
las variables de input i, j:
func minimo(X : array[1, n] of Int, i, j : Int) dev y : Int
{X[i, j] tiene orden cı́clico}
m : Int
if i = j ó X[i] ≤ X[j] then y := X[i]
else
m := (i + j) ÷ 2
if X[m] ≤ X[j] then y := mı́nimo(X,i,m)
else y := mı́nimo(X,m+1,j)
{y es el mı́nimo de X[i, j]}
Para garantizar la corrección del algoritmo debemos asegurarnos que en los
casos no triviales las instancias X[i, m] y X[m + 1, j] son instancias ”estrictamente más chicas” que X[i, j]. Note que si i < j (cosa garantizada si estamos
en un caso no trivial) entonces m es siempre estrictamente menor que j. Luego
2
X[i, m] es siempre más chico que X[i, j]. Por otro lado, m = i se dá si y sólo si
i + 1 = j. En tal caso X[m + 1, j] se reduce al caso trivial, que es estrictamente
menor que X[i, j].
Para efectuar el análisis de la complejidad del algoritmo fijamos n = j − i + 1
como el largo del input. Obtenemos la inecuación recursiva asintótica
t(n) ≤ t(n ÷ 2) + c
Por ende el algoritmo es O(log(n)).
Vamos ahora a obtener una versión iterativa del mismo algoritmo. Del
análisis anterior se desprende que cada vez que achicamos el intervalo [i, j] a
[i, m] o [m + 1, j] en una llamada recursiva, conservamos el valor mı́nimo en el
nuevo intervalo más chico. Luego podemos pensar un ciclo en el cual vamos
achicando [i, j] en cada iteración manteniendo la propiedad
”el mı́nimo de X[1, n] es exactamente el mı́nimo de X[i, j]”
De esta manera tendremos el mı́nimo cuando i sea igual a j o cuando X[i]
sea menor o igual que X[j] :
{1 ≤ i ≤ j ≤ n y X[i, j] tiene orden cı́clico}
m : Int
while i < j ∧ X[j] < X[i] do
m := (i + j) ÷ 2
if X[m] ≤ X[j] then j := m else i := m + 1
y := X[i]
{y es el mı́nimo de X[1, n]}
3
Descargar