Ejercicio 1 Ejercicio 2

Anuncio
Ejercicio 1
Ideas clave del ejercicio: (1) La posición hay que guardarla indirectamente según el puntaje y no directamente
según el equipo, porque no da el tiempo de actualizar la posición cuando hay muchos equipos empatados en el mismo
puntaje. (2) Cuando alguién gana un punto solo modifica la posición de sı́ mismo y de quienes estuvieran empatados
con él (esto se ve claramente en la especificación basada en #masPuntos). De este modo, hay sólo dos puntajes, a lo
sumo, a los que hay que actualizarles la posición correspondiente (ver algoritmo de regPartido para mas detalles).
Luego, hay muchas implementaciones distintas posibles combinando diccionarios sobre AVL y arreglos ordenados.
Incluso, un poco mas difı́cil, se puede usar una lista enlazada de puntajes, posiciones y cantidad de equipos apuntada
con iteradores desde desde un diccionario de equipos. Aquı́ se presenta la opción mas simple de entender.
Estructura:
equipo, puntaje y posición son nat
torneo se representa con estr donde
estr es h ptos: diccAVL(equipo,puntaje), pos: diccAVL(puntaje,h p: posición, cant: nat i) i
ptos dice para cada equipo su punaje. pos dice para cada puntaje que tenga algún equipo la posición en la que
están los equipos con ese puntaje y cuántos son. Los diccionarios devuelven y reciben todos los parámetros por copia.
Algoritmos:
Puntos(in t:estr, in e:equipo)
res ← Obtener(t.ptos, e)
Complejidad: O(log n)
Pos(in t:estr, in e:equipo)
res ← Obtener(t.pos, Obtener(t.ptos, e)).p
Complejidad: O(log n)
regPartido(in t:estr, in g:equipo, in p:equipo)
nat n ← Obtener(t.ptos, g) + 1
Definir(t.ptos, g, n)
h p: posición, cant: nat i p ← Obtener(t.pos, n − 1)
p.cant ← p.cant − 1
if p.cant = 0 then
Borrar(t.pos, n − 1)
else
Definir(t.pos, n − 1, p)
end if
if Definido(t.pos, n) then
p ← Obtener(t.pos, n)
p.cant ← p.cant + 1
Definir(t.pos, n, p)
else
p.p ← p.p + p.cant
Definir(t.pos, n, p)
end if
Complejidad: O(log n)
Ejercicio 2
La idea es la siguiente: Considerar n arreglos de longitud k, que están ordenados por la forma de generarlos. Luego
tenemos que mergearlos. Si los mergeamos de forma acumulativa (los dos primeros, el resultado con el tercero, el
resultado con el cuarto, y ası́ siguiendo) la complejidad no dá. Hay 2 opciones: 1. Mergear todos a la vez. 2. Mergearlos
de a parejas tipo “playoff”.
Escribo completa la versión 1 porque es mas simple de entender y de justificar. Aca la hice con Heap, pero se puede
hacer lo mismo con AVL.
ordenarMúltiplos(in A:arreglo(nat), in k:nat) → res : arreglo(nat)
arreglo(arreglo(nat)) B ← CrearArreglo(tam(A))
res ← CrearArreglo(k × tam(A))
ColaDePrioridadHeapMin(tupla(nat,nat,nat)) c ← CrearCola
for i = 1 hasta tam(A) do
1
B[i] ← CrearArreglo(k)
for j = 1 hasta k do
B[j] ← j × A[i]
end for
Encolar(c, hB[i][1], i, 1i)
end for
for i = 1 hasta k × tam(A) do
x ← Primero(c)
Desencolar(c)
if π3 (x) < k then
Encolar(c, langleB[π2 (x)][π3 (x)], π1 (x), π2 (x)i)
end if
res[i] ← π1 (x)
end for
Complejidad O(nk log n)
Justificación de complejidad (escueta):
Las declaraciones y creaciones iniciales son O(nk) porque es el tamaño de los arreglos que se están creando. La
primer iteración también es O(nk) porque son dos anidadas de n y k elementos repsectivamente, haciendo todas cosas
constantes dentro. Es importante notar que se encolan n cosas en la cola c. Lo primero es notar que c nunca tiene mas
de n cosas a lo largo de la última iteración. Esto es porque empieza con n y en cada paso se saca 1 y a lo suma se encola
1, con lo cual el total de elementos que contiene se mantiene o disminuye en todos los pasos. De este modo, cada paso
de la iteración cuesta O(log n) por operaciones de costo constante o logarı́tmico en la cola (solo se encola, se pregunta
el mı́nimo y se desencola el mı́nimo). Como se itera nk veces, el costo total de la iteración resulta O(nk log n), que
además domina los costos calculados de la parte anterior, con lo cual la complejidad total es O(nk log n).
Ejercicio 3
La idea es, supongamos que el resultado esta en A: dado un ı́ndice m de A, podemos, en O(log n) ver cual serı́a su
ı́ndice en el hipotético merge. Si este es i, encontramos la respuesta, si es mayor, la respuesta está a la izquierda de m
(entre los menores a m) y si es menor, la respuesta está a la derecha (entre los mayores a m). Esto nos permite hacer
una busqueda binaria considerando estos tres casos. Como consideramos el caso de la igualdad, el while solo concluye
sin haber retornado cuando no se encontró un resultado. Si el resultado no estaba en A, debe estar en B, ası́ que
hacemos lo mismo invirtiendo los roles. En esta segunda pasada el resultado se encontrará, asi que nunca llegará una
tercera pasada.
iésimoMerge(in A:arreglo(nat), in B:arreglo(nat), in i:nat) → res : nat
nat a ← 1, b ← tam(A) + 1
while a < b do
nat m ← (a + b)/2
if m + ÚltimoMenorA(B, A[m]) = i then
return A[m]
else if m + ÚltimoMenorA(B, A[m]) ¡i then
a←m+1
else
b←m−1
end if
end while
return iésimoMerge(B,A,i)
Complejidad: O(log2 n)
Justificación de complejidad (escueta):
Por el razonamiento explicado antes hay 2 pasadas del algoritmo (una sola llamada recursiva). Cada una de ellas
tiene una iteración que itera a lo sumo log n veces porque el intervalo [a, b] comienza con tamaño n y su tamaño se
divide por dos a cada paso. Cada iteración cuesta log n por una cantidad constante de llamados a ÚltimoMenorA
y una cantidad constante de instrucciones O(1), con lo cual el costo de toda la iteración es O(log2 n). Fuera de la
iteración y del llamado recursivo, todo cuesta O(1), con lo cual termina la justificación.
2
Descargar