Selección de problemas resueltos

Anuncio
Tema 1: LA EFICIENCIA DE LOS ALGORITMOS
2. Supongamos diseñado un algoritmo de coste exponencial Θ(K ), usando la
definición de Θ, el número de pasos requerido por el mismo lo podemos escribir como
n
T(n)=aK , donde a es una constante real positiva. En un cierto tiempo t y sobre un
m
determinado sistema se resuelve un problema de tamaño m; esto es TS(m)=aK =t.
Si tenemos un sistema c veces más rápido y el mismo algoritmo, el tiempo de cálculo lo
n
podemos expresar como TS’(n)=(a/c)K . La pregunta es, en el mismo tiempo t, ¿qué
tamaño del problema m’ se puede resolver?:
n
ALGORITMOS Y ESTRUCTURAS DE DATOS 2
Escuela Universitaria de Informática / Facultad de Informática
Departamento de Sistemas Informáticos y Computación
Universidad Politécnica de València
m
m’
m
m’
m
m’
aK =(a/c)K ⇒ K =(1/c)K ⇒ cK =K ⇒
m
m
m’=logk(cK )=logkc+logk K =logkc+m
Para conocer la mejora obtenida en el caso de un algoritmo de coste polinómico hay que
proceder de manera similar.
4. Estudio del algoritmo de búsqueda secuencial:
función Búsqueda (var v:TipoVector; x:TipoBase) : entero;
var i: entero; encontrado:lógico;
i:=1; encontrado:=falso;
mientras (i<=n) ∧ not encontrado hacer
Si v[i]=x entonces encontrado:=cierto
Sino i:=i+1;
fmientras;
si encontrado devuelve i sino devuelve 0;
ffunción;
Curso 2000-01
SELECCIÓN DE PROBLEMAS RESUELTOS
•
•
•
Problemas resueltos por:
A. Casanova
F. Marqués
N. Prieto
•
¿Cuál es el tamaño del problema?: El número n de elementos del vector. ¿Existen
instancias significativas?. Sí. Para un número de elementos determinado, el coste del
algoritmo va a depender de si el elemento buscado x está o no en el vector y en el
caso de que esté, de la posición del vector en la que se encuentre. Nótese que las
instrucciones de inicio, la guarda del bucle, el cuerpo del bucle y las instrucciones
de después del bucle tienen un coste constante (independiente de n), y el número de
veces que se realiza el bucle depende de si x está o no en el vector y en el caso en
que esté, depende de la posición.
¿Cuál es la complejidad del algoritmo en el caso peor?. El caso peor corresponde a
la situación de búsqueda sin éxito (x no está en v). Si tomamos como instrucción
crítica de la función la evaluación de la guarda del bucle y contamos cuantas veces
se repite, tenemos que: Tp(n)=n+1.
¿Cuál es la complejidad del algoritmo en el caso mejor?. El caso mejor se da cuando
x se encuentra en la primera posición del vector. Para esta instancia la guarda del
bucle se evalúa sólo dos veces, en la segunda encontrado está a cierto y la búsqueda
ha concluido: Tm(n)=2.
Estúdiese el coste medio del algoritmo suponiendo que la búsqueda siempre tiene
éxito y considerando que la probabilidad de que el elemento buscado se encuentra
en cualquiera de las posiciones del vector es la misma. Podemos identificar una
instancia mediante la posición del vector i en la que se encuentra x, la descripción
de todas las instancias posibles la haremos variando la i entre 1 y n. Si
consideramos como instrucción crítica la evaluación de la guarda del bucle, el coste
2
de cada una de las instancias será i+1. Por lo tanto y considerando que todas las
instancias son equiprobables, la probabilidad de cada una de ellas será (1/n). Así:
n
n
i=1
i=1
Tµ (n) = ∑ (1 / n)(i + 1) = (1 / n)∑ i + 1 = (1 / n) (n + 3) =
•
n +3
2
12. Estudio del algoritmo de búsqueda binaria:
Estúdiese el coste medio del algoritmo suponiendo que es equiprobable que el
elemento buscado esté o no en el vector y en el caso de encontrarse, todas las
posiciones son equiprobables. El coste de la búsqueda sin éxito es n+1 y la
probabilidad de esta instancia es 1/2. La búsqueda sin éxito tiene una probabilidad
de 1/2 también y por lo tanto el término aportado por cada instancia al coste medio
es (1/2n)(i+1). Así:
1
2
función BuscaBinaria (var v:TipoVector;x:TipoBase):entero;
var izq,der,medio: entero; encontrado:lógico;
izq:=1; der:=n; encontrado:=falso;
mientras (izq<=der) ∧ no encontrado hacer
medio:=(izq+der) div 2;
si v[medio]=x entonces encontrado:=cierto
sino si v[medio]>x entonces der:=medio-1
sino izq:=medio+1
fmientras;
si encontrado devuelve medio sino devuelve 0;
ffunción;
1
1 n
1
1 n
3n + 5
1
i + 1 = (n + 1) +
(i + 1) = (n + 1) +
(n + 3) =
2
n
2
2
n i=1
2
2n 2
4
i=1
n
Tµ (n) = (n + 1) + ∑
5.
n
2
número de cifras que tiene un número m es log10m+1, y por lo tanto es equivalente
decir que el coste del algoritmo es lineal con el número de cifras de m que con el
logaritmo del valor de m.
∑
función Suma (var v:TipoVector): natural;
var i,j,x:natural;
x:=0;
para i:=1 hasta n hacer
para j:=1 hasta v[i] hacer
x:=x+1;
fpara
fpara
devuelve x
ffunción;
Si consideramos como instrucción crítica x:=x+1, el número de veces que se repite esta
instrucción y por lo tanto la complejidad temporal de esta función, es:
n
T(n) = ∑ v[i]= S
•
•
El tamaño del problema es el número de elementos del vector (n).
Existen instancias significativas que están definidas en función de si x está o no en
v, y si está, de la posición en el vector en la que se encuentre.
El caso peor corresponde al de la búsqueda sin éxito. Si tomamos como instrucción
crítica la evaluación de la guarda del bucle, tenemos que
Tp(n)= log2n +1∈ Θ(log n).
El caso mejor se da cuando el elemento que se busca (x) se encuentra en la posición
central del vector, en este caso el número de veces que se evalúa la guarda es 2 y
Tm(n)= 2∈ Θ(1).
Por lo tanto podemos decir que este algoritmo tiene un coste O(log n) y Ω(1).
i=1
y coincide con la suma de los elementos del vector; es decir es 1 si la suma de los
elementos del vector es 1 ó es 0 si todos los elementos son 0. Evidentemente esto no es
correcto ya que el coste de esta función ha de ser como mínimo n que es el número de
veces que se repite la estructura de repetición más externa. ¿Dónde está el error en el
razonamiento anterior? En la elección de la instrucción crítica; deberíamos haber
tomado la comparación implícita del bucle más interno (j<=v[i]). En este caso
tenemos que el coste del algoritmo viene dado por:
n
T(n) = ∑ 1 + v[i]= n + S
i=1
11. El procedimiento cifras obtiene, a partir de un cierto valor entero m, una secuencia
de los dígitos que lo componen (sobre el vector v) y el número de cifras que tiene el
número (sobre la variable núm_cifras).
Si tomamos como tamaño del problema el valor de m y considerando como instrucción
crítica la evaluación de la guarda del bucle (q>0), tenemos que T(m)=log10m+1 ∈
Θ(logm).
Si tomamos como tamaño del problema el número de cifras de m, llamémosle n, y
considerando como instrucción crítica la evaluación de la guarda del bucle (q>0),
tenemos que T(n)=n+1∈Θ(n). No hay contradicción entre los resultados ya que el
3
14. Para encontrar el k-ésimo elemento más pequeño de un vector podemos aplicar el
método de seleccionar el mínimo k veces de la misma forma que se hace en el algoritmo
de ordenación por Selección Directa. Para que el problema esté correctamente planteado
es necesario que sobre el dominio de los elementos del vector esté definida una relación
de orden.
Tipo TipoVector=vector[1..n] de TipoBase;
función k_Min (ent v:TipoVector) devuelve TipoBase;
var i,j,pos_min:entero;
min:TipoBase;
para i:=1 hasta k hacer
min:=v[i]; pos_min:=i;
para j:=i+1 hasta n hacer
si v[j]<min entonces
min:=v[j];pos_min:=j;
fsi
fpara
v[pos_min]:=v[i];
v[i]:=min;
fpara
devuelve v[k];
ffunción;
4
•
•
•
Estudio de la complejidad temporal del algoritmo:
Tamaño del problema: número de elementos del vector v y k.
No hay instancias significativas.
Si tomamos como instrucción crítica la evaluación de la guarda del si_entonces, el
número de veces que se repite y por lo tanto el coste del algoritmo es:
k
T(n, k) = ∑
n
k
∑ 1 = ∑ n − i = nk −
i=1 j=i+1
i=1
(1 + k)k
∈ Θ(nk)
2
17. Veamos las dos soluciones que se proponen para realizar la mezcla natural o fusión
de dos vectores ordenados de forma creciente. En los dos casos utilizaremos tres índices
i,j,k para recorrer respectivamente el primer y segundo vector de entrada y el de salida.
(a) Tipo t_v1=vector[1..n] de enteros;
t_v2=vector[1..m] de enteros;
t_v3=vector[1..m+n] de enteros;
Procedimiento mezcla(u:t_v1; v:t_v2; sal w:t_v3);
Var i,j,k : natural;
P={n≥1 ∧ m≥1 ∧ ∀i:1≤i<n:v[i]≤v[i+1] ∧ ∀i:1≤i<m:u[i]≤u[i+1]}
i:=1;j:=1;k:=1;
mientras i≤n y j≤m hacer
Si (u[i]≤v[j]) entonces w[k]:=u[i]; i:=i+1;
Sino w[k]:=v[i]; j:=j+1;
fsi
k:=k+1;
fmientras;
si i≤n
entonces para l:=i hasta n hacer
w[k]:=v[l];
k:=k+1;
fpara;
sino para l:=j hasta m hacer
w[k]:=u[l];
k:=k+1;
fpara
fprocedimiento;
Complejidad Asintótica:
• Talla del problema: número de elementos de los vectores de entrada n,m.
• No hay instancias significativas
• Si tomamos como instrucción crítica el incremento de la variable local k, tenemos
que el número de veces que ésta se repite y por lo tanto el coste del algoritmo es
u[m+1]:= +∞;
para k:=1 hasta n+m hacer
Si (u[i]≤v[j]) entonces w[k]:=v[i]; i:=i+1;
Sino w[k]:=u[i]; j:=j+1;
fpara
fprocedimiento;
Complejidad Asintótica:
• Talla del problema: número de elementos de los vectores de entrada n, m.
• No hay instancias significativas
• Si tomamos como instrucción crítica el incremento de la variable local k, tenemos
que el número de veces que ésta se repite y por lo tanto el coste del algoritmo es, de
nuevo, T(n,m)∈ Θ(n+m)
20. La estrategia a seguir es similar a la búsqueda binaria. Suponemos que el tipo base
del vector es entero y que está ordenado de forma estrictamente creciente (no hay
repetidos). Utilizaremos dos índices para el recorrido del vector, izq y der, éstos
definen el intervalo de búsqueda en [izq..der]; calculamos la posición central en
este intervalo sobre la variable local m, y comparamos v[m] con m (el elemento con su
índice) si v[m] ya es mayor que m para comprobar si la propiedad se cumple (v[i]=i),
hay que buscar en la mitad inferior y si v[m] es menor que m hay que buscar en la mitad
superior. El algoritmo quedaría:
{ ∀i: 1≤i<n: v[i]<v[i+1] }
función Buscar(var v:TipoVector) devuelve entero;
var izq,der,m:entero;
izq:=1; der:=n; m:=(izq+der) div 2; {Inicio}
mientras (v[m]≠m ∧ izq<der) hacer
{1≤izq≤m<der≤n}
opción
v[m]<m: izq:=m+1; {1≤izq≤n}
v[m]>m: der:=m; {1<der≤n}
fopción ;
{1≤izq≤m≤der≤n}
m:=(izq+der) div 2 ;
{1≤m≤n}
fmientras
{(v[m]=m) ∨ (izq=der=m)}
si izq>der devuelve 0 sino devuelve m;
ffunción;
T(n,m)∈Θ(n+m)
(b) Para esta segunda versión modificaremos los tipos de los vectores de entrada para
extender el rango una posición:
tipo t_v1=vector[1..n+1] de enteros;
t_v2=vector[1..m+1] de enteros;
Procedimiento mezcla_con_centinelas (u:t_v1;v:t_v2; sal w:t_v3);
Var i,j,k : natural;
{n≥1 ∧ m≥1 ∧ ∀i:1≤i<n:v[i]≤v[i+1] ∧ ∀i:1≤i<m:u[i]≤u[i+1] }
i:=1; j:=1;
v[n+1]:= +∞;
5
6
Tema 2: DISEÑO Y ANÁLISIS DE ALGORITMOS RECURSIVOS.
2. El número de llamadas que hace la función se puede expresar mediante las siguientes
ecuaciones de recurrencia:
llamadas(n)=1+llamadas(n-1)+llamadas(n-2)
llamadas(1)=1
llamadas(0)=1
n≥2
3. Perfil de la función:
{x>0 ∨ y>0, x≥0 ∧ y≥0}
función num_caminos(x,y:entero): entero;
Dado que sólo hay dos tipos de movimientos permitidos en la retícula, ↑ y →, se
intentará reducir el problema a los puntos desde los cuales se puede alcanzar el punto
(x,y) con estas acciones elementales:
Esta recurrencia no se puede resolver por sustitución. Existen métodos conocidos para
resolver este tipo de recurrencias1, que no se han considerado en esta asignatura. De
cualquier manera, aquí se tratará de obtener una aproximación.
Considérese la traza de llamadas que se efectuaría para n=4 y que se despliega a
continuación:
(x-1,y)
(x,y)
(x,y-1)
fib(4)
fib(3)
fib(2)
fib(2)
fib(1)
fib(1)
fib(0)
fib(1) fib(0)
Si se hiciera una traza análoga para n=5, se podría comprobar que, aproximadamente, el
número de llamadas se duplica. A continuación, se va a demostrar por inducción que
∀n:n≥2:2n/2≤llamadas(n)≤2n
•
•
Caso base: n entre 2 y 3.
Si n=2 se comprueba que 2≤llamadas(2)=3≤22=4.
Si n=3 se comprueba que 23/2≤llamadas(3)=5≤23=8.
Caso general: n>3. Como se ha hecho notar, en este caso se tiene que
•
Caso base: Para los puntos que están sobre los ejes, x=0, y=0, sólo hay una manera
de llegar desde el origen, moviéndose a lo largo del eje correspondiente.
• Caso general: Para el resto de puntos, x>0, y>0, suponiendo que se hubiese
calculado de cuántas formas diferentes se puede llegar a (x,y-1), x>0, y-1≥0, y
de cuántas a (x-1,y), x-1≥0, y>0, según el gráfico anterior, bastaría con sumar
ambas cantidades.
Función limitadora: t=x+y>0, que decrece en uno en cada llamada. Esta función
limitadora tiene una interpretación gráfica, puesto que toma el mismo valor para todos
los puntos que se encuentran en la misma diagonal:
llamadas(n)=1+llamadas(n-1)+llamadas(n-2)
siendo n-1≥2, n-2≥2. Por hipótesis de inducción, se puede suponer que
2(n-1)/2≤llamadas(n-1)≤2n-1 y que 2(n-2)/2≤llamadas(n-2)≤2n-2
Por lo tanto:
2(n-1)/2+2(n-2)/2≤llamadas(n-1)+llamadas(n-2)+1=llamadas(n)
luego 2n/2≤llamadas(n), dado que 2(n-1)/2+2(n-2)/2<2(n-2)/2+2(n-2)/2=2(n-1)/2+1=2n/2
La otra desigualdad se obtendría de una forma análoga.
Lo que se acaba de demostrar, permite establecer una cota inferior y superior al coste de
fib(n), dado que cada llamada, resueltos los subproblemas generados, se resuelve con
un paso de programa. Se puede concluir que
coste(n)∈Ω(2n/2), coste(n)∈O(2n).
Se desea llamar la atención sobre el hecho de que ambas cotas no son del mismo orden,
como se comprueba a continuación:
lím
n→∞
2n
= lím 2n − n / 2 = lím 2n / 2 = ∞
n→∞
n→∞
2n / 2
t=1 t=2 t=3 t=4...
Así, el problema para un punto (excepto para puntos sobre los ejes), se reduce a llegar
previamente a dos puntos sobre la diagonal anterior, y en dicha medida, más cercanos al
origen.
Transcripción algorítmica.
{x>0 ∨ y>0, x≥0 ∧ y≥0}
función num_caminos(x,y:entero): entero;
opción
x=0 ∨ y=0:devuelve 1;
x>0 ∧ y>0:devuelve num_caminos(x-1,y)+num_caminos(x,y-1)
fopción
ffunción;
Para estudiar el comportamiento temporal del algoritmo, se puede expresar el coste en
función de m, el número de diagonal en el que se encuentra el punto, del siguiente modo:
coste(m)=2coste(m-1)+k
1
Véase por ejemplo, el método de la ecuación característica, presentado en Fundamentos de Algoritmia.
G. Brassard, T. Bratley. Prentice Hall, 1997, capítulo 4.
7
La dificultad reside en que esta ecuación no es cierta para todos los puntos de una m
dada, porque los extremos (puntos sobre los ejes) se resuelven con un paso de
programa.
8
1
Véase, por ejemplo, el esquema de llamadas que se generan para el punto (2,2):
 
