ALGORITMOS
ALGORITMOS
Septiembre 2018
Documento maquetado con TEXiS v.1.0.
Y el profesor
José Alberto Verdejo López
Septiembre 2018
Copyright
c Beatriz Coronado Sanz
A mis padres
Agradecimientos
Todo lo que empieza tiene que acabar, o al menos eso dicen. Hace 5 años empecé
este doble grado y me parece increíble que llegue ya a su fin. Han sido años muy duros,
con muchas alegrías pero también muchas tristezas. Ha habido asignaturas que parecían
imposibles de sacar y otras que hubiera seguido dándolas un cuatrimestre más o dos solo
para seguir aprendiendo.
Llegué sin saber nada de informática y me voy ahora con grandes conocimientos sobre
ella. Muy feliz por haber descubierto una rama que no conocía y que seguramente sea
clave en mi futuro. Mi amor por las matemáticas no ha cambiado pero ahora sé muchas
más cosas y me voy con el deseo de seguir profundizando en la parte que se mezcla con
la programación que tanto me gusta.
En primer lugar, y para que no se me olvide, a todos los profesores que me han dado
clase, ya sea en la facultad de Informática o en la de Matemáticas. Sin vosotros no habría
aprendido nada nuevo y aunque hay profes y profes el balance que me llevo es positivo.
Intentaré aplicar lo que me habéis enseñado todos estos años en mi vida aunque no pro-
meto nada.
A mis amigas del colegio Alba e Iria que, aunque nos hemos visto menos, habéis se-
guido conmigo y me habéis apoyado cuando las cosas no salían del todo bien.
A mis compañeras de clase Elena y Patri que, aunque aún os queda un año para ter-
minar, sé que en nada escribiréis una memoria como esta y acabaréis igual que yo. Sin
vosotras el día a día en la universidad no habría sido lo mismo. Sobre todo sin ti Elena,
eterna compañera de prácticas. No sé que voy a hacer ahora sin nuestras prácticas infinitas
con mil líneas de comentarios.
A las mejores amigas que he podido conseguir gracias a mi deporte favorito, el fútbol.
Reich, Lore, Alba me refiero a vosotras. Mi vida no sería la misma sin haberos conocido.
Gracias por todo lo que habéis hecho por mi, por leerme y aconsejarme cuando he tenido
algún problema. Nuestra vida va a cambiar a partir de ahora pero sé que nuestra amistad
no.
vii
viii Agradecimientos
Y por último, pero no menos importante, a mi familia. A mis padres por apoyarme en
todo momento, confiar en mí y darme la libertad para tomar mis propias decisiones me
equivoqué con ellas o no. Y a mis dos hermanos pequeños, intentaré devolveros todo lo
que habéis hecho por mi estos años que estáis en la uni.
En definitiva, gracias a todos los que habéis formado parte de mi vida estos años. Se
termina una etapa pero empieza una nueva y espero que sea igual de memorable que esta
que he vivido.
Resumen
ix
Abstract
The goal of this FDP (Final Degree Project) is to implement different string-searching
algorithms in given numerical strings. The algorithms are applied as the diameter of the
trunks of the trees changes along the day. Two types of algorithms are implemented: ge-
neral algorithms of string that search different characteristics produced in the trunk as
the time goes by, for example the largest interval of time in which the trunk has expan-
ded without compressing, or compare the intervals in which this characteristics appear in
some trees and string-searching algorithms which look for a certain pattern in the strings.
Since the data changes a lot we search the tendency of growth or shrinking of the mea-
surements letting some mismatches. Due to this the implemented algorithms are in the
most part approximated, allowing that some values are not exactly adjusted in the strings.
Both algorithm are implemented in a graphical application where the user can choose
the desirable algorithm and its characteristics.
At the end it is done a study of the execution time of the string-searching algorithms
and the most relevant results are commented. Firstly it is studied the execution time of
the algorithms applied to real trees and then virtual trees are created to study certain
characteristics of the Boyer Moore algorithm. The improvement of the exact Boyer Moo-
re algorithm times is compared to the approximated Boyer Moore algorithm with cyclic
patterns and equality when there is a larger size of the pattern. Additionally it is checked
that the Shift Add algorithm maintains constant execution times with respect to the size
of the pattern and the number of mismatches admitted.
xi
Índice
Agradecimientos vii
Resumen ix
Abstract xi
1. Introducción 1
2. Algoritmos 3
2.1. Algoritmos generales de cadenas . . . . . . . . . . . . . . . . . . . . . . . . 3
2.1.1. Búsqueda de intervalos de tamaño fijo que cumplen una propiedad . 3
2.1.2. Búsqueda del segmento máximo que cumple una propiedad . . . . . 4
2.2. Algoritmos de búsqueda de subcadenas . . . . . . . . . . . . . . . . . . . . 6
2.2.1. Distancia de Hamming . . . . . . . . . . . . . . . . . . . . . . . . . 6
2.2.2. Algoritmo de Fuerza Bruta . . . . . . . . . . . . . . . . . . . . . . . 6
2.2.3. Algoritmo Boyer Moore . . . . . . . . . . . . . . . . . . . . . . . . 7
2.2.4. Algoritmo Shift-Add . . . . . . . . . . . . . . . . . . . . . . . . . . 14
3. Aplicación 19
3.1. Explicación del problema . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
3.2. Descripción de la aplicación. Usuario . . . . . . . . . . . . . . . . . . . . . 20
3.2.1. Funciones de la aplicación . . . . . . . . . . . . . . . . . . . . . . . 21
3.2.2. Campos en la aplicación . . . . . . . . . . . . . . . . . . . . . . . . 25
3.2.3. Ejemplos de uso de la aplicación . . . . . . . . . . . . . . . . . . . . 26
3.3. Descripción de la aplicación. Implementación . . . . . . . . . . . . . . . . . 31
3.3.1. Entrada de Datos . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
3.3.2. Modelo-Vista-Controlador . . . . . . . . . . . . . . . . . . . . . . . 33
3.3.3. Modelo de la aplicación . . . . . . . . . . . . . . . . . . . . . . . . . 34
3.3.4. Implementación de las funciones de la aplicación . . . . . . . . . . . 35
3.3.5. Vista de la aplicación . . . . . . . . . . . . . . . . . . . . . . . . . . 40
3.3.6. Controlador de la aplicación . . . . . . . . . . . . . . . . . . . . . . 43
3.3.7. Unión de los tres componentes . . . . . . . . . . . . . . . . . . . . . 44
3.3.8. Gráficas de los árboles . . . . . . . . . . . . . . . . . . . . . . . . . 46
4. Análisis de tiempos 49
xiii
xiv Índice
4.1. Implementación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
4.1.1. Clase ControlTiempos . . . . . . . . . . . . . . . . . . . . . . . . . 49
4.2. Experimentos con árboles . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
4.2.1. Tiempos para patrones de tamaño 3 . . . . . . . . . . . . . . . . . 50
4.2.2. Tiempos para patrones de tamaño 5 . . . . . . . . . . . . . . . . . 53
4.2.3. Tiempos para patrones de tamaño 10 . . . . . . . . . . . . . . . . . 55
4.2.4. Resumen comparativo de los tres tipos de patrones sobre los datos
de un árbol . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
4.3. Experimentos adicionales . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
4.3.1. Patrones cíclicos . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
4.3.2. Patrones con alfabetos grandes . . . . . . . . . . . . . . . . . . . . 70
5. Conclusiones 79
Bibliografía 81
Índice de figuras
4.1. Gráfico con los tiempos de los algoritmos para el arbol e16-A con 0 discre-
pancias . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
4.2. Gráfico con los tiempos de los algoritmos para el arbol e16-A con 0 discre-
pancias . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
4.3. Gráfico con los tiempos de los algoritmos para el vector Nulo 1 . . . . . . . 62
4.4. Gráfico con los tiempos de los algoritmos para el vector Nulo 1 sin FB . . . 63
4.5. Gráfico con los tiempos de los algoritmos para el vector Alternado 1 . . . . 64
4.6. Gráfico con los tiempos de los algoritmos para el vector Alternado 1 sin FB 65
4.7. Gráfico con los tiempos de los algoritmos para el vector Alternado 1 . . . . 68
4.8. Gráfico con los tiempos de los algoritmos para el vector Alternado 1 sin FB 69
xv
xvi Índice de figuras
4.9. Gráfico con los tiempos de los algoritmos para el vector Nulo 1 . . . . . . . 70
4.10. Gráfico con los tiempos de los algoritmos para el vector Nulo 1 sin FB . . . 71
4.11. Gráfico con los tiempos de los algoritmos para patrones con muchas ocu-
rrencias en distintos alfabetos . . . . . . . . . . . . . . . . . . . . . . . . . 75
4.12. Gráfico con los tiempos de los algoritmos para el patrón 50 51 52... . . . 77
Índice de Tablas
xvii
Capítulo 1
Introducción
Encontrar los intervalos en los que crece o decrece un árbol durante el día y com-
pararlo con el crecimiento y decrecimiento de otros árboles.
Buscar los momentos del día en los que un árbol discrepa en sus medidas frente a
otro árbol.
Estudiar el crecimiento del tronco de un árbol durante varios días y compararlo con
otros árboles.
Para los primeros tres puntos usaremos distintos algoritmos generales de cadenas se-
gún la característica del árbol que querramos estudiar. Para el último punto estudiaremos
distintos tipos de algoritmos para la búsqueda de subcadenas en un texto y aplicaremos
alguno de los algoritmos estudiados a nuestro problema. Todos los algoritmos se aplicarán
al conjunto de medidas obtenidas en los troncos de estos árboles durante un mes.
Además de todo esto, haremos una aplicación específica para permitir a los investi-
gadores aplicar los algoritmos que hemos desarrollado y estudiado a los problemas que
tienen de una manera más sencilla.
1
2 Capítulo 1. Introducción
Los capítulos de esta memoria reflejan este plan de trabajo. El segundo capítulo cuenta
las características más importantes de los algoritmos que se han implementado. El tercero
describe los aspectos más importantes del código de la aplicación y ofrece una guía de uso
para manejarla. El cuarto cuenta los distintos experimentos que se han realizado sobre el
estudio y describe los resultados obtenidos. Se ha añadido un capítulo final para contar
las conclusiones que se han sacado del trabajo realizado.
Capítulo 2
Algoritmos
Dado el tamaño de los intervalos que se buscan, primero se calcula el número de datos
que cumplen la propiedad en una ventana inicial de ese tamaño y se guarda en una va-
riable contador. Si los primeros valores forman un intervalo válido, se añade el resultado
al vector de índices.
3
4 Capítulo 2. Algoritmos
aunque no todos los datos cumplan la propiedad, siempre que el número de datos erróneos
sea menor que una cantidad fijada por el usuario y que no sean consecutivos.
Para hacer esto, se definen tres variables: erroresCometidos que lleva la suma de errores
que se comenten en el intervalo, erroresSeguidos que lleva la suma de los errores consecu-
tivos que hay en el intervalo y error que es un booleano que indica si el elemento anterior
fue un error.
Lo primero que se hace es comprobar cuántos errores hay en la ventana inicial. Para
ello, se comprueba la negación de la propiedad y se suma un error cada vez que la propie-
dad no se cumpla. Se utiliza la variable booleana error para calcular el número de errores
seguidos que hay en el intervalo. Si se han producido menos errores que los permitidos y
el número de errores seguidos es 0, el intervalo es válido y se añade al vector de índices.
Al mostrar los datos, los intervalos consecutivos se agrupan dando lugar a un único
intervalo más largo para facilitar la lectura. Si se ha indicado un número de errores mayor
que 0, la agrupación de los intervalos se realiza pero el intervalo mostrado puede no ser
correcto debido a que contenga más errores. Los intervalos con el tamaño indicado por el
usuario dentro de ese intervalo mayor serán los que cumplan la propiedad.
La complejidad del algoritmo es lineal respecto del número de datos del vector.
Para implementar este algoritmo se definen dos variables: cont que lleva el número de
elementos que cumplen la propiedad en el segmento que se está explorando y contMax que
lleva el número de elementos que cumplen la propiedad en el intervalo máximo encontrado
hasta el momento.
Si el dato es erróneo pero el segmento aún puede ser válido, es decir, si el número
de errores cometidos es menor que el permitido y la variable erroresSeguidos está a
falso: se pone la variable erroresSeguidos a cierto porque el siguiente dato si puede
hacer saltar ese error, se añade 1 al contador de errores cometidos y se añade el
índice del error al vector de errores.
Tras este análisis de casos tenemos que mirar si el segmento que tenemos es mayor o
igual que el que ya teníamos. Para ello miramos si el contador de los elementos correctos
más el número de errores cometidos es mayor o igual que la variable contMax. Si es igual
se añade el intervalo al vector de índices. Si es mayor se resetea el vector de índices y se
añade el intervalo como el de mayor tamaño encontrado hasta el momento.
6 Capítulo 2. Algoritmos
La complejidad del algoritmo es lineal respecto del número de datos del vector.
Se define la distancia de Hamming (Langmead [5]) para dos cadenas con la misma
longitud como el mínimo número de sustituciones necesarias para convertir una cadena
en la otra. Por ejemplo, si tenemos las cadenas 1 2 3 4 y 1 2 3 5, la distancia de Ham-
ming entre ellas es 1 porque si sustituímos el 4 de la cadena 1 2 3 4 por un 5 obtenemos
la cadena 1 2 3 5. Esta distancia es diferente de la distancia de edición (edit distance)
en la que además de sustituciones se permiten inserciones y borrados.
2.2.2.2. Algoritmo
2.2.2.3. Complejidad
En el peor caso, el algoritmo de Fuerza Bruta tiene que comparar m caracteres para las
n−m+1 posibles subcadenas. Esto nos da un orden de complejidad de O(m(n−m+1)) =
O(nm). Sin embargo, en la práctica el algoritmo optimizado es bastante más rápido y tiene
una complejidad media de O(nk) siendo k el número de discrepancias permitidas.
2.2.3.1. Explicación
El algoritmo exacto intenta encajar el patrón de derecha a izquierda en el trozo del
texto considerado y usa dos heurísticas, en el caso de que se produzca una discrepancia
entre el texto y el patrón, para el desplazamiento del patrón:
Regla de sufijo bueno: se desplaza el patrón a la derecha para hacer coincidir el sufijo
correcto del texto considerado con la siguiente ocurrencia de ese sufijo en el patrón.
Además, el carácter incorrecto anterior al sufijo debe ser diferente al de la siguiente
ocurrencia de ese sufijo en el patrón.
Para el mismo patrón y la regla de sufijo bueno, si no tenemos ningún sufijo correcto
válido (0 posiciones correctas), se desplaza el patrón una posición. Si tenemos un sufi-
jo correcto de una posición (2) se desplaza el patrón ocho posiciones (todo el patrón)
porque no existe ningún 2 en el patrón que no vaya precedido de un 1. Si tenemos un
sufijo correcto de dos posiciones (1 2) se desplaza el patrón 3 posiciones porque al sufijo
1 2 primero le precede un 3 y luego un 4, así que esa ocurrencia es válida. Si tenemos
un sufijo correcto de tres posiciones (3 1 2), se desplaza el patrón 6 posiciones porque
no existe ningún otro sufijo válido de tamaño tres en el patrón. Debido a este último
caso, el resto de sufijos correctos de más de 3 posiciones también hacen que se despla-
ce el patrón 6 posiciones a la derecha. El resumen de esta regla puede verse en la tabla 2.1.
En nuestro programa usaremos dos versiones del algoritmo: una versión del algoritmo
de Boyer Moore para resolver el problema de búsqueda de patrones exacto en el que se
2.2. Algoritmos de búsqueda de subcadenas 9
Tamaño Sufijo
Desplazamiento
sufijo correcto
0 - 1
1 2 8
2 1 2 3
3 3 1 2 6
4 2 3 1 2 6
5 12 3 1 2 6
6 412 3 1 2 6
7 2412 3 1 2 6
implementan las dos reglas y una versión basada en el algoritmo de Horspool (simplifica-
ción del algoritmo de Boyer Moore) para resolver el problema de búsqueda de patrones
con k-discrepancias en el que únicamense se implementa la regla de desplazamiento de
caracter malo.
Hay que generalizar la búsqueda de derecha a izquierda del patrón en el trozo de texto
considerado y calcular el desplazamiento adecuado del patrón para cada heurística.
Estudiando todos los valores posibles del texto y del patrón se puede construir un
vector que nos indique cuántas posiciones hay que desplazar en la regla del carácter malo
para cada número x. A este valor le llamamos R(x).
Figura 2.2: Regla del sufijo bueno, el caracter x de T discrepa del caracter y de P . Los
caracteres y y z son distintos, por lo que z igual coincide con x
0 0
Si S existe, desplazamos P a la derecha de tal forma que la subcadena S de P coin-
0
cida con la subcadena S de T . Si S no existe, desplazamos a la derecha P hasta pasar la
subcadena S del texto más la menor cantidad tal que un prefijo del patrón P desplazado
encaja con un sufijo de la subcadena S de T . Si no encontramos esta cantidad, desplaza-
mos P m posiciones a la derecha, esto es, pasamos la subcadena S del texto.
Para la regla del sufijo bueno del algoritmo de Boyer Moore se definen varios conceptos:
Ni (P ): longitud del sufijo más largo de la subcadena P [1..i] que es sufijo del patrón
P.
L(i): la mayor posición estrictamente menor que la longitud del patrón P tal que
P [i..n] coincide con un sufijo de P [1..L(i)]. Puede ser 0.
0
L (i): la mayor posición estrictamente menor que la longitud del patrón P tal que
P [i..n] coincide con un sufijo de P [1..L(i)] y el carácter anterior de ese sufijo no es
igual a P (i − 1). Puede ser 0.
0
l (i): longitud del sufijo más largo de P [i..n] que es prefijo de P .
Para el patrón 2 1 2 4 1 2 4 1 2 vemos en la tabla 2.2 cada uno de los valores para
algunos de los conceptos definidos.
Pos 1 2 3 4 5 6 7 8 9
Patrón 2 1 2 4 1 2 4 1 2
Zpos 8 0 1 0 0 1 0 0 1
Npos 1 0 2 0 0 5 0 0 8
L(pos) 0 0 0 0 6 6 6 6 6
0
L (pos) 0 0 0 0 6 0 0 3 0
0
l (pos) 8 1 1 1 1 1 1 1 1
0 0
Tabla 2.2: Cálculo de Zpos , Npos , L(pos), L (pos) y l (pos) para el patrón 2 1 2 4 1 2 4
1 2
0
Figura 2.3: Pseudo-código del cálculo de L (i) [2]
El algoritmo de Boyer Moore exacto tiene una complejidad del orden O(n) para el
peor caso (cuando el patrón no aparece en el texto) siendo n el número de caracteres del
texto.
2.2. Algoritmos de búsqueda de subcadenas 13
Hay que generalizar la búsqueda de derecha a izquierda del patrón en el trozo de texto
y calcular el desplazamiento adecuado para cada carácter encontrado.
Para calcular el desplazamiento de carácter malo hay que entender qué queremos
calcular. Supongamos que comparamos el patrón con una subcadena que discrepa por k+1
vez en la posición i del texto y en la posición j del patrón. Para calcular el desplazamiento
tenemos que mirar las últimas k+1 posiciones de la subcadena coincidentes con el patrón y
ver si desplazando el patrón hacía la derecha encontramos una coincidencia entre alguno
de esos caracteres y alguno de los del patrón. Si esta coincidencia ocurre desplazamos
la subcadena tantas posiciones como hemos desplazado el patrón. Si no hay ninguna
coincidencia desplazamos la subcadena m − k posiciones (valor para saltarnos la última
posición estudiada).
Lo importante de este código es el tercer bucle, que calcula los valores de la tabla
dk para los caracteres que pertenecen al patrón P . En definitiva, para cada carácter del
patrón pi y para cada posición j > i, añade que dk [j, pi ] = j − i.
c\i 1 2 3 4 5
1 5 1 2 1 2
-1 5 5 1 2 1
Figura 2.5: Pseudo-código del preprocesado del algoritmo Boyer Moore aproximado [8]
Algoritmo La idea del algoritmo es, para cada subcadena del texto de tamaño m, ir
mirando de derecha a izquierda si la subcadena y el patrón coinciden. Si no lo hacen se
suma 1 al número de discrepancias encontradas y, si se llega al final de la subcadena con
un número menor de k errores, se devuelve una ocurrencia en esa posición. Tras cada
intento se desplaza la subcadena con el valor que permite hacer coincidir el carácter más
a la derecha posible del patrón con un carácter de la subcadena estudiada. Si no existe
ningún carácter, se desplaza m − k (lo máximo que se puede desplazar si la subcadena y
el patrón no tienen ningún carácter en común).
La complejidad del algoritmo es del orden O(mn) en el peor caso. En media, el orden
de complejidad de este algoritmo es O(nk × ( kc + m−k
1
)).
2.2.4.1. Explicación
El algoritmo Shift-Add resuelve el problema de búsqueda de subcadenas con k-discrepancias
usando vectores de bits. La idea principal es representar m estados de la búsqueda (tan-
tos como la longitud del patrón) en un vector y recorrer el texto de izquierda a derecha
intentando llegar a estados en donde el patrón encaje con el texto.
Cada posición del vector de estado lleva la cuenta de cuántas discrepancias se han
detectado hasta esa posición en un alineamiento del patrón P con un trozo del texto T .
Así, si Si [j] es el vector de estados para la posición de texto i y la posicion del patrón j,
tenemos que Si [j] = dH (T [i − j + 1..i], P [1..j]), es decir, la distancia de Hamming entre
el prefijo de longitud j del patrón y el sufijo del trozo de texto que termina en la posición i.
16 Capítulo 2. Algoritmos
De esta forma, Si [m] ≤ k indica una ocurrencia del patrón P en el trozo de tex-
to T [i − j + 1..i] donde como mucho hay k discrepancias. Podemos comprobar que en el
ejemplo se ha encontrado el patrón debido a que el último estado es 000, lo que indica que
no hay discrepancias entre las posiciones [2 . . 5] del texto y todas posiciones del patrón.
En otras palabras, en las posiciones [2 . . 5] del texto se ha encontrado una ocurrencia del
patrón.
Para pasar de un estado al siguiente se usan operaciones lógicas de bits como sumas y
desplazamientos. En cada paso se actualizan simultáneamente las m posiciones del vector.
0 si j = 1 0 si P [j] = T [i]
Si [j] = + (2.1)
Si−1 [j − 1] en otro caso 1 si P [j] 6= T [i]
Para realizar la primera actualización con la primera posición del texto en todos los
estados tenemos que desechar el último estado y añadir uno nuevo con valor 000. Para el
primer estado, además, tenemos que comprobar la igualdad entre la primera posición del
texto y la primera posición del patrón. En nuestro ejemplo ambos valores son 1, así que
no se le suma nada y sigue valiendo 000. Para los estados intermedios sumamos al estado
2.2. Algoritmos de búsqueda de subcadenas 17
anterior la comprobación de si son iguales la primera posición del texto con la posición
j-ésima del patrón. De esta forma, para el segundo estado, como la primera posición del
texto es un 1 y la segunda del patrón es un -1 no coinciden ambos números y el resultado
del segundo estado será la suma del primero más uno, en otras palabras, 010. Para el
tercer y el cuarto estado si coinciden las posiciones entre el texto y el patrón pues la
tercera y la cuarta posición del patrón es un 1 al igual que la primera posición del texto,
así que el resultado de estos estados es el estado anterior sin sumarle nada, es decir, 001.
El vector de estados tras esta primera actualización es 000 010 001 001.
2.2.4.2. Implementación
Para implementar esto no necesitamos más que log2 (k + 1) bits para representar el
estado. Esto es debido a que cada estado debe tener al menos b = dlog2 (k + 1)e + 1 bits
ya que se necesitan k + 1 discrepancias para determinar si un alineamiento no es correcto.
Se precisa de un bit adicional para evitar los desbordamientos.
Para calcular más eficientemente la actualización del vector de estados, se define una
tabla t para cada número c posible y cada posición j que nos devuelve el valor del segundo
término de Si [j] (un conjunto de L bits). De esta forma, para un número n tenemos:
0b si P [j] = c
t[c, j] = (2.2)
0b−1 1 si P [j] 6= c
c\j 1 2 3 4
1 00 01 00 00
-1 01 00 01 01
La primera línea define la constante b y la segunda las máscaras necesarias para el al-
goritmo. La tercera inicia la tabla t para el alfabeto α, que en nuestro caso es un conjunto
de números, con el valor por defecto. La cuarta sobrescribe ese valor para los números que
constituyen el patrón. La última línea inicia el vector de estados (variable s) y el vector
que llevará los posibles desbordamientos (variable o).
Para nuestro ejemplo y suponiendo que admitimos una discrepancia ya habíamos cal-
culado que b = 2. Por lo tanto, la línea 3 del algoritmo nos daría t[1,:]=01 01 01 01 y
t[-1,:]=01 01 01 01. La cuarta línea corregiría la tabla según los valores que toma el
patrón y nos quedaría finalmente t[1,:]=00 01 00 00 y t[-1,:]=01 00 01 01 que, si
nos fijamos, coincide con los valores calculados en la tabla 2.4.
Para cada posición nueva del texto se actualiza el vector de estados s según la defi-
nición dada: se desplaza b posiciones el vector de estados desechando el último estado y
añadiendo uno nuevo nulo y se suma a este nuevo vector de estados el vector de la tabla t
correspondiente al número de la posición i-ésima del texto. Luego se divide esta suma en
el vector de desbordamientos o y el vector de estados s sin la parte del desbordamiento.
A continuación se comprueba si Si [m] + Oi [m] ≤ k para reportar un posible ocurrencia
del patrón P en el texto con como mucho k discrepancias.
El coste del algoritmo es del orden O(n) asumiendo que las operaciones de vectores de
bits tienen tiempo constante. Para un patrón de longitud m y un número de discrepancias
k tales que m log(k) > w siendo w la longitud de la palabra en la máquina usada, el
algoritmo tiene un coste O(mn).
Capítulo 3
Aplicación
El fichero con el que se ha trabajado en este estudio incluye las medidas de 20 árboles
durante 26 días. Esto supone 78832 medidas para cada árbol. Para minimizar el uso de
la memoria, sólo se cargan en el programa los árboles necesarios para cada función. De
esta forma, antes de aplicar cualquier función, se cargan los datos de los árboles que se
necesitan.
Para cada árbol se tienen las fechas de inicio y fin y un dato del grosor del tronco cada
30 segundos. Lo primero que se hace es realizar una limpieza de los datos corrigiendo
ciertos errores que se producen por fallos en el sensor. En particular, se quitan valores
inválidos, se añaden fechas que faltan en las medidas y se eliminan grandes diferencias
entre los valores. Una vez obtenida esta nueva colección de datos, se obtendrán otras dos
colecciones asociadas de la siguiente manera:
19
20 Capítulo 3. Aplicación
es más fácil ver las relaciones que existen entre ellos. Esta colección se utilizará a la
hora de representar las gráficas.
Colección de diferencias: se obtiene restando a cada elemento de la colección de
datos normalizada su elemento anterior. Se obtiene así una colección de datos con
las diferencias que existen de unos datos a otros. Esta colección es la que se utiliza
en la mayoría de los algoritmos, en los que se buscan relaciones en las diferencias
que existen entre unos datos y otros.
Con esta función se pueden encontrar los intervalos de tiempo en donde la diferencia
de una medida a la siguiente en el árbol no difiere en más de una cantidad. Para ello el
usuario tiene que seleccionar el árbol sobre el que se quieren calcular los intervalos, indicar
la cantidad máxima de una medida a otra y escribir las fechas de inicio y fin. Además
tiene que indicar el tamaño del intervalo o si quiere que se encuentre el intervalo más
largo en el conjunto de datos. También puede indicar el número de errores que permite
en el intervalo, es decir, si admite que haya algún dato que no cumpla la función de
homogeneidad que se ha definido pero aún así siga siendo un intervalo válido.
22 Capítulo 3. Aplicación
Con esta función se pueden encontrar los intervalos de tiempo en donde la diferencia
de una medida a la siguiente no difiere en más de una cantidad en varios árboles a la vez.
El usuario tiene que indicar los árboles sobre los que aplicar la función, las fechas de inicio
y fin, el valor sobre el que aplicar la condición de homogeneidad y la forma de aplicarla
(cuál de las dos versiones tener en cuenta).
Además, como en la función anterior, el usuario tiene que indicar si quiere buscar
intervalos de un tamaño concreto o el intervalo más grande. También tiene que indicar el
número de errores que permite que se produzcan en el intervalo. Como particularidad, no
se puede buscar el intervalo más grande con errores para más de dos árboles debido a que
la forma de calcular el resultado sería mediante ensayo y error y no usando los algoritmos
ya explicados. El resto de posibilidades sí están implementadas.
El usuario tiene que elegir los dos árboles en donde buscar los intervalos, las fechas de
inicio y fin y el número de errores que se permiten en el intervalo. En este caso, un error
se producirá cuando ya no se produzca una discrepancia, es decir, cuando el signo de las
medidas de los dos árboles sea el mismo.
Del mismo modo al de las dos funciones anteriores, el usuario indicará si quiere buscar
intervalos de un tamaño concreto o el intervalo más grande.
El usuario tiene que indicar el árbol sobre el que quiere medir el crecimiento y las
fechas de inicio y fin de esa búsqueda. Para este algoritmo sólo se tendrán en cuenta los
días completos que hay entre ambas fechas. Como parámetro opcional, el usuario puede
indicar si se devuelve el crecimiento de los días superiores a una cierta cantidad dada o
el día en el que más creció el tronco.
En esta función se compara el crecimiento de los troncos de dos árboles. Para ello, hay
que definir cómo de parecidos son dos árboles respecto al crecimiento de sus troncos a lo
largo de los días.
Dados los dos vectores con el crecimiento del tronco de los árboles ordenados de ma-
yor a menor según lo que han crecido, la relación de semejanza que se considera es que
la ordenación por días sea parecida entre ambos. Para ejemplificar esto, consideremos la
siguiente situación:
Supongamos que nuestro intervalo de tiempo es de tres días y para el árbol 1 el orden
del crecimiento es [1,3,2], es decir, el día en el que más ha crecido el árbol ha sido el
primero, luego el tercero y por último el segundo. Vemos a simple vista que si el orden del
vector del árbol 2 es [1,3,2] también, esto tiene que ser más semejante que si es [2,3,1].
Lo que no sabemos es si es más semejante que el vector del árbol 2 valga [2,3,1] o que
valga [1,2,3].
Para solucionar esto calcularemos a cuántos días gana cada día en cada uno de los dos
árboles y guardaremos el mínimo de estos dos valores. Luego sumaremos los mínimos de
todos los días y dividiremos por el valor que resultaría si todos los días coincidieran en
ambos árboles. De esta forma se obtendrá un valor de semejanza entre los dos árboles.
Si el árbol 2 tuviera un vector de [2,3,1] tendríamos que el segundo día gana a los
otros 2 así que su valor sería de 3, el tercer día gana al primero y su valor sería 2 y el
primer día es en el que menos crece el árbol así que su valor es 1. Su vector correspondien-
te de ganancia sería [1,3,2]. Del mismo modo, si el árbol 2 tuviera un vector de [1,2,3]
obtendríamos que el primer día gana al resto y queda con un valor 3, el segundo día gana
al tercero y queda con un valor de 2 y el tercero no gana a ningún día así que toma un
valor de 1. El vector de ganancia queda [3,2,1].
24 Capítulo 3. Aplicación
Ahora comparamos el vector de ganancia del árbol 1 con el vector de ganancia del
primer caso del árbol 2. Tenemos que encontrar el mínimo de cada día entre los dos
vectores y sumar los vectores obtenidos. La operación sería sum(min([3, 1, 2], [1, 3, 2])) =
sum([1, 1, 2]) = 4. Procedemos a hacer lo propio con el vector de ganancia del árbol 1 y
el segundo caso del árbol 2. La operación en este caso es sum(min([3, 1, 2], [3, 2, 1])) =
sum([3, 1, 1]) = 5.
Si los dos vectores fueran iguales, tendríamos un valor total de 6 (la fórmula de la
progresión aritmética desde 0 hasta el número de días que tenemos). Como resultado final
tenemos un valor de 64 para el primer caso y de 56 para el segundo. Así que el vector del
segundo caso ([1,2,3]) es más semejante al vector del árbol 1 ([1,3,2]) que el vector
del primer caso ([2,3,1]).
El usuario tiene que dar el árbol sobre el que se quieren medir las contracciones, las
fechas de inicio y fin y, opcionalmente, indicar si quiere sólo el día cuando se produzca la
mayor contracción o los días en donde la contracción supera una determinada cantidad.
Por ejemplo, si queremos encontrar intervalos de dos minutos (4 medidas) en los que
el árbol crece de forma constante una unidad en cada medida respecto a la anterior ten-
dríamos que buscar el patrón 1 1 1. Cada número del patrón indica la diferencia que se
produce con la medida anterior así que para intervalos crecientes tenemos que buscar pa-
trones con todos los datos positivos y para intervalos decrecientes patrones con los datos
negativos.
Debido a como se devuelven los resultados de esta función se puede usar la búsqueda
3.2. Descripción de la aplicación. Usuario 25
de patrones para encontrar intervalos más grandes en donde los árboles se comportan
de la misma forma a lo largo del tiempo. Por ejemplo, si queremos buscar los interva-
los en donde las medidas del árbol crecen en una unidad cada 30 segundos nos bastaría
con buscar el patrón 1 y observar si en los resultados se devuelven intervalos consecutivos.
Para usar esta función el usuario tiene que seleccionar el árbol sobre el que quiere bus-
car el intervalo y escribir las fechas de inicio y fin. En cuanto a los datos del patrón debe
escribir los números del patrón separados por espacios, el número de discrepancias que
admite en las subcadenas encontradas y los datos de división y tolerancia que permiten
transformar los datos reales de las medidas de un árbol a datos enteros.
El número que se indique en el campo de Division dividirá a todas las medidas del
árbol y el número que se indique en el campo Tolerance (entre 0 y 0,5) aproximará el valor
final al entero anterior o al entero superior obtenido del dato de la medida tras la división.
Para ello obtendrá los decimales del número y si estos decimales son menores que el valor
dado en la tolerancia el dato irá al entero anterior. Si la unidad menos los decimales son
menores que la tolerancia el dato irá al entero superior. Si no ocurre ninguna de estas dos
cosas el dato se considerará como erróneo. Cabe destacar que si la tolerancia vale 0,5 no
pueden existir datos erróneos.
Tree number: número de árboles que se van a tratar en el estudio. Debe ser un
entero positivo.
Tree list: lista desplegable con los identificadores de los árboles que se consideran
en la aplicación. Se deben marcar tantos árboles como se vayan a estudiar. Debe
coincidir el número de árboles marcados con el campo Tree Number. Si no aparece
ese campo, sólo se puede marcar un árbol y en caso de pulsar más de uno, éste se
reemplaza por el anterior.
Initial time: fecha inicial de los datos de los árboles que se consideran en el estudio.
Su formato debe ser dd/mm/aa hh:mm:ss con ss = 00 o 30.
Final time: fecha final de los datos de los árboles que se consideran en el estudio.
Su formato debe ser dd/mm/aa hh:mm:ss con ss = 00 o 30.
Para las funciones relacionadas con el cálculo de distintos intervalos en el árbol (con-
dición de homogeneidad y discrepancias), se tienen los siguientes campos adicionales:
Interval length: longitud del intervalo que se considera. Debe ser un entero mayor
que 0 la palabra INF. En el caso de poner INF se devuelve el intervalo más grande
que cumple la propiedad pedida. Si hay varios intervalos de longitud máxima, se
devuelven todos ellos.
26 Capítulo 3. Aplicación
Pattern: patrón que se quiere buscar. Un patrón válido está compuesto por números
enteros separados por espacios.
Division: valor por el que se quieren dividir los datos del árbol a la hora de calcular
sus patrones. Debe ser un real distinto de 0.
Tolerance: tolerancia que hay en los datos del árbol para ajustarlos a un número
entero o a otro. Debe ser un real mayor o igual que 0 y menor o igual que 0,5.
Para las funciones que calculan el crecimiento del tronco y de la contracción cada día,
aparecen los siguientes campos adicionales:
Min trunk size: tamaño mínimo del tronco que se permite en el árbol sobre el que
se busca. Debe ser un real mayor que o igual a 0 o la palabra INF. En el caso de
poner INF se devuelve el día con el tamaño del tronco más grande.
Para mostrar los datos de un árbol, el usuario tiene que elegir el árbol que desea. Para
ello, debe pulsar sobre el campo Tree obteniendo el siguiente desplegable:
A continuación, tiene que rellenar los datos de las fechas de inicio y fin y pulsar sobre
el botón Compute. Tras hacer esto, la ventana de la aplicación tendrá la siguiente forma:
28 Capítulo 3. Aplicación
El usuario tiene que rellenar el campo Tree number con el número de árboles que desea
comparar y elegir tantos árboles en el desplegable como el número que haya escogido. La
ventana, por tanto, debe quedar de la siguiente forma:
3.2. Descripción de la aplicación. Usuario 29
Tras esto, debe rellenar el resto de datos antes de darle al botón Compute. El campo
Interval size se corresponde con el tamaño del intervalo que busca, el campo Maximum
difference con la diferencia máxima que se permite entre un dato y el siguiente, el campo
Jumps con la definción de homogeneidad que quiere usar y el campo Mismatches con el
número de errores que permite. Tras rellenar todos los campos y darle al botón Compute,
el resultado de esta opción queda de la siguiente forma:
El usuario tiene que elegir el árbol sobre el que realiza la búsqueda y rellenar los
campos de Initial time y Final time. Además, deberá rellenar el campo Min trunk size,
que se corresponde con el mínimo crecimiento del tronco que se permite en un día. Una
opción válida en este campo es rellenarla con el valor INF para devolver el día en el que
el árbol creció más. Si se elige este caso, el resultado de esta opción queda de la forma:
3.2.3.4. Patrones
El usuario tiene que elegir el árbol sobre el que realizar la búsqueda y rellenar los
campos Pattern y Mismatches con el patrón que quiere y las discrepancias que admite.
Ademas debe rellenar los campos Initial time y Final time de las fechas de inicio y fin de
la búsqueda y los campos Division y Tolerance que se necesitan para adaptar el vector de
medidas reales a uno de enteros. Tras rellenar todos los campos y dar al botón Compute
el resultado queda:
Para procesar estos datos se define la clase FicheroMaestro. Esta clase se encarga de,
dada una entrada de datos como la anterior guardada en un fichero de texto separado
por tabuladores, crear un fichero diferenciado para cada árbol que se puede utilizar en la
aplicación.
El constructor de esta clase sólo necesita el nombre del fichero que contiene los datos.
Su método principal se llama arreglarFichero y realiza el proceso que se ha descrito. Para
ello, primero llama a un método auxiliar que intercambia todas las comas de los datos de
los árboles por puntos (método cambiarComasPorPuntos). Este método es necesario para
transformar el formato de Excel que utiliza comas en los números reales por el formato de
Eclipse que utiliza puntos. No puede realizarse a mano debido al gran tamaño que tiene
el fichero.
Tras esto se lee la primera línea del fichero, que contiene los nombres de todos los
árboles, y se crea un fichero auxiliar para cada árbol que se rellena con los datos en bruto
de ese árbol. Para ello se recorre el fichero de datos de principio a fin y para cada línea se
escribe la fecha en todos los ficheros de árboles y el dato que le corresponde a cada árbol
en esa línea. Tras realizar esto, se llama al método auxiliar arreglarArbol para cada uno
de los ficheros que se han creado.
El método arreglarArbol lee los datos del fichero de un árbol en bruto y crea otro
fichero con los datos del árbol corregidos, que son los que se usarán en la aplicación. Este
método corrige tres posibles errores:
1. Añade las fechas que falten en los datos de los árboles debido a que el sensor no las
ha tomado.
2. Corrige saltos muy grandes entre un dato y el siguiente debido a un fallo del sensor.
3.3. Descripción de la aplicación. Implementación 33
Cuando se produce uno de estos errores en los datos de un árbol, se suma 1 a un conta-
dor de errores. Al finalizar, se crea un objeto de la clase Errores para cada árbol arreglado.
La clase ListaArboles es una clase auxiliar que contiene el nombre del árbol, el número
de errores que se han producido y el porcentaje de errores respecto del total de los datos
del árbol. Tiene una función getValido que devuelve si el árbol se puede utilizar para los
cálculos de las distintas funciones o no. Se considera que un árbol no es válido cuando su
porcentaje de errores es mayor que el 20 %. Aparte de este método, se define una función
compareTo que compara el nombre del árbol con el nombre de otro árbol de otro objeto de
la clase ListaArboles. Este método se usa para ordenar alfabéticamente distintos objetos
en una lista de la clase ListaArboles.
3.3.2. Modelo-Vista-Controlador
Para la implementación del programa se usa MVC (Modelo-Vista-Controlador). El
MVC es un patrón de arquitectura software que separa el manejo de los datos de su pre-
sentación al usuario y de la llamada lógica de negocio.
Vista: consiste en una representación (visual) del modelo, por lo tanto es el com-
ponente que gestiona la salida gráfica y/o textual de la aplicación. La vista puede
decidir destacar ciertos elementos del modelo e incluso ignorar y no presentar otros.
2. Aplicación del patrón MVC a una interfaz gráfica sobre la que interactúa el usuario
final.
Para cada una de estas etapas se ha construido un controlador y una ventana. La parte
del modelo es la misma en ambas partes.
34 Capítulo 3. Aplicación
La clase Programa contiene los métodos necesarios para ir realizando las distintas ac-
ciones que permiten calcular los distintos resultados sobre los árboles que se tienen. Su
constructor necesita el número de árboles que hay, las fechas de inicio y fin de los datos
de los árboles, el nombre del fichero sobre el que se leen todos los árboles de la aplicación
y el texto inicial que se muestra al inicio con todos los errores de los datos de los árboles.
existeArbol: dado un nombre de árbol, una fecha de inicio y una fecha de fin se
comprueba si los intervalos de las fechas son correctos y después se llama al método
cargarArbol. En caso de éxito se devuelve el árbol en cuestión y en otro caso se
devuelve un valor nulo.
Además de estos métodos, la clase Programa define la interfaz Observer con los mé-
todos que tienen que implementar las clases que funcionen como vistas en la aplicación.
Para indicar a estos observadores algún cambio en la clase se llaman a los métodos de la
interfaz en los distintos métodos que de la clase Programa.
Para añadir las distintas funciones que se van a tener en cuenta en nuestro programa se
define la clase abstracta Funcion. Esta clase tiene como atributos los árboles y las fechas,
que son comunes a todas las funciones que se van a definir como hijas de esta clase, así
como sus getters y setters. De esta forma se pueden definir algunos métodos generales que
sirven para cualquier tipo de función. Por ejemplo se define el valor máximo o mínimo en
los árboles considerados en un intervalo de fecha dados (esta función será usada para la
gráfica en donde se muestran los datos de los árboles).
Los dos métodos más importantes de la clase abstracta Funcion son el método abstrac-
to calculo y el método abstracto toString. Cada una de las clases hijas que se definan
tendrán que implementar estos dos métodos obligatoriamente. El método calculo se re-
fiere a la lógica que hay que realizar para cada función y el método toString al resultado
3.3. Descripción de la aplicación. Implementación 35
que se devuelve de cada función realizada. Además se definen algunos getters y setters
como getIndices, setIndices y setTam que, aunque no tienen sentido para algunas de
las funciones hijas, se necesitan definir en esta clase ya que es a la única a la que pueden
acceder las vistas de la aplicación.
Esta opción tiene un método calculo nulo, que sólo se mantiene para que sea una
clase análoga a la del resto de funciones. Su método toString devuelve los datos de los
distintos vectores separados por tabuladores en una especie de tabla.
Arbol: objeto de la clase Arbol con el árbol sobre el que se va a realizar la función.
Ventana: entero con la longitud del intervalo. Si su valor es Integer.MAX_VALUE
quiere decir que el usuario ha marcado la opción de buscar el intervalo más largo
que cumple la condición de homogeneidad.
ValorMax: real con la cantidad que ha dado el usuario para definir la condición de
homogeneidad.
Err: número de errores que se permiten en el intervalo. El número de errores no
puede superar la mitad de la longitud del intervalo.
Ini: fecha de inicio del vector de datos sobre el que se busca.
Fin: fecha de fin del vector de datos sobre el que se busca.
De esta forma, el método calculo elegirá usar una función u otra respecto de los
parámetros ventana y err. El método de devolución de resultados toString realiza un
bucle y devolverá todos los intervalos encontrados en el vector de datos del árbol seleccio-
nado. Como particularidad, si varios intervalos se solapan, se agruparán en un intervalo
de mayor tamaño.
Para poder devolver esta intersección, sólo permitida en la versión gráfica de la aplica-
ción, la clase MainWindow definirá una serie de métodos para calcular la intersección de
los índices de todos los árboles. Para poder realizar esta característica es necesario que la
clase Funcion permita devolver los resultados encontrados en la función y permita cam-
biarlos si hace falta. Por esto se definen los métodos getIndices, setIndices y setTam.
Los dos primeros devuelven y cambian el vector de índices de los intervalos resultado y
el último cambia el tamaño del intervalo y se usa cuando estamos buscando el mayor
intervalo entre muchos árboles.
Su método calculo se realiza igual que en las dos funciones anteriores: se implementa
una función auxiliar para cada uno de los cuatro problemas que se tiene que resolver y
con un análisis de casos previo a partir de los parámetros ventana, mayor y err se elige
a cuál de estas funciones llamar. En este caso hay que tener siempre en cuenta si estamos
con dos medidas discrepantes entre sí o no.
El método toString devuelve los valores del crecimiento de los troncos en los días
que superen el valor del parámetro cantidad. Si cantidad vale Float.MAX_VALUE, sólo
se devuelve el valor del día con mayor crecimiento.
El método calculo consiste en calcular los valores del crecimiento de los troncos para
los dos árboles de la misma forma que se hacía para un solo tronco en la función anterior.
El método toString calcula la relación de semejanza entre ambos troncos por medio
del método auxiliar parecidos, que funciona de la misma forma que la explicación del
ejemplo que se ha mostrado, y devuelve el resultado obtenido.
3.3. Descripción de la aplicación. Implementación 39
En esta clase tenemos todos los algoritmos para calcular búsquedas de subcadenas
dentro de un vector. El método calculo manda que funcionen los algoritmos de Boyer
Moore ya que son los que tienen mejores resultados para nuestra aplicación. El método
toString devuelve en formato de texto los intervalos donde se ha encontrado el patrón
buscado dentro del árbol.
Para poder implementar los algoritmos antes tenemos que pasar las medidas reales de
los árboles a medidas enteras. Para ello se implementa el método computoArbolPatron
que para cada medida real la convierte en una medida entera o en un error. Para realizar
esto divide la medida por el valor dado en la variable dist y calcula los decimales que
resultan tras quitarle la parte entera. Si estos decimales son menores que la variable tol
se añade la parte entera del factor de la medida entre dist. Si la unidad menos estos
decimales son menores que la variable tol se añade el siguiente número entero a la parte
entera de media entre dist (según sea positiva o negativa). En otro caso la medida se
convierte en un error que se representa con el valor Float.MAX_VALUE.
Otro método auxiliar clave de la clase FuncionPatron es maxiVect que calcula los
40 Capítulo 3. Aplicación
números máximo y mínimo del vector del árbol transformado por el vector anterior y del
patrón. De esta forma podemos delimitar el alfabeto que utilizaremos en los algoritmos
ya que hemos pasado del alfabeto infinito de números reales del vector de diferencias del
árbol al alfabeto finito de números enteros en el intervalo definido por los números máxi-
mo y mínimo que hemos calculado.
onMostrar: método que imprime por consola el menú con las distintas opciones que
puede realizar el usuario. Normalmente este método es llamado al final del resto de
métodos que implementa la clase vistaDeConsola.
Para los botones Exit, Compute y Show Graph se usa la clase JButton.
42 Capítulo 3. Aplicación
Para las distintas etiquetas donde escribe el usuario se usa la clase JTextField.
initOpciones: inicializa todos los componentes visuales de las opciones de las po-
sibles funciones. Implementa el listener de la lista desplegable de opciones y añade
unos paneles u otros según la opción elegida en esa lista. También implementa el
listener del botón Compute según los valores que se encuentren en los paneles de
opciones que se muestran (para ello tiene en cuenta el elemento escogido en la lista
desplegable de funciones).
Este listener consiste en mirar si los datos de la función escogida por el usuario
son correctos. Si lo son se crea una función del tipo correspondiente y se le pide a
ControladorVentana que la ejecute para poder mostrar los resultados al usuario. Si
los datos no son correctos se devuelve un error.
agnadirGrafica: añade a la gráfica creada los valores de un árbol entre las fechas
de inicio y fin indicadas por el usuario.
Este controlador tendrá como atributos la instancia de la clase Programa, que repre-
senta al modelo, y la instancia de la clase java.util.Scanner, que se usa para leer datos por
consola. Su método principal es run. Este método usa el método auxiliar pedirDatos para
pedir toda la información necesaria al usuario para realizar una de las posibles funciones
del programa.
El método run lee en un bucle la siguiente opción que quiere realizar el usuario y
según la opción escogida realiza una determinada acción. De manera general, tras elegir
una opción, se piden los datos necesarios al usuario para poder ejecutarla y luego, si estos
son correctos, se llama al método ejecutarFuncion de la clase Programa. Una acción
especial es la opción Salir. En este caso se llama a un método que pone el booleano que
controla el bucle a cierto y se sale de la aplicación.
El método auxiliar pedirDatos diferencia la información que hay que pedir al usuario
según una variable de tipo EnumFuncion que se inicializa en el método run. Para cada
valor del enumerado se piden una serie de datos al usuario necesarios para realizar la
acción que ha escogido en el menú.
44 Capítulo 3. Aplicación
comprobarArbol: dado un nombre de árbol, una fecha de inicio y una fecha de fin
este método devuelve un objeto de la clase Arbol con los datos del árbol que se
piden entre el intervalo de fechas seleccionado o un valor nulo que representa que
se ha producido un error. La implementación del método consiste simplemente en
llamar al método existeArbol de la clase Programa.
getIni: este método devuelve la fecha inicial de los datos de los árboles que hay en
la aplicación.
Para relacionar las vistas con el modelo, la clase Programa define la interfaz interna
Programa.Observer y extiende a la clase Observable con esa interfaz. La clase Observable
es una clase genérica y su función consiste en añadir o eliminar instancias de objetos a
una lista que tiene como atributo. La interfaz Programa.Observar define cuatro métodos:
onInic, onListar, onError y onResultado.
Las vistas tienen que definirse como observadoras de la clase Programa y tienen que
implementar los métodos de la interfaz Programa.Observer como mejor les convenga para
responder a los cambios de estado que se van produciendo en la lógica de la aplicación.
Cuando se produce un cambio de estado, la clase Programa llama al método correspon-
diente de todos los objetos de su lista de observadores y, de esta forma, se van produciendo
los distintos cambios en las vistas.
1. Tipo de interfaz: si quieres que la interfaz sea por consola (console) o por ventana
(window). El argumentos es java tfg.main.Main -u console/window.
2. Fichero: el nombre del fichero con los datos de los árboles. El argumento es
java tfg.main.Main -f nombreFichero.txt.
Si se inicia con una interfaz como eclipse sólo hay que cambiar la variable booleana
consola a true o false según se quiera y la variable fichero con el nombre del fichero
que contiene los datos de los árboles.
En ambos casos, el programa lo primero que hace es arreglar el fichero por medio de
la clase FicheroMaestro para pasar de tener un fichero a tener un fichero por árbol. A
continuación genera un String con los datos de los errores de los árboles del fichero e
inicia una instancia de la clase Programa.
46 Capítulo 3. Aplicación
En ambos casos se llama al método reset de la clase Programa para que las vistas se
inicien mostrando el número de errores que hay en cada árbol.
Se ha modificado esta librería permitiendo así poder pintar varias gráficas de árboles a
la vez con más de un color para poder diferenciar los árboles representados (no soportado
en la librería original). Además, se ha creado la clase MuestraArbol para representar como
función continua los datos de un árbol en un intervalo de tiempo dado. Para ello se ha
usado repetidamente la ecuación de la recta que pasa por dos puntos a partir del vector
de datos normalizados de un árbol. De esta forma, pasamos de un vector de datos finito
a una función de datos continuos.
En la figura 3.13 podemos ver un ejemplo de una de las gráficas de nuestra aplicación.
Observamos que se han pintado 3 árboles distintos. Debajo de la imagen tenemos una
leyenda con el color que le corresponde a cada árbol. Podemos elegir que sección de la
gráfica queremos observar cambiando las coordenadas de los ejes y dando al botón Update
o podemos guardar la imagen con el botón Save. Las líneas negras que van desde el eje
hasta las gráficas de los árboles indican que en esos puntos se ha encontrado un intervalo
resultado de la función que ha mandado calcular el usuario.
3.3. Descripción de la aplicación. Implementación 47
Análisis de tiempos
4.1. Implementación
Se han implementado una serie de facilidades en la aplicación que nos permiten medir
los tiempos de ejecución de los algoritmos. Se define el flag tiempos en la clase Main para
indicar que se quiere realizar un análisis de los tiempos de los algoritmos de subcadenas.
Si este flag se desactiva, la aplicación funciona igual que antes: si el flag consola está
activado se inicia la vista de consola y si no lo está se inicia la vista de ventana. El co-
mando para ejecutar el análisis de tiempos en consola es java tfg.main.Main -u times.
Para realizar el análisis de los tiempos hay que indicar una fecha de inicio, una fecha
de fin, el árbol sobre el que se quiere realizar el análisis, el patrón que se quiere buscar,
la división y la tolerancia para aproximar los datos del árbol a los del patrón y el número
de discrepancias que se admiten. Además, hay que indicar cuántas repeticiones queremos
que se hagan para cada algoritmo. Tras iniciar todos los datos, se crea una instancia de
la clase ControlTiempos a la que le pasamos todos los datos como parámetro y se llama a
su método principal (tiempos) que devuelve por pantalla el tiempo que ha tardado cada
uno de los algoritmos que se consideran.
Para cada algoritmo implementado se realiza un bucle en el que se ejecuta tantas veces
el algoritmo con los datos proporcionados como repeticiones haya pedido el usuario. Tras
realizar esto se divide el tiempo total que se ha tardado en realizar el bucle por el número de
49
50 Capítulo 4. Análisis de tiempos
Para el algoritmo de fuerza bruta se realiza un bucle distinto para la versión optimi-
zada y la versión sin optimizar. La versión sin optimizar nos da una cota superior de los
tiempos del resto de algoritmos porque es la peor forma de realizar la búsqueda.
Para los algoritmos Boyer Moore y Shift Add se realiza un bucle distinto para ca-
da versión implementada (versión con matriz y versión con tabla hash) y se devuelve el
tiempo que se tarda en cada caso teniendo en cuenta el tiempo con él preprocesado o
sin él. El algoritmo de Shift Add solo se ejecuta si el tamaño del patrón y el número de
discrepancias indicadas están dentro de los límites en donde se puede aplicar el algoritmo.
Para cada tamaño de patrón generamos una tabla con el patrón que estamos conside-
rando, el número de discrepancias que se piden, el número de ocurrencias encontradas en
el árbol y los tiempos para todas las versiones de los algoritmos que consideramos. Para
los algoritmos Shift-Add y Boyer Moore tenemos dos tiempos: el tiempo del preprocesado
del algoritmo y el tiempo de la fase de búsqueda.
Lo primero que observamos es que cuantas más ocurrencias del patrón existen en los
datos del árbol, más tiempo tardan en ejecutarse los algoritmos. Por lo tanto, los tiempos
son mayores cuando se busca el patrón con alguna discrepancia.
Para el algoritmo de fuerza bruta tenemos que la versión optimizada es bastante mejor
que la versión sin optimizar y que esta mejora es inversamente proporcional al número
de ocurrencias encontradas. Esto es porque cuantas menos ocurrencias haya en el árbol,
menos veces es necesario comparar todo el patrón en el algoritmo optimizado. Observamos
que se mejora entre un 7 − 9 % cuando hay más de 40000 ocurrencias. Con más de 2000
4.2. Experimentos con árboles 51
ShAdd ShAdd
Ocurrencias FB
Patrón Disc FB (hash) (matriz)
encontradas opt
Prep Alg Prep Alg
0|0|0 0 19800 10326 8711 22 8383 14 3492
0|0|0 1 41529 14206 13167 22 9073 14 6064
1|1|1 0 890 6497 3928 18 5717 12 1864
1|1|1 1 7726 7799 6798 22 6958 14 3242
1|-1|1 0 1307 6961 3899 24 5820 469 1882
1|-1|1 1 8732 8287 7422 24 7277 13 2441
BM BM BM
Ocurrencias
Patrón Disc (hash) (matriz) (exacto)
encontradas
Prep Alg Prep Alg Prep Alg
0|0|0 0 19800 15 9298 12 2689 20 2743
0|0|0 1 41529 20 20890 12 5177 - -
1|1|1 0 890 15 3595 12 1145 17 1129
1|1|1 1 7726 20 10979 13 3267 - -
1|-1|1 0 1307 16 3984 45 1239 17 1187
1|-1|1 1 8732 20 9816 13 3315 - -
Para el algoritmo Boyer Moore tenemos que las versiones exacta y con matriz son más
eficientes que la versión con la tabla hash. Los tiempos de preprocesado son muy pareci-
dos en las tres versiones siendo siempre superior el de la versión exacta al de la versión
aproximada con matriz. Esto es debido a que en ambas se deben calcular las tablas de la
regla de carácter malo pero en la exacta también se calculan los valores necesarios para
la regla del sufijo bueno. Tanto la versión exacta como la versión aproximada con matriz
son algo más del 200 % mejores que la versión aproximada utilizando la tabla hash. Esto
es debido a que los accesos a las tablas hash en Java son muy costosos.
Los tantos por ciento de mejora entre los algoritmos de Boyer Moore no dependen
del número de ocurrencias puesto que encontramos que la versión con matriz es un 214 %
mejor que la de la tabla hash con 890 ocurrencias, un 85 % mejor con 20782 ocurrencias
pero un 303 % mejor con 41529. Entre las versiones con matriz y exacta, quitando el dato
anómalo del árbol e23-A (tabla 4.4) de que con 20782 ocurrencias la versión exacta es
un 95 % mejor, obtenemos en media que la versión exacta es un 2 % mejor que la versión
con matriz. El dato quitado corresponde con el patrón 0 0 0, cuyas ocurrencias se van
solapando ya que se corresponde con el patrón cíclico 0 que suele producirse asiduamente
52 Capítulo 4. Análisis de tiempos
ShAdd ShAdd
Ocurrencias FB
Patrón Disc FB (hash) (matriz)
encontradas opt
Prep Alg Prep Alg
0|0|0 0 20782 10279 8921 22 9306 7 2645
0|0|0 1 42798 14232 13065 21 9155 8 5303
1|1|1 0 857 6509 3657 22 6050 7 1892
1|1|1 1 7297 7978 6987 22 6875 7 2132
1|-1|1 0 1050 6656 3948 21 5766 7 1869
1|-1|1 1 7831 7863 6747 24 6873 7 3020
BM BM BM
Ocurrencias
Patrón Disc (hash) (matriz) (exacto)
encontradas
Prep Alg Prep Alg Prep Alg
0|0|0 0 20782 18 9360 7 5061 14 2592
0|0|0 1 42798 20 21010 8 5303 - -
1|1|1 0 857 13 2993 7 1183 11 1025
1|1|1 1 7297 19 10696 7 3189 - -
1|-1|1 0 1050 17 3809 6 1153 12 1288
1|-1|1 1 7831 19 9153 7 3020 - -
Para el algoritmo Shift-Add observamos de nuevo que la versión con tabla hash es mu-
cho peor que la versión con matriz. En este caso el preprocesado también es superior en la
versión con tabla hash. Los tantos por ciento de mejora entre los algoritmos de Shift-Add
no parecen depender de las ocurrencias, aunque cuando hay más de 40000 ocurrencias la
mejora desciende drásticamente: pasa del 189 % de mejora en media al 61 %. Teniendo
en cuenta que hay 78832 medidas en total, podemos decir que este descenso de la mejo-
ra es debido a que es necesario realizar comparaciones en casi todos los elementos del árbol.
Respecto al algoritmo de fuerza bruta optimizado no parece haber una relación entre
el número de ocurrencias y los tantos por ciento de mejora si se compara con el resto de
los algoritmos, así que pasamos a indicar la media para cada par de algoritmos. Respecto
a la versión con tabla hash del algoritmo Shift-Add, el algoritmo de fuerza bruta es mejor
en un 1, 4 %. Respecto a la versión con matriz de este mismo algoritmo comprobamos que
es bastante mejor que el algoritmo de fuerza bruta optimizado ya que en todos los casos
se ejecuta en menos tiempo. En media, la versión con matriz del algoritmo Shift-Add
es un 144 % mejor que el algoritmo de fuerza bruta. Para la versión con tabla hash del
algoritmo de Boyer Moore tenemos que el algoritmo de fuerza bruta se ejecuta en menos
tiempo para menos de 1100 ocurrencias pero tarda más para un número mayor de estas.
En media, el algoritmo de fuerza bruta optimizado es un 12, 5 % mejor. Por último, la
versión con matriz del algoritmo de Boyer Moore siempre es mejor que el algoritmo de
fuerza bruta optimizado, cuantitativamente es un 165 % mejor.
Observando los tiempos de las tablas de los dos árboles vemos que hay una similitud
entre ellos, así que para valores del tamaño de patrón mayores solo mostramos los tiempos
de los algoritmos en un árbol.
Observamos otra vez que los tiempos para los algoritmos son mayores cuantas más
ocurrencias se encuentren del patrón en el árbol. En este caso si se observa que la mejora
del algoritmo de fuerza bruta optimizado respecto al algoritmo sin optimizar va disminu-
yendo a medida que aumentan las ocurrencias encontradas ya que es del 189 % para 170
ocurrencias pero, si quitamos este valor, la media de mejora es del 37 %.
ShAdd ShAdd
Ocurrencias FB
Patrón Disc FB (hash) (matriz)
encontradas opt
Prep Alg Prep Alg
0|0|0|0|0 0 12311 13757 8168 22 7448 14 2741
0|0|0|0|0 1 25498 14287 12372 25 8936 15 3653
0|0|0|0|0 2 41583 18346 16913 22 11434 15 3983
1|-1|1|-1|1 0 170 10642 3679 25 5734 14 1752
1|-1|1|-1|1 1 958 10533 6116 23 6121 13 1847
1|-1|1|-1|1 2 5585 11860 9518 23 6710 14 2027
BM BM BM
Patrón Disc (hash) (matriz) (exacto)
Prep Alg Prep Alg Prep Alg
0|0|0|0|0 0 17 8239 13 2987 22 2062
0|0|0|0|0 1 19 17199 13 5030 - -
0|0|0|0|0 2 23 27491 14 9595 - -
1|-1|1|-1|1 0 15 2556 29 919 19 810
1|-1|1|-1|1 1 18 7175 12 2110 - -
1|-1|1|-1|1 2 23 13320 13 3900 - -
cias y del orden del 167 % para más de 10000. Observamos que la diferencia se mantiene
respecto a los patrones de tamaño 3.
Respecto al algoritmo de fuerza bruta optimizado, la versión con tabla hash del al-
goritmo Booyer Moore es peor en todos los casos menos en el primero donde es superior
por un 43 %, para el resto es mejor el de fuerza bruta en un 22 %. Respecto a su versión
con matriz es mejor el algoritmo de Booyer Moore siempre, siendo superior en un 300 %
en el primer caso. Para el resto de casos la mejoría es del 145 % y desciende considera-
blemente para más de 400000 ocurrencias siendo sólo mejor por un 76 % cuando para el
resto siempre ronda el 150 % de mejora. Como en un caso anterior, esto se explica debido
a que hay 78832 medidas, por lo que en la mayoría de los casos no se producen grandes
desplazamientos en el algoritmo de Boyer Moore.
4.2. Experimentos con árboles 55
En cuanto a fuerza bruta optimizado con Shift-Add con tabla hash tenemos que para
170 ocurrencias es mejor el algoritmo de fuerza bruta con un 36 % de mejora. Para el
resto de casos el algoritmo Shift-Add es mejor con una ganancia del 27 %. Para la versión
con matriz, el algoritmo Shift-Add siempre es mejor teniendo una media de ganancia del
243 %. Vemos en este caso que a medida que aumenta el número de discrepancias el algo-
ritmo Shift-Add funciona mejor.
En este caso se observa claramente que a mayor número de discrepancias, mayor nú-
mero de ocurrencias encontradas y mayores son los tiempos que tardan en ejecutar los
distintos algoritmos. Todos los tiempos para el algoritmo de fuerza bruta superan los
20000 milisegundos.
ShAdd ShAdd
Ocurrencias FB
Patrón Disc FB (hash) (matriz)
encontradas opt
Prep Alg Prep Alg
0|0|0|0|0|0|0|0|0|0 0 5715 21959 8236 24 6336 16 2045
0|0|0|0|0|0|0|0|0|0 1 12092 23581 13329 26 7598 18 2223
0|0|0|0|0|0|0|0|0|0 2 19016 24146 17961 - - - -
0|0|0|0|0|0|0|0|0|0 3 27201 24895 20306 - - - -
0|0|0|0|0|0|0|0|0|0 4 36520 28028 26045 - - - -
1|-1|1|-1|1|-1|1|-1|1|-1 0 0 20193 3634 26 5680 15 1815
1|-1|1|-1|1|-1|1|-1|1|-1 1 17 20374 6008 28 5565 16 1772
1|-1|1|-1|1|-1|1|-1|1|-1 2 61 20202 8305 - - - -
1|-1|1|-1|1|-1|1|-1|1|-1 3 230 20450 10745 - - - -
1|-1|1|-1|1|-1|1|-1|1|-1 4 909 20420 13478 - - - -
BM BM BM
Patrón Disc (hash) (matriz) (exacto)
Prep Alg Prep Alg Prep Alg
0|0|0|0|0|0|0|0|0|0 0 18 6523 13 2215 27 1799
0|0|0|0|0|0|0|0|0|0 1 22 15655 13 4727 - -
0|0|0|0|0|0|0|0|0|0 2 24 25375 15 8286 - -
0|0|0|0|0|0|0|0|0|0 3 34 35575 14 11290 - -
0|0|0|0|0|0|0|0|0|0 4 30 42573 17 15861 - -
1|-1|1|-1|1|-1|1|-1|1|-1 0 16 1580 16 562 25 488
1|-1|1|-1|1|-1|1|-1|1|-1 1 20 4255 22 1408 - -
1|-1|1|-1|1|-1|1|-1|1|-1 2 22 8468 13 2687 - -
1|-1|1|-1|1|-1|1|-1|1|-1 3 27 14073 13 4609 - -
1|-1|1|-1|1|-1|1|-1|1|-1 4 28 20577 14 6659 - -
Para el algoritmo Shift-Add tenemos que la mejora de la versión con tabla hash res-
pecto de la versión con matriz ronda el 219 %. Además observamos que los tiempos de
ejecución son parecidos a los mostrados para tamaños del patrón inferiores. Esto se co-
rresponde a lo estudiado en [3] ya que para un tamaño del patrón relativamente grande el
tiempo ha permanecido invariable respecto al resto de patrones menores en el árbol sobre
el que hemos buscado.
bruta y esta mejora va aumentando hasta llegar a sixtuplicar el tiempo con 12092 ocu-
rrencias (la mejora es del 500 %).
Por último, para los algoritmos de fuerza bruta optimizado y Boyer Moore observa-
mos que el porcentaje de mejora depende del número de discrepancias considerado y del
número de ocurrencias encontradas. Cuando no se admiten discrepancias el algoritmo
Boyer Moore es mejor que el de fuerza bruta aunque el número de ocurrencias sea alto.
Así tenemos que para 5715 ocurrencias la versión con tabla hash es un 26 % mejor y la
versión con matriz un 271 % mejor. Para 0 ocurrencias las mejoras son mucho mayores en
ambos casos y tenemos que la versión con tabla hash es un 130 % mejor que la de fuerza
bruta y la versión con matriz un 546 % mejor. A partir de estos valores las mejoras van
disminuyendo a medida que aumenta el número de discrepancias y van aparenciendo más
ocurrencias. Para la versión con tabla hash el algoritmo de fuerza bruta pasa a ser mejor
cuando hay dos discrepancias y pocas ocurrencias o cuando hay una discrepancia pero
hay muchas ocurrencias. Las mejoras en cuanto al número de discrepancias suelen ser pa-
recidas y no pasan del 40 % para las 4 discrepancias consideradas. En el caso de la matriz
el algoritmo de Boyer Moore siempre es mejor que el de fuerza bruta pero la diferencia
entre ambos algoritmos disminuye en un factor de división del 1, 5 aproximadamente por
cada discrepancia que se añade.
Figura 4.1: Gráfico con los tiempos de los algoritmos para el arbol e16-A con 0 discrepan-
cias
Para el estudio creamos un excel con 4 árboles ficticios con 78832 medidas y seguimos
todos los pasos para hacer que funcionen en nuestra aplicación. El contenido de los 4
árboles creados es el siguiente:
Nulo 1: árbol con todos los valores a 0. Este árbol nos permite estudiar el patrón
cíclico 0.
Nulo 2: árbol con todos los valores a 0 pero con 10 valores discrepantes cada 1000
medidas. Este árbol nos permite estudiar el patrón cíclico 0 y la diferencia que
supone tener menos ocurrencias que con el árbol Nulo 1.
Alternado 1: árbol con valores 1’s y -1’s de forma alternada. De esta forma la
colección de diferencias del árbol está compuesta por los valores 2 y −2 y podemos
estudiar el patrón cíclico 2 -2.
Alternado 2: árbol con valores 1’s y -1’s de forma alternada pero con 10 valores
4.3. Experimentos adicionales 59
Figura 4.2: Gráfico con los tiempos de los algoritmos para el arbol e16-A con 0 discrepan-
cias
Para realizar el experimento pasamos a todos los árboles distintos tamaños de los
patrones cíclicos 0 y 2 -2 pudiéndo estudiar el caso de tener el máximo número de ocu-
rrencias y ninguna ocurrencia para cada árbol.
Para cada patrón se ha creado una tabla con los cuatro árboles y los tiempos para cada
algoritmo para distintos tamaños del patrón cíclico. Además, tenemos varios diagramas
auxiliares para ver la diferencia de tiempos de forma gráfica tanto cuando hay muchas
ocurrencias en el vector como cuando no hay ninguna. En este segundo caso se utiliza un
diagrama auxiliar quitando los algoritmos de fuerza bruta porque en comparación con el
resto de algoritmos la diferencia de tiempo es muy considerable.
frente a Alternado 1 y Alternado 2 es que los tiempos de ejecución son mucho menores
para los dos últimos debido a que como no se encuentran ocurrencias se producen todos
los desplazamientos posibles.
Para el caso de los vectores Nulo 1 y Nulo 2 tenemos unas 78833 ocurrencias en el
primer vector y unas 77821 en el segundo. Observamos que todos los tiempos aumentan
a medida que pedimos buscar el patrón original repetido varias veces.
ShAdd ShAdd
Tamaño FB
Árbol FB (hash) (matriz)
Patrón Opt
Prep Alg Prep Alg
3 48420 46359 22 13847 5 5723
6 71616 70183 24 13922 4 8933
Nulo 1
12 119632 114183 27 16595 4 9369
24 213630 210181 - - - -
3 48003 42988 23 12829 5 8388
6 70643 65469 25 13016 6 8264
Nulo 2
12 116835 110161 28 14774 7 9282
24 206808 199685 - - - -
3 29871 16182 20 5151 3 1653
6 53554 16121 23 5194 2 1657
Alt 1
12 100490 16014 23 5244 3 1638
24 - - - - - -
3 29574 16123 21 5133 3 1718
6 52995 15995 21 5287 3 1719
Alt 2
12 99900 15858 23 5230 5 1731
24 - - - - - -
En los algoritmos de fuerza bruta la mejora del algoritmo optimizado es del 2,75 %
para Nulo 1 y del 6,75 % para Nulo 2. En este segundo caso observamos que la mejora
disminuye a medida que aumentamos el tamaño del patrón.
Para los algoritmos Shift-Add, la versión con matriz es mejor que la de tabla hash
siempre sin tener en cuenta el tamaño del patrón. Para el vector Nulo 1 la mejora es en
torno al 91 % y para Nulo 2 en torno al 56 %.
En cuanto a los algoritmos de Boyer Moore, tenemos que la versión de matriz siempre
supera a la de tabla hash y su mejora va aumentando a medida que aumentamos el ta-
maño del patrón. Tenemos en media un valor de mejora del 154 % para ambos vectores.
En cuando a la versión exacta y la versión con matriz, normalmente la versión exacta es
mejor que la versión con matriz y en media la mejora es del 10 % para el vector Nulo 1 y
del 23 % para el vector Nulo 2.
4.3. Experimentos adicionales 61
BM BM BM
Tamaño
Árbol (hash) (matriz) (exacto)
Patrón
Prep Alg Prep Alg Prep Alg
3 20 24260 5 10893 12 14113
6 20 33140 6 14170 17 12058
Nulo 1
12 30 55964 6 21034 23 15798
24 30 97601 6 33520 30 29076
3 24 24810 5 11300 15 9918
6 24 35611 6 14881 17 12642
Nulo 2
12 29 55971 7 21064 23 15778
24 31 99538 9 33162 31 25388
3 13 2180 3 823 8 780
6 13 1496 3 520 10 439
Alt 1
12 13 755 2 284 16 259
24 - - - - - -
3 15 2232 3 852 9 773
6 13 1473 3 502 11 441
Alt 2
12 14 704 4 267 18 273
24 - - - - - -
Si comparamos los algoritmos Boyer Moore y Shift-Add obtenemos que las versiones
del algoritmo Shift-Add son mejores que las del Boyer Moore y van aumentando a medida
que aumentamos el tamaño del patrón en un factor cercano al 1, 8 en la mayoría de los ca-
sos. Esto significa que cuando doblamos el tamaño del patrón la mejora de los algoritmos
Shift-Add frente a los de Boyer Moore es cercana a doblarla también. Cuantitativamente,
la versión con tabla hash tiene valores más altos de mejora que la versión con matriz del
algoritmo Shift-Add (84 % y 62 %, respectivamente para tamaño de patrón igual a 3).
Figura 4.3: Gráfico con los tiempos de los algoritmos para el vector Nulo 1
de fuerza bruta y Shift-Add a medida que pedimos buscar el patrón original repetido
varias veces pero disminuyen para el algoritmo Boyer Moore.
Para los algoritmos Boyer Moore observamos que siempre son mejores los algoritmos
exacto y con matriz frente a la versión con tabla hash siendo la mejora del 172 % en media
para la versión con matriz para los dos vectores y del 198 % para la versión exacta. Por
transitividad, el algoritmo exacto es algo mejor que el aproximado en ambos casos pero
la diferencia es muy pequeña y la mejora no supera el 10 % en media en ninguno de los
dos vectores.
En este caso, cuando comparamos los algoritmos Shift-Add y Boyer Moore nos damos
cuenta que las versiones que ganan en todos los casos son las del Boyer Moore. Al doblar
el tamaño del patrón, la diferencia de mejora supera el factor 2 y llega a multiplicarse por
un factor del 3, 8 en algún caso. Cuantitativamente, ambas versiones empiezan con una
mejora por encima del 100 % para los dos vectores y llegan a alcanzar mejoras entre el
4.3. Experimentos adicionales 63
Figura 4.4: Gráfico con los tiempos de los algoritmos para el vector Nulo 1 sin FB
Si comparamos el algoritmo de fuerza bruta optimizado con las versiones del Shift-Add
obtenemos que para la versión de tabla hash la mejora es del 208 % en favor del algoritmo
Shift-Add. Este valor se multiplica por 4 cuando comparamos con la versión con matriz
rondando un valor de mejora del 845 % sin importar el tamaño del patrón considerado.
Por último, si comparamos el algoritmo de fuerza bruta optimizado con las versiones
del algoritmo Boyer Moore obtenemos que la mejora va incrementándose a medida que
aumentamos el tamaño del patrón y ronda la multiplicación por factores entre el 1, 5 y
el 2, 2. En este caso la diferencia en tiempo entre ambos algoritmos es enorme. Para la
versión con tabla hash pasamos de una mejora en torno al 632 % a una en torno al 2086 %.
Para las versiones con matriz y exacta esta diferencia es aún mayor y pasamos de una
mejora en torno al 1909 % a una en torno al 5817 %.
Podemos sacar como conclusión final que el algoritmo Shift-Add suele tener siempre
tiempos parecidos sin importar el tamaño del patrón a buscar y cuantas menos ocurrencias
se encuentren menor es el tiempo que tarda en ejecutarse. Por otro lado, el algoritmo Bo-
yer Moore es bastante malo cuando hay demasiadas ocurrencias en el patrón pero cuando
no hay casi ninguna el tiempo que tarda en ejecutarse disminuye a medida que aumenta-
mos el tamaño del patrón y alcanza unos valores muy bajos respecto al resto de algoritmos.
En cuanto a los diagramas, observamos en el diagrama 4.3 que para el vector Nulo
64 Capítulo 4. Análisis de tiempos
Figura 4.5: Gráfico con los tiempos de los algoritmos para el vector Alternado 1
1 donde casi todos son ocurrencias los algoritmos de fuerza bruta aumentan cuadrática-
mente a medida que aumenta el tamaño del patrón.
En el diagrama 4.4 vemos que el algoritmo de Booyer Moore con tabla hash sigue un
incremento constante que ronda un factor que va del 1, 14 al 1, 03 cada vez que aumen-
tamos en 1 el tamaño del patrón. El factor de multiplicación baja en cada incremento
del patrón. En cuanto al resto de algoritmos, observamos que el algoritmo Shift-Add con
tabla hash es equiparable a las versiones exacta y con matriz del algoritmo Boyer Moore
y que por debajo de todos estos algoritmos se encuentra la versión con matriz del algorit-
mo Shift-Add. Las versiones de Boyer Moore aumentan un poco cuando crece el tamaño
del patrón pero las versiones del Shift-Add tienen un valor practicamente constante para
cualquier tamaño del patrón.
Para el vector Alternado 1 observamos primero en el diagrama 4.5 que la cota superior
dada por el algoritmo de fuerza bruta es muy costosa en tiempo y hace parecer que el
resto de algoritmos no varían a medida que aumenta el tamaño del patrón.
En el diagrama 4.6 donde hemos quitado los dos algoritmos de fuerza bruta observamos
la verdadera tendencia de los algoritmos cuando no hay practicamente ocurrencias en el
vector. El algoritmo más costoso es la versión de tabla hash del algoritmo Shift-Add. Luego
tenemos un poco más abajo las versiones con tabla hash del algoritmo de Boyer Moore
y la versión con matriz del algoritmo Shift-Add y por último las dos versiones que faltan
4.3. Experimentos adicionales 65
Figura 4.6: Gráfico con los tiempos de los algoritmos para el vector Alternado 1 sin FB
del algoritmo Boyer Moore. Observamos otra vez que los algoritmos Shift-Add siguen una
linea recta a medida que aumenta el tamaño del patrón. En este caso, las versiones de
Boyer Moore disminuyen su valor a medida que aumenta el tamaño del patrón dando como
cierto el resultado teórico de que para un texto con un número razonable de ocurrencias
a medida que aumentamos el tamaño del patrón este algoritmo va a funcionar mejor que
el resto.
Los resultados del estudio del patrón cíclico 2 -2 se encuentran en las tablas 4.11 y
4.12 y en los diagramas 4.7, 4.8, 4.9 y 4.10. Con este patrón estudiamos el caso en el
que hay muchas ocurrencias pero no el máximo posible ya que sólo pueden repetirse cada
2 posiciones. Debido a esto tenemos unas 39410 ocurrencias para el vector Alternado 1
y unas 38860 para Alternado 2. Como en el estudio anterior, los vectores que no están
destinados para tener la subcadena buscada no tienen ninguna ocurrencia. Volvemos a
observar que, por lo general, los tiempos aumentan al aumentar el tamaño del patrón
buscado.
Esta vez empezamos analizando los vectores Alternado 1 y Alternado 2 al ser los que
nos aportan más datos para nuestro experimento.
El algoritmo de fuerza bruta es una cota superior para el resto de algoritmos. Su ver-
66 Capítulo 4. Análisis de tiempos
ShAdd ShAdd
Tamaño FB
Árbol FB (hash) (matriz)
Patrón Opt
Prep Alg Prep Alg
3 29623 15927 23 4964 3 1645
6 52594 15966 23 4943 3 1643
Nulo 1
12 99234 16044 27 4990 2 1631
24 - - - - - -
3 28768 15424 23 4958 3 1621
6 52046 15066 24 4925 4 1644
Nulo 2
12 97679 15376 26 5080 4 1645
24 - - - - - -
3 38479 25842 25 9767 5 3617
6 62026 36882 25 9783 5 3656
Alt 1
12 107895 60381 28 9732 4 3645
24 205162 109330 - - - -
3 39340 26626 23 9737 5 3717
6 62359 43273 27 9847 5 3714
Alt 2
12 109618 65983 34 10485 7 3823
24 202622 106293 - - - -
sión optimizada mejora este algoritmo en un 50 % para el tamaño del patrón igual a 3 y
a medida que se dobla este valor esta mejora va multiplicandose por un factor entre 1,1
y 1,5 llegando a ser superior con una mejora del 90 % para el tamaño del patrón igual a 24.
Entre las versiones Shift-Add observamos que sus valores permanecen relativamente
constantes en el tiempo aunque aumentan un poco a medida que doblamos el tamaño
del patrón. La mejora de la versión con matriz respecto a la de Shift-Add permanece
constante con un valor en media del 172 %.
Para las versiones de los algoritmos de Boyer Moore tenemos que las versiones exacta
y con matriz superan a la versión con tabla hash y que esta mejora se incrementa con un
factor del 1, 2 cada vez que doblamos el tamaño del patrón. Se pasa de una mejora del
195 % a una del 254 % para la versión con matriz y de una mejora del 243 % a una del
308 % para la versión exacta. Observamos que en todos los casos el algoritmo exacto es
mejor que el aproximado con matriz y, cuantitativamente, la mejora va del 13 % al 17 %
dependiendo del caso. Con este resultado demostramos por tanto que el algoritmo exacto
es mejor que el aproximado cuando los patrones son cíclicos.
BM BM BM
Tamaño
Árbol (hash) (matriz) (exacto)
Patrón
Prep Alg Prep Alg Prep Alg
3 15 2300 2 839 7 804
6 14 1441 2 516 10 446
Nulo 1
12 14 824 2 248 15 259
24 - - - - - -
3 16 3777 3 2179 10 2227
6 15 1427 3 536 12 426
Nulo 2
12 14 822 4 359 17 275
24 - - - - - -
3 18 11750 4 3943 12 3363
6 21 18075 4 5596 16 4780
Alt 1
12 24 30128 6 8899 21 7759
24 30 53850 5 15285 31 13242
3 23 12045 6 4113 16 3564
6 19 18932 5 5614 16 4932
Alt 2
12 22 32341 5 8589 23 7579
24 29 53303 7 14967 29 12978
mismo y se multiplica la mejora primero por 5 y luego por 2, 5 pero en este caso la mejora
es bastante menor que la que teníamos en la versión con tabla hash. Cuantitativamente
pasamos de una mejora en media del 10 % para tamaño 3 a una del 130 % para tamaño 12.
En cuanto al algoritmo de fuerza bruta optimizado frente a las versiones del algoritmo
Shift-Add observamos que para ambas versiones la diferencia se va aumentando en un
factor entre 1, 5 y 1, 8 a medida que se dobla el tamaño del patrón. Esto es así debido
a que el algoritmo de fuerza bruta tarda considerablemente más en cada caso mientras
que los algoritmos Shift-Add tienen unos tiempos constantes para cualquier tamaño del
patrón. Cuantitativamente la versión con tabla hash pasa de una mejora del 170 % a una
del 525 % y la versión con matriz de una mejora del 615 % a una del 1590 %.
Por último, si comparamos el algoritmo de fuerza bruta optimizado frente a las versio-
nes de Boyer Moore observamos que la diferencia de mejora se va manteniendo a medida
que doblamos el tamaño del patrón. Para la versión con tabla hash tenemos una mejora
del 110 % en media, para la versión con matriz tenemos una mejora del 600 % y para la
versión exacta tenemos una mejora del 707 %.
Figura 4.7: Gráfico con los tiempos de los algoritmos para el vector Alternado 1
media que aumenta en un factor entre 2, 2 y 2, 8 cada vez que doblamos el tamaño
del patrón.
La versión de matriz del algoritmo Shift-Add es superior a la versión con tabla hash
en un 203 % en media y su mejora no depende del tamaño del patrón.
Las versiones exacta y con matriz mejoran a la versión con tabla hash en todos
los casos y suelen rondar un incremento superior al 200 %. Esta diferencia solo se
incumple en el caso del vector Nulo 2 para un tamaño del patrón igual a 3. En este
caso la mejora es inferior y ronda el 70 %. Esta disminución posiblemente se deba
a que cada 2000 datos hay 10 datos que se corresponden con diferencias de valor
2, así que en estas medidas los algoritmos de Boyer Moore no pueden desplazar los
datos. Con tamaños del patrón superiores se elimina este problema.
La versión exacta del algoritmo de Boyer Moore es ligeramente mejor a la versión con
matriz. En muchos casos no hay practicamente diferencias de tiempo entre ambos
algoritmos pero en los casos en los que difiere esta diferencia puede llegar a alcanzar
un incremento del 30 % a favor del algoritmo de Boyer Moore exacto.
Entre las versiones con tabla hash, el algoritmo de Boyer Moore siempre es superior
al de Shift Add. Quitando el caso anómalo del algoritmo de Boyer Moore ya explicado
(vector Nulo 2 para tamaño de patrón igual a 3) la mejora se dobla cada vez que
doblamos el tamaño del patrón. De esta forma empezamos con un valor de mejora
igual al 115 % con tamaño 3 (del 31 % en el caso anómalo) y terminamos con un
valor superior al 500 % de mejora.
4.3. Experimentos adicionales 69
Figura 4.8: Gráfico con los tiempos de los algoritmos para el vector Alternado 1 sin FB
En cuanto al algoritmo de fuerza bruta optimizado frente a las versiones del algo-
ritmo Shift-Add vemos que el algoritmo Shift-Add siempre es superior pero como
ambos algoritmos mantienen sus valores en el tiempo las mejoras que se obtienen
son parecidas sin importar el tamaño del patrón. En el caso de la tabla hash tenemos
una mejora del 213 % en media y en el caso de la matriz del 851 %.
Las diferencias del algoritmo de fuerza bruta optimizado frente a los algoritmos de
Boyer Moore se incrementan a medida que doblamos el tamaño del patrón. Esto es
debido a que el algoritmo de fuerza bruta permanece constante pero los algoritmos
de Boyer Moore disminuyen a medida que el patrón se hace más largo. Cuantitativa-
mente la mejora se multiplica por un factor de 1, 8 cada vez que doblamos el tamaño
del patrón. Para la versión de tabla hash el algoritmo empieza con una mejora del
450 % y termina con una del 1800 %. Para las otras dos versiones en el vector Nulo
1 se empieza con una mejora del 1840 % y se termina con una del 6380 %. En el
vector Nulo 2 se empieza con una mejora del 600 % y se termina con una del 4800 %
en media.
Si estudiamos los diagramas vemos en el 4.7 que los algoritmos de fuerza bruta, fuerza
bruta optimizado y la versión de Boyer Moore con tabla hash se comportan del mismo
modo pero con una diferencia de tiempos considerable entre cada uno de ellos. Obser-
vamos que el crecimiento a medida que se dobla el tamaño del patrón es cuadrático en
70 Capítulo 4. Análisis de tiempos
Figura 4.9: Gráfico con los tiempos de los algoritmos para el vector Nulo 1
los algoritmos de fuerza bruta. También vemos que el incremento que se produce en el
algoritmo de Boyer Moore con tabla hash se multiplica por un factor entre el 1, 13 y el
1, 04 cada vez que aumentamos el tamaño del patrón en 1.
En el diagrama 4.8 observamos como las dos versiones de los algoritmos Shift-Add
permanecen constantes cuando doblamos el tamaño del patrón. Además vemos que las
versiones exacta y con matriz de Boyer Moore tienen sus tiempos entre las dos versiones
del algoritmo Shift-Add y que a medida que se dobla el tamaño del patrón van aumen-
tando pero de una forma más suave que la versión con tabla hash. También observamos
como el algoritmo exacto es ligeramente mejor que el aproximado y que esta diferencia
aumenta a medida que doblamos el tamaño del patrón.
En el diagrama 4.9 observamos que la diferencia entre la cota superior dada por el
algoritmo de fuerza bruta es muy superior al tiempo que se obtiene en el resto de al-
goritmos. En el diagrama 4.10 observamos que los algoritmos de Shift-Add permanecen
constantes a medida que se aumenta el tamaño del patrón mientras que los de Boyer
Moore disminuyen a medida que se dobla el patrón. Observamos que para patrones largos
y con pocas ocurrencias la versión con tabla hash del algoritmo de Boyer Moore es incluso
mejor que la versión con matriz del algoritmo Shift-Add.
Figura 4.10: Gráfico con los tiempos de los algoritmos para el vector Nulo 1 sin FB
Para poder estudiar esto añadimos al excel de experimentos otros 4 árboles ficticios con
78832 medidas y seguimos todos los pasos para hacer que funcionen en nuestra aplicación.
El contenido de los 4 árboles es el siguiente:
Disperso 1: árbol con los valores del vector Alternado 2 multiplicados por 5. De
esta forma la colección de diferencias del árbol está compuesta por los valores 10 y
−10 y podemos estudiar alfabetos con al menos 20 números. Estudiamos el patrón
cíclico 10 -10.
Disperso 2: árbol con los valores del vector Alternado 2 multiplicados por 10. De
esta forma la colección de diferencias del árbol está compuesta por los valores 20 y
−20 y podemos estudiar alfabetos con al menos 40 números. Estudiamos el patrón
cíclico 20 -20.
Disperso 3: árbol con los valores del vector Alternado 2 multiplicados por 50. De
esta forma la colección de diferencias del árbol está compuesta por los valores 100 y
−100 y podemos estudiar alfabetos con al menos 200 números. Estudiamos el patrón
cíclico 100 -100.
Disperso 4: árbol cuyos datos aumentan su diferencia en 1 respecto de un dato
al siguiente. Este árbol está constituido por bloques de valores que empiezan con
una diferencia de valor 1 y terminan con una de valor 98 aumentando la diferencia
en cada dato. Con este árbol conseguimos un vector de diferencias con alfabeto de
72 Capítulo 4. Análisis de tiempos
tamaño 100 y con muchos datos diferentes en el árbol para hacer actuar las dos
reglas del algoritmo de Boyer Moore. El patrón que buscaremos será la sucesión de
números que empieza en el número 50 y aumenta de 1 en 1, es decir, el patrón 50
51 52....
Volvemos a estudiar los tiempos de ejecución con diferentes tamaños de los patrones
que hemos considerado. Estudiamos el caso en el que tenemos el máximo número de ocu-
rrencias y en el que no hay ninguna para cada árbol.
Se ha creado una tabla por patrón estudiado en donde aparecen los tiempos del al-
goritmo de fuerza bruta sin optimizar (la cota superior de los tiempos), el algoritmo
aproximado de Boyer Moore con matriz y el algoritmo exacto de Boyer Moore. El árbol
Disperso 4 no aparece en los estudios de los patrones 10 -10 y 20 -20 debido a que el
árbol Disperso 3 cumple su misma función y, por tanto, no aporta nada nuevo al estudio.
BM BM
Tamaño
Árbol Ocurrencias FB (matriz) (exacto)
Patrón
Prep Alg Prep Alg
3 38865 43071 11 4149 23 3773
Disperso 1 6 38705 69545 13 5855 26 5096
12 38462 118260 13 9174 41 8585
3 0 33390 9 1010 16 905
Disperso 2 6 0 55698 15 600 20 580
12 0 107978 11 353 24 281
3 0 31816 15 931 24 905
Disperso 3 6 0 58601 15 499 28 637
12 0 106701 18 303 33 271
La cota superior dada por el algoritmo de fuerza bruta va aumentando a medida que
aumentamos el tamaño del patrón y tarda más cuántas más ocurrencias se producen. La
mejora producida entre este algoritmo y los de Boyer Moore aumenta a medida que se
dobla el tamaño del patrón. Con muchas ocurrencias (vector Disperso 1 ) el factor entre
ambos tiempos es de 12. Sin ninguna ocurrencia el factor de multiplicación pasa de 34
para tamaño de patrón igual a 3 a 358 para tamaño del patrón igual a 12.
Si comparamos las versiones de los algoritmos de Boyer Moore obtenemos que para el
alfabeto de tamaño 20 y muchas ocurrencias se comporta mejor la versión exacta con un
4.3. Experimentos adicionales 73
Para el alfabeto de tamaño 40 y sin ocurrencias obtenemos una mejora que fluctúa del
3, 5 % al 25, 6 % entre los distintos casos. Para el alfabeto de tamaño 200 sin ocurrencias
llegamos a tener una mejora del 12 % para tamaño del patrón igual a 12 pero venimos de
un empeoramiento del 21, 6 % para tamaño del patrón igual a 6.
Necesitamos más datos para poder llegar a una conclusión entre ambas versiones del
algoritmo.
BM BM
Tamaño
Árbol Ocurrencias FB (matriz) (exacto)
Patrón
Prep Alg Prep Alg
3 0 31480 6 870 14 850
Disperso 1 6 0 59440 7 573 21 542
12 0 108899 7 350 20 262
3 38865 42025 14 4115 23 3675
Disperso 2 6 38705 64744 15 5586 26 4844
12 38462 118366 21 9356 47 8278
3 0 32007 23 933 24 831
Disperso 3 6 0 53617 14 483 23 507
12 0 106681 18 324 35 302
Volvemos a observar que la cota superior dada por el algoritmo de fuerza bruta aumen-
ta a medida que doblamos el tamaño del patrón y que es mayor cuantas más ocurrencias
se encuentren en el árbol. Las diferencias de este algoritmo con los de Boyer Moore son
parecidas al caso anterior. Cuando se producen muchas ocurrencias, vector Disperso 2,
los tiempos del algoritmo Boyer Moore tienen que multiplicarse por un factor de 12 para
llegar a los de fuerza bruta. Sin ocurrencias, el factor es de 36 para tamaño de patrón
igual a 3 y de 353 para tamaño de patrón igual a 12.
Comparando las dos versiones del algoritmo de Boyer Moore obtenemos que para el
74 Capítulo 4. Análisis de tiempos
vector Disperso 2 con alfabeto de tamaño 40 la mejora del algoritmo exacto respecto al
aproximado es en media del 13,42 % y no fluctúa mucho a medida que doblamos el tamaño
del patrón. Para los casos sin ocurrencias tenemos para el vector Disperso 1 una mejora
que pasa del 2,35 % para tamaño del patrón igual a 3 a una del 33,58 % para tamaño
del patrón igual a 12 a favor del algoritmo exacto. En el caso del vector Disperso 3 con
alfabeto de tamaño 200 tenemos una mejora del 12,27 % para tamaño del patrón igual a
3, un deterioro del 4,7 % para tamaño del patrón igual a 6 y una mejora del 7,3 % para
tamaño del patrón igual a 12.
Empezamos a ver que cuánto más largo es el patrón mejor funciona el algoritmo exacto
pero con alfabetos grandes las diferencias entre ambas versiones se asemejan más que con
alfabetos pequeños.
BM BM
Tamaño
Árbol Ocurrencias FB (matriz) (exacto)
Patrón
Prep Alg Prep Alg
3 0 31555 15 839 24 896
Disperso 1 6 0 58354 19 559 27 574
12 0 107488 21 367 36 310
3 0 31272 17 937 26 917
Disperso 2 6 0 56509 17 557 30 540
12 0 106426 19 368 29 246
3 38865 42122 18 4117 29 3593
Disperso 3 6 38705 69634 21 5104 36 5068
12 38463 119984 29 10926 48 8157
3 0 30858 15 848 19 768
Disperso 4 6 0 54805 25 494 26 547
12 0 102334 47 353 30 238
La cota superior dada por el algoritmo de fuerza bruta vuelve a aumentar a medi-
da que doblamos el tamaño del patrón y aumenta para el vector donde se producen las
ocurrencias. En cuanto a factores de multiplicación de los tiempos respecto del algoritmo
de Boyer Moore pasamos de un factor de 35 para tamaño del patrón igual a uno de 338
para tamaño del patrón igual a 12 en el caso de no tener ninguna ocurrencia. Para el
vector Disperso 3 con muchas ocurrencias se tiene un factor de multiplicación de 12 para
pasar del tiempo de ejecución de los algoritmos de Boyer Moore al tiempo de ejecución
4.3. Experimentos adicionales 75
En el caso del vector Disperso 3 observamos que el algoritmo exacto es siempre mejor
que el aproximado y que esa mejora para el tamaño del patrón igual a 12 llega al 34 %.
Figura 4.11: Gráfico con los tiempos de los algoritmos para patrones con muchas ocurren-
cias en distintos alfabetos
Las conclusiones de este experimento es que las diferencias entre las versiones exac-
ta y aproximada del algoritmo de Boyer Moore para patrones cíclicos se aproximan a
medida que aumenta el tamaño del alfabeto pero vuelven a diferenciarse si tenemos un
patrón muy largo. En el diagrama 4.11 podemos ver los tiempos que se obtienen para las
versiones exacta (BME) y aproximada (BMA) en los casos donde cada vector alcanza el
mayor número de ocurrencias. Así podemos ver la diferencia que nos aporta el tamaño
del alfabeto ya que el vector Disperso 1 (D1) tiene un alfabeto de tamaño 20, el vector
Disperso 2 (D2) un alfabeto de tamaño 40 y el vector Disperso 3 (D3) un alfabeto de
tamaño 200.
76 Capítulo 4. Análisis de tiempos
Observamos que los tiempos para el algoritmo exacto son parecidos para cualquier
alfabeto y son menores que para el algoritmo aproximado. Además, observamos que para
un alfabeto mayor el algoritmo aproximado empieza a empeorar para patrones de tamaño
superior a 6. Por último, también vemos que para tamaños del patrón largos los algoritmos
exactos se posicionan todos en tiempos parecidos sin importar el tamaño del alfabeto
mientras que para el caso aproximado el que menor tiempo tiene es el de menor tamaño
del alfabeto y la diferencia es considerable a medida que aumentamos el tamaño de éste.
BM BM
Tamaño
Árbol Ocurrencias FB (matriz) (exacto)
Patrón
Prep Alg Prep Alg
3 0 30671 13 875 18 794
Disperso 3 6 0 56941 16 517 32 507
12 0 103843 18 340 67 269
3 788 30197 9 976 16 925
Disperso 4 6 788 56204 11 671 17 702
12 788 106005 10 576 21 505
Vemos de nuevo que la cota superior dada por el algoritmo de fuerza bruta aumenta
a medida que doblamos el tamaño del patrón. Para el caso sin ocurrencias pasamos una
mejora del 3584 % a una del 34025 % a favor del algoritmo de Boyer Moore y para el caso
con ocurrencias de una del 3079 % a una del 19597 %. Observamos que el algoritmo Boyer
Moore funciona mejor cuántas menos ocurrencias aparezcan en el vector y que el tiempo
va disminuyendo a medida que doblamos el tamaño del patrón.
En cuanto a las versiones de algoritmo Boyer Moore tenemos que para el caso sin ocu-
rrencias siempre gana el algoritmo exacto y su mejora fluctúa entre el 2 % y el 25 %. Para el
caso con ocurrencias a veces el algoritmo aproximado es mejor que el de Boyer Moore para
tamaños del patrón pequeños pero cuando doblamos y alcanzamos un valor del tamaño
del patrón de 12 vemos que gana el algoritmo exacto con un porcentaje del 14 % de mejora.
Vemos entonces que con alfabetos grandes las dos versiones del algoritmo Boyer Moore
son muy parecidas en tiempos pero que el algoritmo exacto funciona mejor que el algoritmo
aproximado para patrones largos. En el diagrama 4.12 observamos que los tiempos del
vector Disperso 3 sin ocurrencias son menores que los del vector Disperso 4. Además
4.3. Experimentos adicionales 77
Figura 4.12: Gráfico con los tiempos de los algoritmos para el patrón 50 51 52...
los tiempos del algoritmo exacto empiezan y terminan por debajo que los del algoritmo
aproximado.
Capítulo 5
Conclusiones
Se ha desarrollado una aplicación que puede tratar las medidas del tronco de los ár-
boles dadas por un archivo de excel y que devuelve distintos resultados interesantes sobre
estas medidas a petición del usuario.
Como trabajo a realizar quedaría pendiente seguir añadiendo nuevas funciones que
fueran útiles para el estudio de las medidas de los árboles. Además se podría realizar un
estudio de cuáles de estas funciones son las más importantes en el mundo real para seguir
trabajando sobre ellas añádiendo nuevas mejoras.
En cuanto a resultados específicos hemos podido constatar que la regla del sufijo bueno
del algoritmo Boyer Moore en búsquedas exactas siempre mejora algo al mero uso de la
regla de desplazamiento de carácter malo. Y que esta mejora se acentúa si tratamos con
patrones cíclicos que se dan bastantes veces en los vectores estudiados. Además, hemos
estudiado que esta mejora se mantiene en alfabetos grandes cuando el patrón es largo y
que, en la práctica, usar las dos reglas al final aporta más beneficios que solo usar la regla
de desplazamiento de carácter malo. Por lo tanto, un posible nuevo estudio sería intentar
adaptar esta regla al caso aproximado y ver cómo son sus nuevos tiempos.
79
Bibliografía
[2] Gusfield, D. Algorithms on Strings, Trees, and Sequences: Computer Science and
Computational Biology. Cambridge University Press, New York, NY, USA, 1997.
ISBN 0-521-58519-8.
[3] Hirvola, T. Bit parallel approximate string matching under Hamming distance.
Proyecto Fin de Carrera, Aalto University; School of Science, 2016.
[7] Salmela, L., Tarhio, J. y Kalsi, P. Approximate Boyer Moore String Matching
for Small Alphabets. Algorithmica, vol. 58(3), páginas 591–609, 2010.
81