Repaso de Matemáticas discretas Andrés Becerra Sandoval 23 de julio de 2007 1. Introducción La matemática discreta en contraposición a la continua (como el cálculo) estudia entidades que no varían indefinidamente, como si lo hacen los números reales. Sus temas son los números enteros, los conjuntos, los grafos, los lenguajes formales, entre otros. Esta matemática es fundamental para entender el funcionamiento de los computadores, que son digitales, discretos, y para analizar los algoritmos que ejecutamos en ellos. Antes de empezar con las técnicas de análisis y diseño de algoritmos es muy conveniente repasar varios temas que usaremos frecuentemente. 2. Los Números Naturales y Enteros Los números naturales N y los números enteros Z son muy útiles en programación, a menudo usamos varios hechos muy conocidos sobre ellos: n ∑ i = 1+2+3+4...+n = i=1 n(n+1) 2 n ∑ (2i − 1)2 = 12 + 22 + 32 . . . + (2i − 1) = n2 i=1 La primera formula fue obtenida por Gauss, dicen que cuando era niño era tan inquieto que su profesor le ponía a sumar muchos números, por ejemplo todos los números del 1 al 100: 1 + 2 + 3 + . . . + 100 1 Gauss descubrió que sumándolos dos veces en orden distinto: 1 + 2 + 3 . . . + 99 + 100 100 + 99 + 98 . . . + 2 + 1 101 + 101 + 101 . . . + 101 + 101 | {z } Obtenía 100 veces la suma vertical 101, esto es 10100, que, al dividir por dos1 , da la suma correcta de los primeros 100 números naturales, 5050. Si generalizamos éste ingenioso procedimiento de Gauss, llegamos a la formula: n ∑i= i=1 n(n + 1) 2 La segunda identidad fue descubierta por los griegos observando sucesiones de cuadrados mas grandes: b b 1 + 3 = 4 = 22 a b c c c b b c 1 + 3 + 5 = 9 = 32 a b c d d d d c c c d 1 + 3 + 5 + 7 = 16 = 42 b b c d a b c d Estas dos geniales intuiciones de Gauss y de los griegos no prueban las formulas mencionadas pero nos permiten comprender porque son ciertas. Comprendemos porque vemos una analogía gráfica con algo que ya conocemos (sumas verticales, o cuadrados), pero esto no es suficiente para probar las formulas. La matemática es más exigente, una prueba debe ser más formal, debe garantizar que para cualquier n la formula se cumple. Con los algoritmos tenemos la misma situación, la idea básica que nos permite comprender un algoritmo (alguna analogía gráfica, algún invariante) no nos permite probar que éste va a entregar las respuestas correctas para cualquier entrada. Para probar que éstas formulas funcionan correctamente podemos usar el principio de inducción matemática sobre los números naturales. 1 ¿Por que? 2 2.1. Principio de Inducción matemática Con la palabra principio de inducción matemática queremos expresar que es un hecho evidente, un punto de partida, un axioma que no necesita ser demostrado. La aritmética ha sido construida a partir de algunos axiomas básicos entre los cuales se encuentra el de inducción matemática. Recordemos que los formalismos matemáticos como la aritmética son modelos simbólicos autocontenidos (en la medida de lo posible) que parten de ciertos axiomas o enunciados que se asumen ciertos, y, por lo tanto, no hay necesidad de demostrar; éstos constituyen el punto de partida de todas las demostraciones. Si la afirmación es cierta para el número 1 Y, si asumiendo que la afirmación es cierta para un número k>1, se puede probar que también se cumple para el número k+1 Tenemos que la afirmación es cierta para cualquier n. Si usamos el cálculo de predicados podemos expresar este principio de una manera mas formal. Recuerde que en el cálculo de predicados ∀ significa “Para todo”, y que → representa la implicación lógica. P(1) es cierto ∀kP(k) → P(k + 1) Tenemos que: ∀nP(n) Por medio de éste principio podemos demostrar nuestras dos formulas. Para la de Gauss tenemos que: 1 P(1) ≡ ∑ i = 1 n(n+1) |n=1 = 1 2 k (Esto, es substituyendo n por 1) Si asumimos que P(k) ≡ ∑ i = 1 k ∑ i + (k + 1) = 1 k+1 ∑ i= 1 k(k+1) 2 k(k+1) 2 k(k+1) 2 es cierta + (k + 1) (sumamos k+1 a ambos lados) + (k + 1) (metemos el termino k+1 dentro de la sumatoria) 3 k+1 ∑ i= 1 k+1 ∑ i= 1 k+1 ∑ i= 1 k(k+1)+2(k+1) 2 (denominador común al lado derecho de la igualdad) k2 +k+2k+2 2 k2 +3k+2 2 = (k+1)(k+2) 2 Que equivale a afirmar que la formula se cum- ple para k+1 !! Como probamos P(1), y luego, asumiendo P(k) pudimos deducir P(k + 1), tenemos que ∀nP(n) La segunda se puede probar de manera similar (hágalo como ejercicio). 2.2. Segundo principio de inducción matemática El principio que viene a continuación puede resultar mas fácil de usar: P(1) es cierto ∀k[P(r), 1 ≤ r ≤ k → P(k + 1)] Tenemos que: ∀nP(n) Cuya interpretación es: si la proposición se cumple para todos los números desde 1 hasta un k preestablecido, y de allí podemos deducir que se cumple para un k mayor (k+1); entonces la proposición es cierta para todo n. Es importante resaltar que el primer principio de inducción matemática puede deducirse a partir de éste segundo principio (Hágalo!). También es importante recordar que los principios de inducción matemática pueden deducirse del principio del buen ordenamiento: Todo conjunto de números naturales no vacío tiene un elemento menor La importancia de éste segundo principio puede mostrarse intentando demostrar que existen infinitos números primos. O, en una forma mas conveniente para operar; que para n ≥ 2, n es un número primo o puede expresarse por un producto de números primos. Si intentamos el primer principio de inducción aquí tendremos algunos problemas: 4 Base: P(1) no se cumple!, tenemos que empezar desde P(2) Paso inductivo: P(k) ≡ k es primo o k = ab el producto de dos f actores ¿Como llegamos a P(k + 1)? Es muy difícil. Si usamos el segundo principio el problema se simplifica mucho. Partamos asumiendo P(k) ≡ ∀k[P(r), 1 ≤ r ≤ k] En este contexto significa todo número r entre 2 y k ó es primo o está compuesto por factores primos Pongamos la meta a la que queremos llegar: P(k + 1) ≡ ∀k[P(r), 1 ≤ r ≤ k + 1] Que en este contexto significa que todo número r entre 2 y k+1 ó es primo o está compuesto por factores primos Para llegar a esta meta pensemos en k+1. Este número puede ser primo, lo que haría a P(k + 1) cierta. Si no es primo lo podemos expresar como k + 1 = ab. Como P(k) se cumple, tenemos que a y b ó son primos o están compuestos por factores primos (porque 2 ≤ a ≤ k y 2 ≤ b ≤ k. Esto nos permite deducir que P(k + 1) es cierta (pienselo bien!). Una advertencia: para que k + 1 = ab nos permita deducir que 2 ≤ a ≤ k y 2 ≤ b ≤ k, tenemos que imponer la restricción de que 1 < a < k + 1 y 1 < b < k, pero esto es natural!; quiere decir que los factores a y b no pueden ser los valores triviales 1 y k+1. Con estos dos principios de inducción se pueden probar propiedades y formulas en los números naturales. Si el primer principio de inducción no se puede aplicar directamente (porque del caso k no se puede llegar al k+1 de una manera sencilla), se procede a usar el segundo principio. 2.3. Inducción Estructural El axioma de inducción matemática en la aritmética no es suficiente para probar todo lo que deseariamos—propiedades de grafos, árboles, programas, etc.—. Necesitamos una forma general, mas computacional, que se pueda particularizar para diferentes propósitos. Para lograr esto pensemos en que cualquier estructura de datos puede definirse de manera recursiva, por ejemplo: una lista ó es vacía, o es el resultado de añadir un elemento a una lista, un aŕbol o es vacío o es un nodo que tiene un conjunto de subarboles. Asi como los dos principios de inducción se pueden probar a partir del principio de buen ordenamiento que se presentó en 5 la sección anterior, en las estructuras definidas recursivamente tenemos siempre un ordenamiento parcial, con el que se puede probar el siguiente principio de inducción estructural: P(base) la proposición se cumple sobre los casos mínimos (base) de la estructura Si, asumiendo que P se cumple para todas las subestructuras de s, se puede probar que P se cumple para s. ∀t ∈ sub(s)P(t) → P(s) Tenemos que: ∀eP(e), la proposición se cumple para todas las estructuras Otra forma de verlo consiste en pensar en el “tamaño” de la estructura sobre la que hacemos la inducción: Si, asumiendo que la proposición se cumple para estructuras de un tamaño menor a k, podemos probar que se sigue cumpliendo para un tamaño mayor a k, entonces la proposición es correcta. Este principio se puede ilustrar demostrando que un árbol binario perfecto de h niveles tiene 2h − 1 nodos: Base: para el árbol vacío, 20 − 1 = 0 nodos, para un árbol con un solo nodo, 21 − 1 = 1 nodo. Si asumimos que todo subarbol de A cumple la proposición, entonces, para (Aizq , Ader ): 2hizq − 1 = nizq y 2hder − 1 = nder (con nizq y nder siendo los números de nodos). Ahora, como el árbol es perfecto, hizq = hder , lo que implica que nizq = nder . Por ser perfecto, el número de nodos de A es, n = nder + nizq + 1 = (2hizq − 1) + (2hder − 1) + 1 = 2 · 2hizq − 1 Como Aizq es el hijo de A tenemos que hizq = h − 1 Luego n = 2 · 2h−1 − 1 = 2h − 1 Para probar programas que operan sobre estructuras de datos es muy conveniente usar la inducción estructural. Por ejemplo, para invertir una lista: 6 I NVERTIR(m) if m = NIL then return NIL else return PEGAR(I NVERTIR(T(m)),H(m)) Donde PEGAR concatena dos listas. T retorna la cola de una lista (todos los elementos, excepto el primero), y H retorna una lista con el primer elemento. (Recuerde que éste tipo de listas está definido recursivamente: una lista ó es vacía, o es el resultado de añadir un elemento a una lista). Nuestra demostración de corrección por medio de inducción estructural sería: Base: INVERTIR(NIL) funciona correctamente. Paso inductivo: Si INVERTIR funciona correctamente para listas de tamaño menor o igual a k. Podemos inferir que cuando INVERTIR es llamado con una lista de tamaño k, el llamado I NVERTIR(T(m)) invierte correctamente a T(m), porque esta lista tendría un tamaño menor a k. Como este resultado se concatena al primer elemento de la lista, la función invertiría correctamente a una lista de tamaño k. Luego I NVERTIR es correcta (asumiendo que pegar funciona correctamente). Como ejercicio, escriba la función pegar, usando a CONS(a,l), una función que dado un elemento a y una lista l, retorna una lista mas grande con a como primer elemento y continuando con los elementos de l. Además, pruebe la corrección de pegar por inducción estructural! 3. Invariantes para probar algoritmos iterativos Cuando se escribe un ciclo para calcular la sumatoria de los elementos de un arreglo no vemos facilmente como usar la inducción matemática o la estructural que ya hemos aprendido: SUMATORIA (A) 1 2 3 4 5 suma ← 0 n ← length[A] for i ← 1 to n do suma ← A[i] return suma 7 Aquí, la estrategia para demostrar la corrección del ciclo es distinta y se basa en: Demostrar que el algoritmo termina eventualmente. Encontrar un invariante. Esto es, una relación entre las variables que controlan el ciclo y el resultado que arroja que no varía a través de las iteraciones. Revisar el valor que toman las variables en la ultima iteración y el invariante. Estos dos hechos deben garantizar el resultado que se espera del ciclo. Para el sencillo ciclo anterior podemos proceder así: Detención: el ciclo For va de 1 a n (el número de elementos del arreglo), asi que eventualmente se detiene para cualquier arreglo A i Invariante: sum = ∑ A[ j] (Revise si esto se cumple antes y después de cada j=1 iteración!) En la última iteración el contador que controla el ciclo, i=n. Asi que el invan riante al final es: sum = ∑ A[ j]. Lo que garantiza que el ciclo es correcto! j=1 Encontrar invariantes no es una tarea fácil, de hecho, no es una tarea algorítmica. No existe un algoritmo general que dado cualquier ciclo while produzca un invariante. Pero si usted está escribiendo un algoritmo a partir de una idea ó analogía, como las intuciones de los griegos y la de Gauss que comentamos, encontrar un invariante se reducirá a expresar su idea matemáticamente. En la mayoría de las situaciones tenemos que la entrada debe cumplir ciertas restricciónes, esto es la precondición. Las restricciones que deben cumplir después de la ejecución del ciclo las denominamos postcondición. En este caso, cuando realizamos una demostración por medio de un invariante la corrección será parcial, algo así como: si los datos cumplen la precondición la ejecución del ciclo garantizará la postcondición. 4. Conteo 4.1. El principio de multiplicación ¿Cuantos números de placa distintos pueden haber en colombia? 8 30 30 30 10 10 10 Tenemos 30 posibilidades para la primera letra, 30 posibilidades para la segunda letra, 30 posibilidades para la tercera letra. 10 posibilidades para el primer numero, 10 para el segundo y 10 para el último. Esto es 30 × 30 × 30 × 10 × 10 × 10. Lo que se puede resumir: Si hay n1 posibilidades para el primer evento, n2 para el segundo,..., nm para el evento m, entonces hay n1 × n2 . . . × nm posibilidades para la secuencia de los m eventos. Esto se puede aplicar a programas, por ejemplo, ¿cuantas multiplicaciones se hacen en el siguiente ciclo? MATRIX-MULTIPLY(A, B) 1 if columns[A] 6= rows[B] 2 then error "incompatible dimensions" 3 else 4 for i ← 1 to rows[A] 5 do for j ← 1 to columns[B] 6 do C[i, j] ← 0 7 for k ← 1 to columns[A] 8 do C[i, j] ← C[i, j] + A[i, k] × B[k, j] 9 return C En este caso se puede afirmar que el primer ciclo For hace rows[A] iteraciones, el segundo For hace columns[B] iteraciones y el tercero hace columns[A] iteraciones. Luego, la sentencia que multiplica un elemento de A y otro de B en la línea 8 se ejecuta rows[A] × columns[B] × columns[A] veces! 4.1.1. Permutaciones Si las posibilidades para los eventos van descendiendo de uno en uno llegamos a las permutaciones. Por ejemplo, ¿cuantas claves distintas (que no repitan dígitos) puede soportar un cajero electrónico? La respuesta es 10 · 9 · 8 · 7 porque puedo tomar los diez dígitos para el primer número, pero para el segundo dígito debo considerar solo 9 dígitos para no repetir 9 el primero, 8 para el tercero y 7 para el último. Si las claves tuvieran 10 dígitos el número sería 10 · 9 · 8 · 7 · 6 · 5 · 4 · 3 · 2 · 1, que es 10!. Es decir, el factorial es un caso particular del número de permutaciones en el que el número de posibilidades (dígitos en nuestro ejemplo) es igual al número de eventos (el tamaño de la clave en nuestro ejemplo). Asi que los dos parámetros básicos para una permutación son: número de eventos (r) número de posibilidades para cada evento (n) [normalmente se asume que r ≤ n] Si generalizamos el conteo anterior, obtenemos la formula: P(n, r) = n(n − 1)(n − 2) . . . (n − r + 1) Aunque es mas general usar esta formula equivalente (chequeélo!): P(n, r) =n Pr = n! (n − r!) Recordando que 0! se define como 1, observe que que P(n, n) = n!. Además P(n, 1) = n En general P(n, r) significa el número de ordenes posibles para n objetos o eventos posibles , tomando r cada vez, y prohibiendo repeticiones. El siguiente algoritmo calcula el número de permutaciones (verifiquelo!): PERM (n, r) m←1 for i ← 1 to r do m ← m × n n ← n−1 return m 4.1.2. Combinaciones En las combinaciones estamos interesados en contar número de ordenes posibles para n objetos, tomando r cada vez (sin prohibir repeticiones). Por ejemplo, 10 ¿cuantas manos de poker pueden obtenerse de un mazo de 52 cartas? (cada mano tiene 5 cartas). Una mano tiene 5 cartas, pero no importa el orden, el jugador puede organizarlas como desee, asi que 2-3-4-8-7 es la misma mano que 8-7-2-3-4. Observe que el número de permutaciones de 52 cartas, tomando 5 a la vez no nos sirve, porque discriminaría diferencias entre manos que, para los jugadores, son la misma. ¡El número de permutaciones es mucho mas grande que el número de combinaciones!. El número de combinaciones puede calcularse con el principio de la multiplicación de la siguiente forma. Si denotamos al número de combinaciones C(n, r), para cada combinación existen r! formas de permutar los r objetos escogidos (por ejemplo, 5 cartas pueden permutarse de 5! formas distintas). Entonces, el número de permutaciones, en el que si importa el orden puede dividirse por r! para calcular el número de combinaciones: P(n, r) n n! = C(n, r) = = r r! r!(n − r)! 4.2. El principio de la adición Similarmente, si tenemos eventos disjuntos: Si hay n1 posibilidades para el primer evento, n2 para el segundo,..., nm para el evento m, entonces hay n1 + n2 . . . + nm posibilidades para la secuencia de los m eventos. Esto se puede aplicar en programas también, ¿dentro de la siguiente función cuantas veces se hace un llamado recursivo a R ECURSIVE -B INARY-S EARCH)? R ECURSIVE -B INARY-S EARCH(A, v, low, high) if low > high then return NIL mid ← b(low + high)/2c if v = A[mid] then return mid if v > A[mid] then return R ECURSIVE -B INARY-S EARCH(A,v,mid+1,high) else return R ECURSIVE -B INARY-S EARCH(A,v,low,mid-1) 11 La respuesta no es dos veces. Solo se hace un llamado recursivo, ya que los dos llamados son eventos exclusivos, están controlados por la revisión que hace el condicional entre v y A[mid]. Bueno, para ser precisos, en algunos casos la respuesta también puede ser cero, cuando v=A[mid] la función retorna inmediatamente un valor y no hace recursión. 4.3. El principio del palomar (pigeonhole principle) Si mas de k palomas se acomodan en k casillas de un palomar, al menos una casilla tendrá dos palomas Aunque este principio es muy intuitivo, también es muy poderoso. Por ejemplo, puede ayudar a resolver las siguientes preguntas: ¿Cuantas personas tienen que haber en un salón para garantizar que al menos dos tengan apellidos con la misma inicial? ¿Cuantas veces hay que tirar un dado consecutivamente para obtener el mismo valor dos veces? ¿Que pasa en una tabla hash de tamaño máximo k, cuando insertamos mas de k datos? 5. Algebra y conjuntos Todas las estructuras algebraicas que usamos en computación descansan sobre la noción de conjunto. Es conveniente recordar algunos conceptos. Relación: Una relación sobre varios conjuntos es un subconjunto del producto cartesiano de estos Relación binaria: Entre A y B, cualquier subconjunto de A × B. Relación de equivalencia: Cuando en una relación binaria A = B decimos que el conjunto A (ó el B) tiene una relación sobre si mismo. Si la relación binaria es reflexiva, simétrica y transitiva, decimos que es una relación de equivalencia. 12 Conjunto parcialmente ordenado: Un conjunto que tiene una relación de equivalencia sobre si mismo. Se es totalmente ordenado, entonces existe el elemento mínimo y el elemento máximo. Clase de equivalencia: Una de las particiones que tiene un conjunto parcialmente ordenado con respecto a una relación de equivalencia. Función: Es una relación binaria entre A y B con una condición especial. Como es subconjunto de A × B, es un conjunto de parejas (a, b) con a ∈ A y b ∈ B. La condición que deben cumplir las funciones es que cada elemento de A aparezca exactamente una vez como primer elemento de las parejas (a, b). Se simbolizan: f : A → B, y decimos que A es el dominio y que B es el codominio. Al conjunto de elementos de B que aparecen en la relación se les denomina rango. Función sobreyectiva: Una función cuyo rango es igual al codominio. Función inyectiva: Una función sobreyectiva entre A y B en la que ningún elemento de B es la imagen de dos elementos distintos de A. Función biyectiva: Una función sobreyectiva e inyectiva. Algebra booleana: Un conjunto A con dos funciones binarias +, · y una función unaria ¬ en la que hay dos elementos distintos 0 y 1. Se deben cumplir ciertas propiedades conmutativas, asociativas, distributivas, de identidad y de complemento. Grupo: Un conjunto A con una función binaria · que es asociativa y que cumple dos propiedades: existe un elemento identidad (o nulo) y cada elemento tiene un inverso con respecto a ·. Si la operación binaria · es conmutativa el grupo es conmutativo. Cuerpo: Un conjunto A con funciones abstractas suma, resta, multiplicación y división, que se comportan como éstas funciones sobre los números reales. Espacio vectorial: Un conjunto de entidades denominadas vectores que tiene dos funciones binarias sobre si mismo: la suma y el producto vectoriales. Estas operaciones deben cumplir varios axiomas. Los espacios vectoriales se construyen sobre un cuerpo de entidades, denominadas escalares. Por ejemplo, los polinomios forman un espacio vectorial con respecto a la suma y multiplicación polinomial; los escalares son los números reales. 13 6. Estadística y Probabilidad Algunos conceptos de estadística que utilizaremos: Media: En una muestra de datos x1 , · · · , xn la media o promedio es: 1 n 1 x̄ = ∑ xi = (x1 + · · · + xn ) n i=1 n Media ponderada: En una muestra de datos x1 , · · · , xn que tienen asignados unos pesos w1 , · · · , wn la media o promedio ponderado es: n ∑ xi wi x̄ = i=1 n ∑ wi i=1 recuerde que así se obtiene su promedio semestral. Orden estadístico: En una muestra de datos x1 , · · · , xn que están en orden ascendente, el 1er orden estadístico es x1 , el 2do es x2 ; y, en general, el orden estadístico k es xk . Algunos conceptos de probabilidad que usaremos: Variable aleatoria: variable que representa las observaciones que podemos hacer a un evento no determinístico. Puede ser discreta o continua. Valor esperado: Para una variable aleatoria discreta el valor esperado está dado por E(X) = ∑ pi xi i donde los pi son las probabilidades de los eventos con resultado xi . El valor esperado cumple las siguientes propiedades: E(X + c) = E(X) + c E(X +Y ) = E(X) + E(Y ) E(aX) = aE(X) donde a, c son constantes y X,Y son variables aleatorias. 14 7. Conclusiones Hemos hecho un muy breve repaso sobre varios temas de matemáticas discretas que usaremos bastante para analizar y diseñar algoritmos. Por brevedad hay muy pocos ejemplos y las definiciones son muy sucintas, si ha encontrado algo que no comprende o recuerde, tomése el tiempo de hacer la búsqueda bibliográfica en un libro de matemáticas discretas, y, para el concepto de espacio vectorial, en un libro de algebra lineal. No menosprecie la importancia de recordar cada uno de los conceptos repasados, todos serán usados eventualmente en el curso. 15