(2,2)
•
(1,2)
(0,2)
Caso general: t>1. Se puede dar el caso de que el punto esté en un extremo de la
diagonal, es decir, sobre uno de los dos ejes. En ese caso:
(2,1)
(1,1)
(0,1)
(1,1)
(1,0)
(0,1)
(2,0)
Una cota inferior se puede establecer truncando todos los caminos en el mismo nivel de
la recursión, como, para el ejemplo anterior, se muestra en la siguiente figura:
el punto está sobre el eje de abcisas).
Si el punto no está en ninguno de los ejes (x>0, y>0), entonces
Dado que (x-1,y) y (x,y-1) están sobre la diagonal t-1, por hipótesis de inducción
se puede suponer que
 x − 1 + y  (x + y − 1)!
 =
 x − 1  (x − 1)! y!
 x + y − 1 (x + y − 1)!
 =
num_caminos(x,y-1)= 
x
x!(y − 1)!


num_caminos(x-1,y)= 
(2,2)
(0,2)
(2,1)
(1,1)
(1,1)
coste(m)=2coste(m-1)+k
coste(m)=k'
m>mínimo(x,y)
m=mínimo(x,y)
Si el punto es central en su diagonal, es decir x=y=m/2, entonces:
coste(m)=2coste(m-1)+k=22coste(m-2)+k(2+1)=
=23coste(m-3)+k(22+21+1)=.....=
=2icoste(m-i)+k(2i-1+....+4+2+1)=....
=2m/2coste(m/2)+k(2m/2-1+...+2+1)
cuya solución es Θ(2m/2).
4. Cualquier camino que llegue desde el origen hasta (x,y) estará formado por una
secuencia de x+y movimientos, x hacia la derecha e y hacia arriba. Los diferentes
caminos se deben al orden en que se van efectuando estos movimientos, es decir, cada
camino se caracteriza por seleccionar cuáles de los x+y movimientos, desde el primero
que se realiza hasta el último, es uno de los x movimientos hacia la derecha. El número
de estos caminos se puede calcular entonces como las combinaciones2 de x+y
x + y
(x + y)!
 =
.
x! y!
 x 
Este cálculo debe coincidir
con el que obtiene la función recursiva del problema anterior. Se puede demostrar por
inducción sobre t=x+y, el número de diagonal en el que se encuentra el punto (x,y).
• Caso base: t=1. En esta diagonal se tiene:
1
x=0, y=1: num_caminos(0,1)=1, que coincide con   , ó
0
 
2
m
m!
Es oportuno recordar que   =
n!(m − n)!
n
num_caminos(x,y)=
(2,0)
Esta cota se puede calcular con la siguiente relación de recurrencia:
elementos tomados de x en x, Cxx + y = 
y por lo tanto
x + y
(x + y − 1)! (x + y − 1)! (x + y)(x + y − 1)!

+
=
= 
x! y!
(x − 1)! y!
x!(y − 1)!
 y 
6. Perfil y especificación:
función igualSum(v:vector[1..n] de entero;x:entero;i:entero):
booleano;
{x≥0,1≤i≤n, ∀k:1≤k≤n:v[k]≥0}
n
{igualSum(v,x,i) ↔ ∑ v[k] = x }
k =i
•
•
Caso base. El vector tiene sólo un elemento.
i=n: el problema se resuelve comprobando si v[i]=x.
Caso general. El vector v[i..n] tiene más de un elemento.
i<n:
n
∑ v[k] = x
k =i
sii
n
∑ v[k] = x–v[i].
k =i+1
es decir igualSum(v,x,i) sii igualSum(v,x-v[i],i+1).
Cabe notar que si x-v[i]<0, entonces ya se puede detectar que el resto de elementos,
v[i+1..n] no pueden sumar esta cantidad, dado que todos los elementos son ≥0.
Función limitadora: t=n-i+1, que decrece en uno en cada llamada recursiva.
Transcripción algorítmica.
función igualSum(v:vector[1..n] de entero;x:entero;i:entero):
booleano;
opción
i=n: devuelve v[i]=x;
i<n: si x<v[i] devuelve F
sino devuelve igualSum(v,x-v[i],i+1)
fsi
fopción
ffunción;
La llamada inicial será igualSum(v,x,1).
9
+ y
 cuando el

