martes, 21 de octubre de 2014

El elemento neutro de la suma y el producto

Hay recuerdos muy lejanos al aprender a sumar o a multiplicar, en el cole (o en la universidad) nos explicaban las propiedades de la suma y del producto: propiedad transitiva, propiedad distributiva, etc. una de las propiedades (generalmente la primera) es la propiedad del elemento neutro que dice:

A todo número que se le sume cero da el mismo número. -> El 0 es el elemento neutro de la suma

A todo número que se le multiplique por uno da el mismo número. -> El 1 es el elemento neutro de la multiplicación.

¿A que viene todo esto del elemento neutro? Esto va relacionado a operaciones que hacemos de forma acumulativa, por ejemplo:

1. Calcule la suma de los N primeros números enteros.
2. Calcula el factorial de un número N.

En ambos casos, una de las soluciones por las que se podría optar es hacer un bucle que acumule la suma o el producto de esta forma:

//ejemplo 1
Para i=1 hasta N hacer
  suma = suma + i;
Fin_Para

//ejemplo 2
Para i=1 hasta N hacer
  factorial = factorial *  i;
Fin_Para

En ambos casos al estar calculando un valor acumulado, reutiliza el valor de la variable en la iteración anterior (suma utiliza a suma para calcularse a si misma, lo mismo con factorial).
¿Pero qué pasa en la primera iteración? En la primera iteración es necesario que suma y factorial simplemente sean igual a "i", por este motivo es necesario (indispensable) inicializar ambas variables con el valor del elemento neutro. Los algoritmos completos quedarían así:

//ejemplo 1
suma = 0: //variable inicializada con el valor del elemento neutro de la operación que haremos
Para i=1 hasta N hacer
  suma = suma + i //la primera iteración: suma = 0 + i;
Fin_Para

//ejemplo 2
factorial = 1 //variable inicializada con el valor del elemento neutro de la operación que haremos
Para i=1 hasta N hacer
  factorial = factorial *  i: //la primera iteración:  factorial = 1 * i
Fin_Para

Para cualquier cálculo en que utilicemos acumuladores es absolutamente necesario inicializarlos correctamente, por lo general con el valor del elemento neutro de la operación de acumulación.

jueves, 2 de octubre de 2014

Árboles binarios, un ejemplo práctico

Muchas veces nos es difícil entender para qué nos enseñan algo sin explicarnos la aplicación. El tiempo que llevo dando clases particulares he podido ver apuntes con mucho contenido y con pocos ejemplos prácticos, esto desmotiva mucho porque el estudiante piensa ¿Para qué me enseñan esto?.

Esto pasa generalmente con el tema de árboles, te explican los tipos de árboles existen (binarios, múltiples, balanceados, etc) y cómo recorrerlos (pre-orden, post-orden, etc), sin embargo son muy pocos los ejemplos prácticos que he podido encontrar.

El objetivo de este post es mostrar un ejemplo práctico en que se podría hacer uso de los árboles, aunque también se puede hacer uso de otras soluciones.

Tenemos un fichero de texto que contiene en cada linea la edad de personas encuestadas para un estudio de mercado, lo que nos interesa es leer el fichero y mostrar en pantalla la cantidad de repeticiones por cada edad, es decir la edad que más se repiten en la muestra.

Partimos de la premisa que la información no está ordenada y es una muestra de 1000 personas.

Utilizaremos para cada número una estructura (o clase) que almacene el valor de la edad y el valor de la repetición:

public struct elemMuestra
{
public int edad;
public int frecuencia;
}

El algoritmo que podríamos plantear es el siguiente:

1. Leer número del fichero
2. Buscar el número en la estructura ordenada
2.1. Si el número existe en la estructura, incrementar el número de repeticiones
2.1. Si el número NO existe en la estructura, inserto el número en la estructura
3. Cuando termino de leer todos los números del fichero, imprimo cada número con su frecuencia de repetición guardada en la estructura.

En el punto 2 vemos que utilizamos una estructura ordenada, aquí podríamos utilizar un vector, una lista de punteros, un árbol binario,etc. lo importante es que la estructura que definamos nos sea útil tanto para buscar si el número ya existe en la estructura, como para insertar un nuevo número en la estructura.

Si utilizamos un vector, podríamos realizar una búsqueda binaria, debido a que los índices del vector ayudan a este tipo de búsqueda, sin embargo si utilizamos una lista de punteros o un árbol binario tendríamos que usar una búsqueda secuencial, pero sin duda el recorrido en un árbol podría ser mucho más eficiente que el recorrido en una lista de punteros, debido a que la forma del árbol no requiere recorrer a todos sus elementos sino que al tener un orden binario es como si realmente se hiciera una búsqueda binaria, a la búsqueda en un árbol binario se le llama búsqueda Silaina.

El otro punto a evaluar es el tema de la inserción, cuando ya sabemos que el número que hemos leído de fichero aún no existe en la estructura que hemos escogido, utilizar un vector podría ser lo menos eficiente, puesto que si tenemos ordenados 900 números en 20 edades distintas, y llega una nueva edad que es la menor de todas, debemos desplazar todas las edades a una casilla posterior para insertar la nueva edad de forma ordenada, ese desplazamiento puede ser muy costoso. En una lista de punteros la inserción es relativamente sencilla y en un árbol también.

A nivel de programación los árboles son más fáciles de trabajar con funciones recursivas, por tanto si tenéis poca experiencia en funciones recursivas y la cantidad de elementos de la muestra no es tan grande, puede ser más sencillo de programar con vectores + búsqueda binaria o secuencial, sin embargo si veis que la muestra es muy grande y luego vais a realizar muchas búsquedas sobre la estructura cargada y ordenada es mejor trabajarla con árboles binarios + búsqueda sobre árboles binarios.

Ahora bien, para que la búsqueda en un árbol binario sea aun más eficiente deberíamos lograr que el árbol tenga el menor numero de niveles, para esto se realiza lo que se llama el balanceo de árboles: