Retículos y Álgebras de Boole Laboratorio de Matemática Discreta Jesús Martínez Mateo jmartinez@fi.upm.es Práctica 1. Ordenación topológica A. Herramientas necesarias Para la práctica que vamos a realizar a continuación necesitamos conocer las siguientes herramientas que nos proporciona el lenguaje de programación Python: creación y manipulación de listas de elementos, matrices, bucles y definición de funciones. Listas de elementos Una lista en Python puede definirse de distintas formas. Por ejemplo, si conocemos todos los elementos que componen la lista, podemos construirla escribiendo la secuencia de elementos de la lista separados por comas y delimitados por corchetes: >>> A = [1, 1, 2, 3, 5, 8, 13] Una vez definida la lista, podemos acceder al valor de un elemento de la lista utilizando su posición o índice (tenga en cuenta que los índices de los elementos de la lista comienzan a partir del 0, esto es, el primer elemento de la lista A es A[0]): >>> A[0] 1 >>> A[4] 5 Podemos conocer el tamaño de una lista con el comando len(): >>>len(a) 7 Por otro lado, si no conocemos los elementos que van a componer nuestra lista, podemos crear una lista inicialmente vacía (e.g. L = []), o parcialmente completa, y añadir elementos de forma sucesiva. Una opción, es añadir elementos al final de la lista utilizando el método append() como se muestra a continuación: >>> L = [2, 4, 8] >>> L.append(16) >>> L [2, 4, 8, 16]
Para eliminar un elemento de una lista, utilizamos el método remove() indicando como parámetro el valor del elemento que queremos eliminar. Por ejemplo: >>> L = [2, 4, 8] >>> L.remove(4) >>> L [2, 8] Matrices Existen varias formas de trabajar con matrices en Python. Una opción posible es definir una matriz como listas de dos niveles (o listas de listas), esto es, una lista donde cada elemento representa una fila (o columna) de la matriz, y por lo tanto ese elemento se vuelve a definir como una lista. Por ejemplo, podemos representar la matriz de la siguiente forma: >>> M = [[1,2],[3,4]] Para encontrar el elemento m i,j de la matriz (el correspondiente a la fila i y columna j) utilizamos la misma notación que vimos anteriormente para listas (teniendo en cuenta que ahora trabajamos con dos listas y que los índices comienzan desde el 0): >>> M[0][1] 2 Nosotros trabajaremos con matrices definidas de esta forma. Sin embargo, para facilitar el manejo de las matrices utilizaremos la biblioteca de funciones SymPy que nos permite operar con matrices de forma más sencilla. Para esto, lo primero que tenemos que hacer es importar la biblioteca: >>> from sympy import * En este momento, ya estamos en disposición de crear nuestra matriz, de la misma forma descrita anteriormente (i.e. como listas de listas), pero ahora llamando a la función Matrix(): M = Matrix([[1,2],[3,4]]) Ahora, podemos obtener el número de columnas y filas de la siguiente forma: >>> M.cols >>> M.rows Podemos también extraer una fila o columna determinada. Por ejemplo, el siguiente comando extrae la columna 1 (esto es, la segunda columna de la matriz M): >>> M[:,1] Así como borrar filas o columnas:
>>> M.col_del(1) >>> M.row_del(1) Bucles en Python Los dos tipos de bucles más comunes en Python son el for y el while. Con el bucle for podemos recorrer todos los elementos de una lista de la siguiente forma: L = [1, 2, 3] for n in L: print n El mismo bucle podría realizarse recorriendo un rango de valores con la función range(). Si proporcionamos un único parámetro a la función range(n), ésta devuelve una lista de valores comprendida entre 0 y n-1. Si proporcionamos dos valores, a y b, range(a,b), la lista estará comprendida entre a y b-1. Por ejemplo: for n in range(3): print n, L[n] for n in range(0,3): print n, L[n] Otra forma de trabajar con bucles es utilizando el comando while. En este caso, el bucle se controla mediante una condición de parada como muestra el siguiente ejemplo: n = 0 while n < 3: print n n = n + 1 Definición de funciones Conforme aumenta la complejidad de nuestros programas, tenemos que agrupar distintos grupos de sentencias en funciones que nos permitan reducir el conjunto de sentencias utilizadas (reutilizando llamadas a funciones), así como aclarar la descripción y comprensión del código. A continuación mostramos un ejemplo de una función en Python que recibe como entrada una lista de elementos, y ejecuta un bucle que suma cada uno de los elementos de la lista para devolver la suma total a su salida: def sum(l): weight = 0 for v in L: # for each v in L weight = weight + v return weight
La siguiente función recibe como parámetro de entrada una matriz y, asumiendo que se trata de la matriz representativa de una relación de orden, busca el conjunto de elementos minimales de la matriz, esto es, aquellos elementos en cuya columna sólo encontramos ceros (excepto en el elemento de la diagonal): def minimal(m): S = [] # empty list for n in range(m.cols): # for each column in M if sum(m[:,n]) == 1: S.append(n) return S B. Planteamiento del problema La empresa PIQUEA.COM se dedica a la venta de muebles económicos a través de Internet. Para ahorrar en el transporte y distribución de los muebles, estos se entregan desmontados y es el comprador el que tiene que ensamblar cada mueble adquirido. Para el montaje la empresa proporciona un conjunto de instrucciones detallado indicando los pasos a realizar y el orden en el que deben realizarse dichos pasos. Por ejemplo, imaginemos que un libro de instrucciones se compone de 7 pasos nombrados con las letras del alfabeto A, B, C, D, E, F y G. Estos pasos deben ejecutarse siguiendo un orden parcial dado por el diagrama de Hasse de la siguiente figura: G F D C A B E FIG. 1 La figura muestra, por ejemplo, que los pasos B y E deben ejecutarse antes de poder llevar a cabo el paso A. Pero, cómo podemos obtener una lista de los pasos a ejecutar que asegure el cumplimiento del diagrama de Hasse descrito? Algoritmo de ordenación topológica Una posible descripción del algoritmo de ordenación topológica es la siguiente:
Paso 1: Si la matriz tiene dimensión 1, añadimos el elemento a la lista de elementos ordenados y el procedimiento termina. Paso 2: Elegimos a un elemento minimal en M, y añadimos dicho elemento a la lista de elementos ordenados. Paso 3: Eliminamos el elemento elegido de la matriz y volvemos al paso 1 Pseudocódigo Basada en la descripción anterior, programa una función que implemente el algoritmo arriba descrito, teniendo en cuenta que disponemos de las funciones anteriormente descritas para calcular el peso de una lista (fila o columna) y obtener una lista de minimales a partir de la matriz de una relación de orden. Como ayuda se proporciona el siguiente pseudocódigo: función topological_sorting(m, L): T = <lista vacía> Mientras el nº de columnas de M > 1: N <- lista de minimales en M n <- seleccionar un minimal de la lista N nombre <- elemento n de la lista L Borrar la fila y columna n de la matriz M Insertar nombre en la lista T Eliminar nombre de la lista L nombre <- primer element de la lista L Insertar nombre en la lista T Devolver T Ejercicio 1 Implementa en Python el pseudocódigo anterior y comprueba que funciona obteniendo una ordenación total para la siguiente matriz que representa la relación de orden del diagrama de Hass de la figura 1. M = Matrix([[1,0,1,1,0,1,1], [1,1,1,1,0,1,1], [0,0,1,1,0,1,1], [0,0,0,1,0,0,0], [1,0,1,1,1,1,1], [0,0,0,0,0,1,0], [0,0,0,0,0,0,1]]) Ejercicio 2 Define la matriz correspondiente a la relación de orden dada por el siguiente diagrama de Hasse, y obtén un orden total utilizando el procedimiento proporcionado.
G D C F E A B Ejercicio 3 (Opcional) Programa un algoritmo de ordenación topológica que obtenga un orden total en sentido inverso sustituyendo el procedimiento minimal() por un procedimiento maximal() que devuelva la lista de maximales de la matriz correspondiente a una relación de orden.