x + y

 cuando
 x 
x
num_caminos(x,y)=num_caminos(x-1,y)+num_caminos(x,y-1).
m>1
m=1
(1,2)
x + y
x
 =1, (valor de 
 0 

x + y
 (valor de
también con 
x + y
num_caminos(x,y)=1, que coincide con 
punto está sobre el eje de ordenadas) y
(1,0)
Una forma sencilla de dar una cota superior al coste es considerar la siguiente
recurrencia:
coste(m)=2coste(m-1)+k
coste(m)=k'
que es Θ(2m).
x=1, y=0: num_caminos(1,0)=1, que coincide con   .
1
10
12. Perfil y especificación:
7. Perfil y especificación:
función capicúa(c:cadena; ini,fin:índice):booleano;
función matriz_simétrica(M:matriz;n:índice): booleano;
Pre: {1≤n}
Post: {matriz_simétrica(M,n) ↔
↔ M[i,j]=M[j,i] para cada par de índices i,j,1≤j≤n,1≤i≤n
↔ ∀banda:1≤banda≤n:(∀i:1≤i≤n:M[banda,i]=M[i,banda])
Pre: {ini≤fin}
Post: {capicua(c,ini,fin) ↔
∀i:ini≤i≤fin:c.palabra[i]=c.palabra[fin-i+ini]}
Esta definición se ilustra en la figura siguiente, que corresponde a una cadena capicúa:
ini
•
Caso base: n=1. La matriz tiene un solo elemento, M[1,1], que es trivialmente
igual a sí mismo; luego la matriz es simétrica.
• Caso general: n>1. Si la banda más externa, la formada por la fila n y la columna n
tiene sus elementos dispuestos simétricamente:
∀i:1≤i≤n:M[n,i]=M[i,n] (*)
entonces la matriz será simétrica sii lo es la submatriz formada por las n-1 primeras
filas y las n-1 primeras columnas.
columna n
n-1
7
1
fila n 7 1 ... 3
En el caso en que se detecte que la n-ésima banda no es simétrica, la matriz no puede
serlo.
Función limitadora: t=n, dimensión de la matriz considerada. En el caso general, la
matriz tiene dimensión 2 ó mayor, por lo que la llamada recursiva que eventualmente se
genera, es para una matriz de dimensión no menor que 1, como exige la precondición.
Transcripción algorítmica:
función matriz_simétrica(M:matriz;n:índice): booleano;
opción
n=1: devuelve V;
n>1: si banda_simétrica(M,n) entonces devuelve
matriz_simétrica(M,n)
sino devuelve F
fsi
fopción
ffunción;
en donde:
• banda_simétrica es una función booleana que comprueba la condición (*), y
cuyo código, expresado iterativamente sería:
columna n
7
1
...
fila n 7 1 ... 3
i
i
b_sim:=V; i:=1;
mientras i<n ∧ b_sim hacer
si M[n,i]≠M[i,n] entonces
b_sim:=F
sino i:=i+1
fsi
fmientras;
devuelve b_sim;
• La primera llamada sería matriz_simétrica(M,Dim), siendo Dim la dimensión
de la matriz M.
11
...
a
fin
r
a
r
a
...
Tal como se observa, si c.palabra[ini]=c.palabra[fin], la cadena será capicúa
sii lo es la subcadena encerrada entre estos dos caracteres. Esta observación conduce a:
• Caso base: si c tiene como mucho dos caracteres, ini=fin, o ini+1=fin,
entonces, basta con comparar c.palabra[ini], con c.palabra[fin] (que son
el mismo carácter si ini=fin).
• Caso general: si c tiene tres o más caracteres, ini+1<fin, entonces se comparan
los caracteres extremos, y si coinciden, el problema se reduce a comprobar si la
subcadena c.palabra[ini+1..fin-1] es capicúa. Hay que tener en cuenta que
si c.palabra[ini] y c.palabra[fin] difieren, la cadena no es capicúa.
Función limitadora: t=n≥1, siendo n=fin-ini+1 el número de caracteres de la
cadena. En el caso general, n≥3, si se genera una llamada recursiva, es para una cadena
con dos caracteres menos, pero que tendrá como poco un carácter: el subproblema
generado es también el de comprobar si una cadena de longitud mayor o igual que uno,
más corta que la de partida, es capicúa.
De alcanzarse el caso base (la cadena es capicúa), se tendrían dos elementos si la cadena
inicialmente fuera de longitud par; si fuera de longitud impar, se reduciría a un caso
base de un elemento.
Transcripción algorítmica.
{ini≤fin}
función capicúa(c:cadena; ini,fin:índice):booleano;
opción
ini+1≥fin: devuelve c.palabra[ini]=c.palabra[fin];
ini+1<fin: {ini+1≤fin-1}
si c.palabra[ini]=c.palabra[fin] entonces
devuelve capicúa(c,ini+1,fin-1)
sino devuelve F
fsi
fopción
ffunción;
La llamada inicial será capicúa(c,1,c.longitud).
14. La talla del problema es n, y no presenta diferentes instancias.
En el caso base, el tiempo de ejecución es constante.
En el caso general, la operación concatena tiene un coste constante.
Ecuaciones de recurrencia:
coste(n)=coste(n/2)+k2
coste(n)=k1
n>0
n=0
en donde, por simplificar la notación, n/2 denota la división entera por 2.
Esta recurrencia es Θ(log2n).
12
Es interesante considerar qué sucedería con respecto al coste si en la llamada recursiva
se permutaran los parámetros de la siguiente forma:
concatena(añade(vacía,car(nmod2)),bin(c)),
entonces esta operación, produciendo la misma cadena resultante, tendría sin embargo
un coste proporcional a kn/2. Renombrando k/2 como k2:
•
•
Cas general: amb i<n,
Cas base: amb i=n,
avaluació(a,x,i)=ai+x(avaluació(a,x,i+1))
avaluació(a,x,i)=ai
Funció limitadora: t=(n-i+1), estrictament decreixent i acotada.
Transcripció algorítmica:
coste(n)=coste(n/2)+k2n n>0
coste(n)=k1
n=0
Cuya resolución por sustitución es como sigue3:
coste(n)=coste(n/2)+k2n
=coste(n/22)+k2n(1+1/2)
=coste(n/23)+k2n(1+1/2+1/22)
=coste(n/24)+k2n(1+1/2+1/22+1/23)= ...=
=coste(n/2i)+k2n(1+1/2+1/22+...+1/2i-1)= ...=
=coste(0)+k2n(1+1/2+1/22+...+1/2log2(n)+1-1)
dado que se llega al caso base después de log2n+1 reducciones de la talla del
problema.
Esta recurrencia es Θ(n), ya que la serie (1+ 1/2 + 1/4 +...+ 1/2m +...) está
acotada por una constante.
{ }
funció avaluació(a:vector[0..n] de reals; x:real; i:enter):
real;
opció
i<n: torna (a[i]+ x*avaluació(a,x,i+1));
i=n: torna (a[n])
fopció
ffunció;
{avaluació(a,x,i)=ai +x(ai+1+x(ai+2 +x(...(an-2+x(an-1+xan))...)))}
Crida inicial: result:= avaluació(a,X,0), éssent result una variable real.
15. En este algoritmo, hay que tener en cuenta que al ejecutar en la primera llamada
inserción_directa(a,1,n), el vector queda ordenado. Ello quiere decir que, en el
resto de llamadas, la ordenación por inserción se hará sobre subvectores ordenados, que
es el mejor caso para esta operación. En resumen:
• Coste de la primera llamada: kn2,
• Coste de las siguientes llamadas:
coste(n-1)=coste(n-2)+k2(n-1) n-1>0
coste(0)=k1,
que resuelta la recurrencia lleva a un término k3n2.
En resumen, el algoritmo es Θ(n2).
Si se extrae inserción_directa del algoritmo y se ejecuta antes de recorrer, el
resultado alcanzado es el mismo. Pero ahora el coste de recorrer es:
coste(n)=coste(n-1)+k2
coste(0)=k1,
n>0
que es Θ(n).
El coste total será debido al coste de inserción_directa(a,1,n) más el de
recorrer(a,1,n), que será Θ(n2) en el caso peor, y Θ(n) en el mejor, debido a las
diferentes instancias que muestra la ordenación por inserción.
20. Perfil i especificació:
{ }
funció avaluació(a:vector[0..n] de reals; x:real; i:enter):
real;
{avaluació(a,x,i) = ai +x(ai+1+x(ai+2 +x(...(an-2+x(an-1+xan))...)))
= ai+xai+1+x2ai+2+...+xn-i-1an-1+xn-ian }
on ai≡a[i].
Llavors la definició recursiva per al cas general es pot resumir de la forma següent:
podem avaluar tot el polinomi, multiplicant per x l'avaluació de lo de dintre del
parèntesi, sumant-li després el terme independent parcial:
3
i
Se debe tener en cuenta que, al igual que sucede con la división real, (n div 2 )div2=ndiv2
13
i+1
, i≥1
14
Tema 3: TIPOS ABSTRACTOS DE DATOS. TIPOS LINEALES.
1. Las operaciones del TAD permiten desplazar el punto de interés a lo largo de las
posiciones de la lista, pero no hay ninguna operación que diga qué orden ocupa la
posición de interés en la secuencia. Por ello, se recorrerá secuencialmente la lista,
utilizando una variable entera contador que cuente los elementos que se visitan en el
recorrido:
principio(l);
{la lista l puede estar vacía, y su punto de interés
estar a la derecha del todo}
contador:=0;
mientras ¬esfin(l) hacer
contador:=contador+1
fmientras
3. a) El procedimiento que se pide tendrá la siguiente especificación:
{l=L1L2…Ln ∧ e=E ∧ 1”SRV”Q ∧ ∀k:1≤k<n:Lk≤Lk+1}
inserta_ord(l,e)
{l=L’1L’2…L’i-1EL’i+1…L’n+1 ∧ L1…Li-1=L’1…L’i-1
∧ Li…Ln=L’i+1…L’n+1 ∧ pos=i+1 ∧ ∀k:1≤k<n+1:L’k≤L’k+1}
b) Se recorrerá la lista de izquierda a derecha mediante un bucle en el que se irá
avanzando el punto de interés, buscando el primer elemento que sea mayor o igual que
e. Según esta estrategia, a lo largo del bucle se tendrá:
l=L1L2..Lpos-1Lpos…Ln, 1≤pos≤n+1,
en donde los elementos anteriores al punto de interés (realzados en negrita), son los
elementos que se ha comprobado que son menores que e. Se usará una variable
booleana menores que indique que todos los elementos revisados hasta el momento
son menores que e.
El cuerpo de la operación quedaría como sigue:
principio(l); menores:=V;
mientras ¬esfin(n) ∧ menores hacer
si e”UHFXSHUDO HQWRQFHV menores:=F
sino siguiente(l)
fmientras;
inserta(l,e);
Nótese que tanto si el bucle acaba llevando el punto de interés a la derecha del todo, o
sobre un elemento de la lista mayor o igual que e, se debe insertar el elemento en dicha
posición.
c) Sobre la representación del tipo, la operación seguiría la misma estrategia:
l.pos:=l.prim; menores:=V;
mientras l.pos≠l.ultimo ∧ menores hacer
si e”OSRVAVLJAGDWR HQWRQFHV menores:=F
sino l.pos:=l.pos^.sig
fmientras;
...
15
Situado el punto de interés en el lugar que corresponde, la operación se completaría con
el código que implementa la inserción de un elemento en referencia a dicho punto de
interés. (La resolución del ejercicio 10 discute una variante de dicha inserción).
4. Perfil de la operación: procedimiento invertirq(ent/sal q: cola);
Especificación:
{q=Q1Q2…Qn, n•`
invertirq(q)
{n=0→ q=λ , n>0 → q= Q’1Q’2…Q’n ∧ Q’1Q’2…Q’n = QnQn-1…Q1 }
Identificación de caso base y caso general.
• Caso base: q=λ. La cola coincide con su invertida.
• Caso general: q≠λ. Es decir, se tiene una cola q=Q1Q2…Qn, con uno o más
elementos, n>0. Para reducir el problema a uno análogo, pero más pequeño o
cercano al caso base, se desencola el primer elemento, reduciendo el número de
elementos de la cola. Supóngase que se invierte la secuencia de elementos Q2…Qn
que quedan en la cola y se llega a tener que q=Q’1…Q’n-1=Qn…Q2. Entonces
bastará con encolar Q1, para conseguir la cola invertida.
Nótese que la función limitadora t=n, n≥0, se reduce de uno en uno en cada llamada
recursiva, hasta llegar a hacerse cero en el caso base. Ello, además de asegurar que
efectivamente el tamaño del problema se va reduciendo recursivamente, indica que el
número de llamadas que se harán al procedimiento para invertir una cola de n elementos
es n, lo que implica un coste lineal, dado que se puede suponer que las operaciones del
tipo cola tienen un coste Θ(1) (véase las implementaciones eficientes propuestas en
clase de teoría).
Transcripción algorítmica:
procedimiento invertirq(ent/sal q:cola);
var e:elemento;
si ¬ vaciaq?(q) entonces
e:=recupera(q);
desencolar(q);
invertirq(q);
encolar(q,e)
{sino la cola ya está invertida}
fsi
fprocedimiento;
En función del número de elementos de la cola, el coste del algoritmo se puede expresar
como
coste(n)= k + coste(n-1)
n>0
coste(n)= k’
n=0
que corresponde a un coste Θ(n), como por otra parte, ya se había hecho notar al
discutir la función limitadora. El ejercicio 13 del boletín pide que se calcule también
este coste en el caso de que la cola estuviese implementada con vectores “no
circulares”. Demuéstrese que el coste es entonces Θ(n2).
6. a) { q=Q1Q2...Qm,Qm+1...Qn ∧ 0≤m≤n ∧ pos=m ∧ e=E ∧ p=P }
Procediment encolar_PR(e/s q:cola; e:elemento; p:prioridad);
{ ( p=normal → q=Q1Q2...Qm,Qm+1...QnE ∧ 0≤m≤n ∧ pos=m ) ∨
( p=urgente → q=Q1Q2...QmE,Qm+1...Qn ∧ 0≤m≤n ∧ pos=m+1 ) }
16
b) És suficient amb modificar la declaració d'una cola del mode següent:
cola= record
primero: enlace;
pos: enlace;
ultimo: enlace
end;
c) Procedure encolar_PR(var q:cola; e:elemento; p:prioridad);
var pr: enlace;
Begin
New(pr); pr^.dato:=e;
if q.primero=nil {cua buida}
then begin
pr^.siguiente:=nil;
q.primero:=pr;
q.ultimo:=pr;
if p=urgente then q.pos:=pr:
end
else {cua no buida}
if p=normal
then begin
pr^.siguiente:=nil;
q.ultimo^.seg:=pr;
q.ultimo:=pr
end
else begin {p=urgente}
if q.pos = nil {el primer urgent }
then begin
pr^.siguiente:=q.primero;
q.primero:=pr
end
else begin
pr^.siguiente:=q.pos^.siguiente;
q.pos^.siguiente:=pr;
end;
if q.pos=q.ultimo then {l'últim urgent}
q.ultimo:=pr;
q.pos:=pr;
end
end;
on l’actualització dels punters apareix amb línies discontínues.
10. Para que sea directamente accesible el elemento anterior desde uno dado, se cambia
la definición del tipo nodo a:
nodo= tupla
dato: elemento;
pred,sig: ptr
ftupla
El campo pred permite el acceso al que se refiere el párrafo anterior, de forma que una
lista l=L1L2..Ln, n≥0, vendrá representada por la estructura de la siguiente figura.
Nótese que el nodo ficticio no tiene ningún predecesor, por ello el campo
correspondiente está a nil.
pri pos ult
l
n
i
l
L1
pr
Ln
...
pri pos
ult
l
aux
E
Lpos
….
y
pri pos
...
Ln
ult
l
aux
...
E
elements amb prioritat normal
17
n
i
l
Implementación de la operación de inserción.
El uso del nodo ficticio al principio de la lista, hace que únicamente la operación creal
afecte al campo pri de la tupla que implementa a la lista. Habrá que distinguir dos
casos:
• El punto de interés está sobre uno de los elementos de la lista. La inserción sólo
afectará a l.pos.
• El punto de interés está a la derecha del todo, es decir l.pos=l.ult. Nótese que en
el caso de la lista vacía, ambos punteros hacen referencia al nodo ficticio. La
inserción afectará a ambos punteros.
Suponiendo que se ha creado un nuevo nodo para albergar el elemento a través de un
puntero auxiliar, estos dos casos serán respectivamente:
Els subcasos que apareixen quan s’encua un element de prioritat urgent en una cua no
buida es deuen a que alguna de les parts, la d’elements urgents o la de normals, pot estar
buida.
Per exemple, siga el cas en el que q.pos=nil (la cua sols té elements de prioritat
normal) i la prioritat d’e és urgent:
primero pos ultimo
q
nil
Lpos
...
...
Ln
E
n
i
l
donde la actualización de los punteros se muestra con líneas discontinuas.
18
n
i
l
En el siguiente procedimiento, se resalta en negrita las diferencias que hay entre el
código de esta operación, y el de la operación de inserción del TAD Lista con punto de
interés visto en clase.
procedimiento insertar(ent/sal l:lista; e:elemento);
var aux:ptr;
{inserción del nuevo nodo en la lista}
NEW(aux);
aux^.dato:=e; aux^.sig:=l.pos^.sig;
aux^.pred:=l.pos; l.pos^.sig:=aux;
{ajuste del punto de interés según los casos}
si l.pos=l.ult entonces l.pos:=aux; l.ultimo:=aux
sino aux^.sig^.pred:=aux; l.pos:=aux
fsi
fprocedimiento;
Transcripción algorítmica:
procedimiento cambiar_signo(p:pila;sal p’: pila);
var e:elemento;
si ¬ vaciap?(p) entonces
e:=-1*tope(p);
desapilar(p);
cambiar_signo(p,p’);
apilar(p’,e)
sino creap(p’)
fsi
fprocedimiento;
15. Especificación de la operación:
función sombrero?(p,p’: pila):booleano;
12. a) El tipo Lista con punto de interés, dado que las operaciones se refererirán a
aquella línea sobre la que se haya situado el punto de interés. Así se podría declarar
tipo texto=lista
en donde el tipo lista se habría declarado como una lista con punto de interés cuyos
elementos fuesen de un tipo línea, convenientemente definido.
b) Por ejemplo, considérese la operación de intercambiar una línea dada con la que le
precede en el texto. Se debería implementar de acuerdo a la siguiente especificación:
{f=L1L2…Li-1Li…Ln ∧ pos=i ∧ 2”SRV”Q`
procedimiento intercambiar_adelante(ent/sal f: texto)
{f=L’1L’2…L’i-1L’i…L’n = L1…LiLi-1…Ln ∧ pos=i }
Con la representación vista en clase, en la cual f.pos apuntaría al nodo que alberga
la línea Li-1, el cuerpo del procedimiento quedaría:
línea_aux:=f.pos^.dato;
f.pos^.dato:=f.pos^.sig^.dato;
f.pos^.sig^.dato:=línea_aux;
14. Especificación de la operación:
procedimiento cambiar_signo(p:pila;sal p’:pila);
{p= P1P2…Pn, n• p’= P’1P’2…P’m, m•`
b:=sombrero?(p,p’)
{b= (P’i..P’m=P1..Pn, para algún i, 1”L”PQ`
Identificación de caso base y caso general.
• Caso base: p=λ. La pila p es sombrero de cualquier otra pila, vacía o no.
• Caso general: p≠λ. Es decir, p=P1P2…Pn, con uno o más elementos, n>0. Si p’ está
vacía, p no puede ser sombrero de p’. En caso contrario, se pueden comparar los
topes de las pilas:
Si los topes de ambas coinciden, entonces el problema se puede reducir desapilando
en ambas pilas:
p =P1P2...Pn-1Pn,
p’=P’1P’2…P’m-1Pn,
entonces p será sombrero de p’ sii P1…Pn-1=P’i…P’m-1, para algún i.
Si los topes difieren, no es necesario hacer más comprobaciones: p no puede ser
sombrero de p’.
La reducción recursiva del problema a un problema menor se pone de manifiesto
tomando como función limitadora t=n, n≥0, la cual decrece en uno cada vez que se
genera una nueva llamada.
Transcripción algorítmica:
{p= P1P2…Pn, n•`
cambiar_signo(p,p’)
{p’=P’1...P’n=-P1…-Pn}
Identificación de caso base y caso general.
• Caso base: p=λ. La pila p’debe ser la pila vacía.
• Caso general: p≠λ. Es decir, p=P1P2…Pn, con uno o más elementos, n>0. Si se
reducen los elementos de la pila a P1P2…Pn-1, y se obtiene en p’ su copia con los
elementos cambiados de signo -P1-P2…-Pn-1, bastará apilar -Pn en p’ para resolver
el problema.
Para este diseño, cuya transcripción algorítmica se da a continuación, se puede aplicar
una discusión sobre la función limitadora y el coste análoga a la que, para la operación
de invertir una cola, se hace en la resolución del problema 4.
19
procedimiento sombrero?(ent/sal p,p’:pila);
opción
¬ vaciap?(p):opción
¬vaciap?(p’):si tope(p)=tope(q) entonces
desapilar(p); desapilar(p’);
devuelve sombrero(p,p’);
sino devuelve F
fsi;
vacia?(p’): devuelve F
fopción;
vaciap?(p): devuelve V
fopción
fprocedimiento;
En función del número de elementos n, de la pila p, el coste del algoritmo será:
20
En el caso peor, cuando p es sombrero de p’:
coste(n)= k + coste(n-1)
n>0
coste(n)=k’
n=0
que pertenece a Θ(n)
En el caso mejor, cuando el tope de p no aparece como tope de p’, el coste es Θ(1).
21
Descargar