pedro cornejo - inteligencia artificial - introducción a la teoría de conjuntos y teoría de...

25
Centro de Enseñanza Técnica Industrial. Inteligencia Artificial 1 Pedro Arturo Cornejo Torres INTELIGENICA ARTIFICIAL CURSO DE TEORIA DE CONJUNTOS Y TEORIA DE GRAFOS AUTOR: M.C. Pedro Arturo Cornejo Torres

Upload: cristobal-flores-iniguez

Post on 26-Oct-2015

196 views

Category:

Documents


7 download

TRANSCRIPT

Centro de Enseñanza Técnica Industrial. Inteligencia Artificial

1 Pedro Arturo Cornejo Torres

INTELIGENICA ARTIFICIAL

CURSO DE TEORIA DE CONJUNTOS Y TEORIA DE GRAFOS

AUTOR:

M.C. Pedro Arturo Cornejo Torres

Centro de Enseñanza Técnica Industrial. Inteligencia Artificial

2 Pedro Arturo Cornejo Torres

Unidad 1. Fundamentos de Teoría de Conjuntos.

1.1 Teoría de conjuntos. ¿Por qué es necesario conocer la teoría de conjuntos?

Una razón muy importante del estudio de la teoría de conjuntos, lo es el hecho de establecer un lenguaje común y formal que nos permita intercambiar conceptos sobre los conjuntos. ¿Es necesario este lenguaje?, la respuesta es si. El uso de un lenguaje formal permite trasmitir, documentar y divulgar conocimiento libre de malas interpretaciones y ambigüedades. La teoría de conjuntos, nos propone el conocimiento necesario para tratar con objetos y conjuntos de manera formal. También nos ofrece un lenguaje formal expresar conjuntos y como operar con ellos y sus elementos. En inteligencia artificial, los algoritmos son fáciles de describir en el lenguaje de conjuntos, independizando los conceptos de la plataforma en particular que se utilice, como lo es un lenguaje de programación.

Conjuntos y subconjuntos.

Tenemos cierta “noción intuitiva” en el sentido de que un conjunto debe ser una

colección bien definida de objetos. Estos objetos se llaman elementos y se dice que son miembros del conjunto.

El adjetivo bien definido implica que para cualquier elemento que consideremos,

podemos determinar si está en el conjunto observado. En consecuencia, evitaremos trabajar con conjuntos que dependan de las opiniones, como el conjunto de los mejores lanzadores de las ligas mayores de béisbol en la década de 1980.

Notación de los conjuntos.

Utilizaremos las letras mayúsculas, A, B, C … para representar los conjuntos y

letras minúsculas para representar los elementos. Para un conjunto A, escribiremos x ∈ A si x es elemento de A; y y ∉ A indicando que y no es elemento de A.

Ejemplo 1: Un conjunto puede designarse enumerando sus elementos dentro de llaves {}.

Por ejemplo si A es el conjunto formado por los primeros cinco números enteros positivos, escribiremos A={1 , 2 ,3 ,4 , 5}.

Utilizando la definición del conjunto A. Podemos decir que se cumple de verdad

lo siguiente: 2 ∈ A y 6 ∉ A.

Otra notación común para el conjunto A es A={ x | x es un entero y 1 ≤ x ≤ 5}, en este caso la línea vertical | que aparece dentro de las llaves se lee como “tal que”. Los símbolos {x | …} se leen como “el conjunto de todos los x tales que …”. Las

Centro de Enseñanza Técnica Industrial. Inteligencia Artificial

3 Pedro Arturo Cornejo Torres

propiedades que van después de | nos ayudan a determinar los elementos del conjuntos descrito.

Hay que tener cuidado con las propiedades que se establezcan para el conjunto. La notación {x | 1 ≤ x ≤ 5} no es una descripción adecuada del conjunto A, a menos que hayamos acordado previamente que los elementos considerados son enteros. Recuerde que existen diferentes conjuntos de números como los reales y los complejos.

Ejemplo 2: Si U = {1, 2, 3, … } es el conjunto de los enteros positivos, sean

a) A = {2 ,4 ,6 ,8 ,10, …} = {x ∈ U | x es par} b) B= {5, 10, 15, 20, 25, 30, 35, 40} = {x ∈ U | x=5k, 1 ≤ k ≤ 3}

Para el caso del inciso a) tenemos el conjunto infinito en su forma enumerada, y luego en su forma definida. En este caso el conjunto de los números pares es un conjunto bastante grande como para enumerar cada uno de estos números. La forma definida nos permite de forma compacta e igual de precisa referirnos a los números pares. En el caso del inciso b) tenermos de igualmanera al inciso anterior, un conjunto enumerado de ocho elementos, y luego tenemos el conjunto definido. En este caso, el objeto x queda restringido por ser necesario que pertenezca al conjunto de los números enteros positivos y por quedar en términos de un monomio 5k. En este caso el dominio de k es de 1 hasta incluir 3. Entre el uno y el tres, como no definimos el conjunto de k, pueden exisitir un conjunto infinito de valores. No importa, en este caso, ya que x debe ser un entero positivo.

Ahora bien, ya existe un fundamento suficiente para comenzar nuestro estudio

sobre los conjuntos. En las siguientes tareas y proyectos analizaremos otros tipos bien conocidos de conjuntos de objetos, como el conjunto de los números reales, el conjunto de los números naturales, etc. También estudiaremos las propiedades y las operaciones de los conjuntos. Nota:

Los conjuntos que hasta ahora hemos estudiado utilizan objetos que son números. Pero en realidad, estos objetos pueden ser cadenas, colores, frutas, tablas de bases de datos, registros, nodos, aristas, etc. En fin, una gran diversidad de tipos de objetos con los que podemos tratar con la teoría de conjuntos. Otro punto más a favor de esta teoría, que motiva su estudio. REFERENCIAS: Para mayor información sobre este tema, puede consultar cualquier libro de matemáticas discretas, por lo general, en estos títulos se discute este tema en las primeras secciones. En la Wiki: http://es.wikipedia.org/wiki/Teor%C3%ADa_de_conjuntos

Centro de Enseñanza Técnica Industrial. Inteligencia Artificial

4 Pedro Arturo Cornejo Torres

Cardinal o tamaño de un conjunto.

El cardinal es un operador que calcula el total de elementos sobre cualquier conjunto. Para el caso de los conjuntos finitos, este valor pertenece al conjunto de los enteros positivos, cuando se trata de un conjunto infinito, simplemente se especifica que su tamaño es ∞. El operador cardinal sobre un conjunto se escribe mediante dos barras horizontales |A| que encierran al símbolo del conjunto. Ejemplo 3

Sea A={1, 2, 3, 4 …} un conjunto infinito. En este caso |A| = ∞. Sea B={1, 2, 16, 32} un conjunto finito. En este caso |B| = 4.

Conjunto Universal.

Decimos que estamos especificando un Universo o Universo de Discurso, que por lo general se denota con U, para definir a donde pertenecen todos los elementos de los demás conjuntos que utilizaremos. Es decir, el universo o universo de discurso es quien contiene a todos elementos.

Subconjunto.

Si C y D son conjuntos del universo U, decimos que C es un subconjunto de D y

escribimos C ⊆ D, si cada elemento de C es un elemento de D. Si además, D contiene un elemento que no esta en C, entonces C es un subconjunto propio de D y se denota como C ⊂ D.

Definición formal de subconjunto:

Observe que para cualesquiera conjuntos C y D del universo U, si C ⊆ D,

entonces ∀x[x ∈ C ⇒ x ∈ D], y si C ⊆ D entonces ∀x[x ∈ C ⇒ x ∈ D].

Aquí el cuantificador universal sobre x, ∀x indica que debemos considerar cada

elemento x del universo dado U. Más detalles acerca los operadores lógicos involucrados se discutirán en el repaso de lógica. Veremos que la lógica y los conjuntos están fuertemente relacionados entre si.

Ejemplo 4. Para el universo U = {1, 2, 3, 4, 5} consideremos el conjunto A={1, 2}. Si B={x |

x2 ∈ U}, entonces los miembros de B son 1, 2. En este caso, A y B contienen los mismos elementos (y ninguno más), lo cual nos lleva a pensar que los conjuntos A y B son iguales.

Sin embargo, también es cierto que A ⊆ B y B ⊆ A. Esto nos conduce al concepto de igualdad de conjuntos.

Centro de Enseñanza Técnica Industrial. Inteligencia Artificial

5 Pedro Arturo Cornejo Torres

Igualdad de conjuntos

Para un universo dado U, los conjuntos C y D (tomados del universo U) son iguales, y esto se escribe C = D, cuando C ⊆ D y D ⊆ C. A partir de las ideas de igualdad entre conjuntos, vemos que el orden o la repetición no son significativos para un conjunto en general. Así, tenemos, por ejemplo {1, 2, 3} = {3, 2, 1} = {2, 2, 1, 3} = {1, 2, 1, 3, 1}. Ejemplo 5

Sea U = {1, 2, 3, 4, 5, 6, x, y, {1, 2}, {1 ,2 ,3}, {1, 2, 3, 4}} (donde x y y son letras minúsculas del alfabeto y no representan nada más, al igual que, 3, 5, o {1,2}). Entonces | U | = 11. a) Si A = {1, 2, 3, 4}, entonces |A| = 4 y tenemos que lo siguiente es verdadero

1) A ⊆ U 2) A ⊂ U 3) A ∈ U 4) {A} ⊆ U

5) {A} ⊂ U 6) {A} ∉ U b) Ahora sea B = {5, 6, x, y, A} = {5, 6, x, y, {1, 2, 3, 4}} entonces |B|=5, no 8 y tenemos que lo siguiente también es verdadero:

1) A ∈ B 2) A ⊆ B 3) A ⊂ B pero,

4) A ∉ B 5) No es cierto que A ⊆ B y 6) tampoco lo es A ⊂ B. Referencias: Matemáticas Discreta y Combinatoria - Una introducción con aplicaciones. Ralph P. Grimaldi. Ed. Pearson- Prentice Hall . Capitulo 3. Páginas Próxima tarea. Estén al pendiente.

Centro de Enseñanza Técnica Industrial. Inteligencia Artificial

6 Pedro Arturo Cornejo Torres

El conjunto vacío

El conjunto vacío o nulo, es el único conjunto que no contiene elementos. Se denota como ∅ o {}. Observemos que | ∅ | = 0, pero {0} ≠ ∅. Así mismo, ∅ ≠ {∅}, ya que {∅} es el conjunto con un elemento, a saber, el conjunto vacío.

Notación de algunos conjuntos comunes de números

Existen algunos conjuntos de números que serán necesarios a través de este curso de inteligencia artificial. En consecuencia, cerraremos esta sección asignándoles los siguientes nombres. a) Z = El conjunto de los enteros = {0, 1, -1, 2, -2, 3, -3, …} b) N = El conjunto de los números naturales = {1, 2 , 3, 4, 5, 6, 7,…} c) N0 = El conjunto de los números naturales incluido el cero = N ∪ {0}

d) Q = El conjunto de los números racionales ={a/b | a, b ∈ Z, b ≠ 0} e) R = El conjunto de los números reales.

f) C = El conjunto de los números complejos = {x + yi | x,y ∈ R, i2 = -1}

Intervalo abierto, cerrado y semiabierto

Para los números reales a,b con a < b,

[a,b] = { x ∈ R | a ≤ x ≤ b }, es un intervalo cerrado (a,b) = { x ∈ R | a < x < b }, es un intervalo abierto [a,b) = { x ∈ R | a ≤ x < b }, es un intervalo semiabierto por la derecha

(a,b] = { x ∈ R | a < x ≤ b }, es un intervalo semiabierto por la izquierda Nota: Como puede observarse, la notación de conjuntos reduce muchísimas

explicaciones acerca de los conjuntos referidos.

Tarea

Los siguientes problemas fueron extraidos del libro de Grimaldi, capítulo 3. Resuelva los siguientes problemas. ACLARACION: Por favor, analice la pregunta, NO TODAS SON PREGUNTAS DE OPCIÓN MULTIPLE. 1.- ¿Cuáles de los siguientes conjuntos son iguales? a) {1, 2, 3} b) {3 ,2 ,1 , 3} c) {3, 2, 1, 3} d) {1, 2, 2, 3} 2.- Sea A={1, {1}, 2}. ¿Cuáles de las siguientes proposiciones son verdaderas?

Centro de Enseñanza Técnica Industrial. Inteligencia Artificial

7 Pedro Arturo Cornejo Torres

a) 1 ∈ A b) {1} ∈ A c) {1} ⊆ A d) {{1}} ∈ A e) {2} ∈ A f) {2} ⊆ A g) {{2}} ⊆ A h) {{2}} ⊂ A 3.- Para A= {1, 2, {2}}, ¿Cuáles de las siguientes proposiciones del ejercicio 2 son verdaderas? 4.- Determine todos los elementos de cada uno de los siguientes conjuntos.

a) {1+ (-1)n | n ∈ N} b) {n + (1/n) | n ∈ {1, 2, 3, 5, 7}} c) { n3 + n2 | n ∈ {0, 1, 2, 3, 4}}

d) {1/(n2+n) | n es un entero positivo impar y n ≤ 11} 5.- ¿Cuáles de los siguientes conjuntos son no vacíos?

a) {x | x ∈ N, 2x+7=3} b) {x | x ∈ Q, x2+4=6} c) {x | x ∈ Z, 3x+5=9} d) {x | x ∈ C, x2+3x+3} Nota: utilice las definiciones de conjuntos numéricos vistos en este documento.

Centro de Enseñanza Técnica Industrial. Inteligencia Artificial

8 Pedro Arturo Cornejo Torres

Relaciones y Funciones. En esta sección extenderemos la teoría de conjuntos que se estudió en la sección anterior para incluir los conceptos de relación y función. Antes de comenzar con la definición de función, es necesario introducir algunos conceptos previos, entre estos, el importante producto cartesiano. Los productos cartesianos nos permitirán construir una asociación entre dos objetos.

Producto cartesiano

Para los conjuntos A, B ⊆ U, el producto cartesiano de A y B se denota con A × B y es

igual a {(a, b) | a ∈ A, b ∈ B}.

Decimos que los elementos de A × B son pares ordenados. Para (a, b), (c, d) ∈ A tenemos que (a, b) = (c, d) si y sólo si a=c y b=d. Ejemplo 6

Sean U = {1, 2, 3, … , 7}, A ={2, 3, 4}, B={4, 5}. Entonces

a) A × B = { (2, 4), (2, 5), (3, 4), (3, 5), (4 ,4), (4,5) } b) B × A = { (4, 2), (5, 2), (4, 3), (5, 3), (4, 4), (5, 4) } c) B2= B × B = {(4,4), (4, 5), (5, 4), (5,5)}

d) B3= B × B × B = { (a, b, c) | a, b, c ∈ B}

Centro de Enseñanza Técnica Industrial. Inteligencia Artificial

9 Pedro Arturo Cornejo Torres

Función

Para los conjuntos no vacíos A y B, una función o aplicación, f de A en B, se denota con f:A → B, es una relación de A en B en la que cada elemento de A aparece exactamente una vez en como la primera componente de un par ordenado en la relación.

Con frecuencia escribimos f(a) = b cuando (a, b) es un par ordenado en la

función f. Si (a, b) ∈ f, entonces b se conoce como la imagen de a mediante f, mientras

que a es una preimagen de b. Además, la definición sugiere que f es un método para

asociar a cada a ∈ A una única b ∈ B; denotamos este proceso como f(a) = b ó b = f(a). Ejercicios de tarea:

Utilizando alguna plataforma de programación, impleméntese un programa que

calcule las siguientes operaciones entre conjuntos. Sea A={-10, -1, 0, 1, 2, 3, 4, 5, 6, 15, 18, 21, 32} y B={-10,-2, -1, 0, 1, 2, 4, 5, 6,

7, 12, 31, 32} dos conjuntos finitos de números. Calcular mediante computador las siguientes operaciones sobre los conjuntos A

y B: a) Union b) Intersección c) Diferencia d) Diferencia Simétrica y e) Producto Cartesiano. Fecha de revisión y sólo revisión de trabajos terminados: Martes 7 de octubre de 2008.

Centro de Enseñanza Técnica Industrial. Inteligencia Artificial

10 Pedro Arturo Cornejo Torres

Introducción a la teoría de grafos. En este apartado estudiaremos algunas definiciones preeliminares muy importantes para tratar con las estructuras de grafos. Los conceptos referentes a los grafos resultan ser muy intuitivos y fáciles de comprender. A partir de los conceptos de la teoría de conjuntos que ya ha aprendido, la formalización de esos conceptos resultará sencilla.

Un grafo, presentado de manera intuitiva, es un dibujo que permite expresar la relación entre los elementos de un conjunto dado. Ejemplos particulares de estos grafos son, los árboles genealógicos, árboles jerárquicos, mapas de alguna cuidad, un diagrama de flujo, un diagrama de bases de datos relacional, el esquemático de un circuito, etc. Es por esos tipos de aplicaciones que resulta importante contar con las herrmientas cognitivas necesarias sobre los grafos para resolver problemas de manera eficaz y eficiente si es posible.

De la introducción dada en el párrafo anterior, Usted se habrá dado cuenta que para conformar un grafo son necesarios dos conceptos: Un conjunto de objetos y una relación. El conjunto define que objetos de algún universo U participan en el grafo, mientras que una relación R establece las condiciones necesarias sobre las cuales los objetos se asocian.

Relaciones

En secciones anteriores, estudiamos el concepto básico de función, en donde a cada elemento del dominio (preimagen) le corresponde uno y sólo un elemento del codominio (imagen). Las relaciones son menos estrictas que las funciones, permitiendo que para algún elemento del dominio, este se encuentre relacionado con varios elementos del codominio. Bajo esta situación, las funciones resultan ser un caso particular de relaciones, es decir, las funciones son también relaciones. El recíproco todas las relaciones son funciones es inválido.

En la teoría de grafos utilizaremos relaciones binarias, es decir, asociaciones entre pares de objetos de dos conjuntos (posiblemente entre elementos de uno mismo).

Una relación binaria R, en los conjuntos A y B es un subconjunto del producto cartesiano entre A y B. R ⊆ A × B. Ejemplo 7

Sean U = {1, 2, 3, … , 7}, A ={2, 3, 4}, B={4, 5} (Del ejemplo 6). Entonces

1. R1 = { (2, 4), (2, 5), (3, 5), (4 ,4)} ⊆ A × B 2. R2 = { (4, 4), (5, 4) } ⊆ B × A 3. R3 = {(4,4), (5,5)} ⊆ B × B Ahora que entendemos que son las relaciones binarias, podemos formalmente definir un grafo.

Centro de Enseñanza Técnica Industrial. Inteligencia Artificial

11 Pedro Arturo Cornejo Torres

Grafo

Un grafo es un par G=(V, E) de conjuntos que satisfacen E ⊆ V × V, en donde los elementos del conjunto V son nodos o vértices (o puntos o círculos) y E es el conjunto de aristas (o líneas). Ejemplo 8

Este grafo corresponde a los conjuntos siguientes:

V={1, 2, 3, 4, 5, 6} E={(1, 2), (2, 1), (1, 5), (5, 1),(2, 3), (3, 2), (2, 5), (5, 2), (3, 4), (4, 3), (4, 5), (5, 4), (4, 6), (6, 4)} Este grafo, es un grafo no dirigido, obsérvese los pares que definen en la relación E. Recuerde la definición de par ordenado y con ello se confirma, por ejemplo que (1, 2) ≠ (2, 1).

Implementación de grafos en C++ (Sugerencias)

Haciendo uso de STL, sobrecarga de operadores y clases, es posible construir software listo para procesar estructuras de grafos como la que se estudió en la sección anterior. Resulta importante tener claro cómo los grafos pueden implementarse para resolver los futuros problemas que estudiaremos en este curso. A continuación se presenta el conjunto de clases para el soporte de programación de Grafos en C++.

Centro de Enseñanza Técnica Industrial. Inteligencia Artificial

12 Pedro Arturo Cornejo Torres

Comenzaremos definiendo el conjunto más básico de un grafo: el vértice. Para ello definiremos la siguiente clase C++. Estudiemos los comentarios para conocer entender el comportamiento de ésta clase de objetos. Vertex.h #ifndef _C_VERTEX

#define _C_VERTEX

class CVertex

{

friend class CGraphManager;

private:

// ID del vértice: Un Número Entero (Read Only) (32 bits)

int m_nID;

public:

//Agregar aquí más datos según la aplicación

//Fin Agregar

protected:

//Constructor del vértice

CVertex(int nID);

//Destructor

virtual ~CVertex(void);

public:

/* Métodos */

//Leer la llave del vértice

int Key();

public:

//Agregar aquí más métodos según la aplicación

//Fin Agregar

};

#endif

Vertex.cpp #include "StdAfx.h"

#include "Vertex.h"

CVertex::CVertex(int nID)

{

//Este constructor es invocado solamente por CGraphManager

m_nID=nID;

//Agregar aquí más inicializaciones simples

}

CVertex::~CVertex(void)

{

//Este destructor es invocado solamente por CGraphManager

//Agregar aquí más código de limpieza

}

int CVertex::Key()

{

return m_nID;

}

Centro de Enseñanza Técnica Industrial. Inteligencia Artificial

13 Pedro Arturo Cornejo Torres

Como puede observarse, es conveniente hacer uso de llaves (keys) para referirnos eficientemente a un vértice. Por lo general, los vértices son portadores de información muy importantes para ciertos algoritmos que acceden a esa información.

La técnica de llaves nos permite manejar de manera única a los vértices, facilitando muchas de las operaciones entre conjuntos.

Las aristas no son la excepción, también utilizaremos la técnica de llaves para

referirnos de manera única a determinada arista en el grafo. Para el caso de los vértices resulta conveniente utilizar su identificador como llave, pero para el caso de las aristas, realizaremos un tratamiento especial. Las aristas pueden modelarse como un par ordenado de identificadores de vértices. Ahora bien, cada identificador de vértice está conformado por un número de 32 bits. Aprovechando las capacidades de las computadoras modernas, combinaremos ambos identificadores de vértice (v1.Key() y v2.Key()) en un solo identificador de 64 bits. La mezcla conforma otro número único que implica la relación entre dos vértices. Haciendo uso de compiladores modernos, esto no es un problema. Actualmente los compiladores cuentan con tipos nativos de 64, 128 y 256 bits.

Para combinar estos valores, una arista es la combinación entre una estructura de

par ordenado y un número de 64 bits. En C y C++ este tipo de organizaciones de memoria son posibles y de manera eficiente, por lo que el trabajo de combinar resulta instantáneo acceder al identificador de la arista o a cualquiera de los dos identificadores de vértices que conforman la arista.

Véamos la siguiente clase C++ y analicemos los comentarios. Edge.h

#ifndef _C_EDGE

#define _C_EDGE

class CEdge

{

friend class CGraphManager;

private:

//Unión para compartir la memoria entre el par ordenado y la

llave de la arista

union

{

//Par ordenado para VxV (m_nIDFirst,m_nIDSecond)

struct

{

//Primer vértice (Read only) (32 bits)

int m_nIDFirst;

//Segundo vértice (Read only) (32 bits)

int m_nIDSecond;

};

long long m_llID; //Llave de la arista (Read only) (64

bits)

}; //(Tamaño total 64 bits)

public:

//Leer Llave del primer vértice

int First();

//Leer Llave del segundo vértice

int Second();

Centro de Enseñanza Técnica Industrial. Inteligencia Artificial

14 Pedro Arturo Cornejo Torres

//Leer Llave de la arista

long long Key();

protected:

//Constructor

CEdge(int nIDFirst,int nIDSecond);

//Destructor

~CEdge(void);

};

#endif

Edge.cpp

#include "StdAfx.h"

#include "Edge.h"

CEdge::CEdge(int nIDFirst,int nIDSecond)

{

//Este constructor es invocado solamente por CGraphManager

m_nIDFirst=nIDFirst;

m_nIDSecond=nIDSecond;

//Agregar aquí más inicializaciones simples

}

CEdge::~CEdge(void)

{

//Este destructor es invocado solamente por CGraphManager

//Agregar aquí más código de limpieza

}

int CEdge::First()

{

return m_nIDFirst;

}

int CEdge::Second()

{

return m_nIDSecond;

}

long long CEdge::Key()

{

return m_llID;

}

Se estará preguntando, ¿Por qué los identificadores de vértice, el par ordenado

de la arista y su correspondiente identificador son privados?. Tenemos varias razones para sugerir esto. 1) Evitar modificaciones de cualquier tipo a la arista. Un cambio no

contrlado implica corromper el significado del grafo. 2) Poder utilizar la arista en varios grafos sin el riesgo de dañar el

significado de esos grafos al modificar dicha arista. 3) Una arista se inicializa sus relaciones sólo una vez y se utiliza así

según se necesite. 4) Promueve el desarrollo de algoritmos robustos, confiables y eficientes.

Centro de Enseñanza Técnica Industrial. Inteligencia Artificial

15 Pedro Arturo Cornejo Torres

También se estará preguntando: ¿Por qué utilizar constructores protegidos? Bueno, también existen muy buenas razones que promueven extensibilidad y

reutilización, pero sin descuidar la seguridad, confiabilidad y eficiencia. 1) Tanto aristas como vértices, deben ser creados y ser correctamente

inicializados por una entidad administradora. 2) La creación y destrucción de objetos sólo es posible a partir de un

solo objeto de la clase CGraphManager. Ahora, cada grafo por definición es una asociación de par ordenado de dos

conjuntos. El conjunto de vértices y el conjunto de aristas. Véase la siguiente definición de la clase CGraph.

Graph.h #ifndef _C_GRAPH

#define _C_GRAPH

#include <set>

using namespace std;

class CGraph

{

friend class CGraphManager;

private:

//Identificador del grafo

int m_nIDGraph;

//Puntero al Administrador de Grafos

CGraphManager* m_pGraphManager;

public:

//Conjunto de vértices

set<int> m_V;

//Relación E que es subconjunto de VxV

set<long long> m_E;

//Llave del grafo

int Key();

//Obtiene una referencia al GraphManager

CGraphManager& GetGraphManager();

protected:

CGraph(int nIDGraph,CGraphManager* pGraphManager);

virtual ~CGraph(void);

};

#endif

Graph.cpp

#include "StdAfx.h"

#include "Graph.h"

#include "GraphManager.h"

CGraph::CGraph(int nIDGraph,CGraphManager* pGraphManager)

{

m_nIDGraph=nIDGraph;

m_pGraphManager=pGraphManager;

}

CGraph::~CGraph(void)

Centro de Enseñanza Técnica Industrial. Inteligencia Artificial

16 Pedro Arturo Cornejo Torres

{

}

int CGraph::Key()

{

return m_nIDGraph;

}

CGraphManager& CGraph::GetGraphManager()

{

return *m_pGraphManager;

}

Por último presentamos la clase CGraphManager, que se encarga de la

importante tarea de la administración de recursos de memoria y ciclo de vida de los objetos. Esta clase tiene como objetivo de encargarse de la creación de este Vértices, Aristas y Grafos. CGraphManager es una implementación del patrón de diseño Builder, Factory, Container.

• Factory, porque crea objetos y asigna sus recursos de memoria, • Builder, porque nos ofrece las estrategias para crear objetos con un

estado válido y, • Container, porque agrupa a estos objetos. Esto con el objetivo de realizar

una administración de datos que puede ser común entre varios grafos. Evitándose así la duplicación de la información y compartiendo datos entre grafos.

Resulta muy importante mencionarle la existencia de los patrones de diseño, por lo que anexo el siguiente fragmento Wiki que resume qué y para qué son los patrones de diseño: http://es.wikipedia.org/wiki/Patr%C3%B3n_de_dise%C3%B1o Los patrones de diseño (design patterns) son la base para la búsqueda de

soluciones a problemas comunes en el desarrollo de software y otros ámbitos referentes al diseño de interacción o interfaces.

Un patrón de diseño es una solución a un problema de diseño. Para que una

solución sea considerada un patrón debe poseer ciertas características. Una de ellas es que debe haber comprobado su efectividad resolviendo problemas similares en ocasiones anteriores. Otra es que debe ser reusable, lo que significa que es aplicable a diferentes problemas de diseño en distintas circunstancias.

Objetivos de los patrones de diseño pretenden:

• Proporcionar catálogos de elementos reusables en el diseño de sistemas software. • Evitar la reiteración en la búsqueda de soluciones a problemas ya conocidos y

solucionados anteriormente. • Formalizar un vocabulario común entre diseñadores. • Estandarizar el modo en que se realiza el diseño. • Facilitar el aprendizaje de las nuevas generaciones de diseñadores condensando

conocimiento ya existente.

Centro de Enseñanza Técnica Industrial. Inteligencia Artificial

17 Pedro Arturo Cornejo Torres

Asimismo, no pretenden: • Imponer ciertas alternativas de diseño frente a otras. • Eliminar la creatividad inherente al proceso de diseño. • No es obligatorio utilizar los patrones siempre, solo en el caso de tener el mismo

problema o similar que soluciona el patrón, siempre teniendo en cuenta que en un caso particular puede no ser aplicable. Abusar o forzar el uso de los patrones puede ser un error.

Ahora estudiemos la implementación de la clase CGraphManager. GraphManager.h

ifndef __C_GRAPH_MANAGER

#define __C_GRAPH_MANAGER

#include <map>

#include <set>

using namespace std;

//Símbolos externos

class CVertex;

class CEdge;

class CGraph;

class CGraphManager

{

private:

//Mapa de Llave a Vértice

map<int,CVertex*> m_mapVertex;

//Mapa de Llave a Arista

map<long long,CEdge*> m_mapEdge;

//Conjunto de Grafos

map<int,CGraph*> m_mapGraph;

public:

//Crear un vértice, si existe, retorna el existente

CVertex& CreateVertex(int nID);

//Crear una arista, si existe, retorna la existente

CEdge& CreateEdge(int nIDFirst,int nIDSecond);

//Crear un grafo, si existe, retorna el existente

CGraph& CreateGraph(int nIDGraph);

//Destruir un grafo

void DestroyGraph(int nIDGraph);

//Limpiar todo

void Clean();

public:

CGraphManager(void);

virtual ~CGraphManager(void);

};

#endif

GraphManager.cpp

Centro de Enseñanza Técnica Industrial. Inteligencia Artificial

18 Pedro Arturo Cornejo Torres

#include "StdAfx.h"

#include "GraphManager.h"

#include "Vertex.h"

#include "Edge.h"

#include "Graph.h"

CGraphManager::CGraphManager(void)

{

}

CGraphManager::~CGraphManager(void)

{

Clean();

}

void CGraphManager::Clean()

{

for(map<int,CGraph*>::iterator it=m_mapGraph.begin();

it!=m_mapGraph.end();it++)

{

delete it->second;

}

m_mapGraph.clear();

for(map<long long,CEdge*>::iterator it=m_mapEdge.begin();

it!=m_mapEdge.end();it++)

{

delete it->second;

}

m_mapEdge.clear();

for(map<int,CVertex*>::iterator it=m_mapVertex.begin();

it!=m_mapVertex.end();it++)

{

delete it->second;

}

m_mapVertex.clear();

}

CVertex& CGraphManager::CreateVertex(int nID)

{

map<int,CVertex*>::iterator it;

it=m_mapVertex.find(nID);

if(it==m_mapVertex.end()) //El vértice no existe, crear uno

nuevo

{

CVertex* pNewVertex=new CVertex(nID);

return

*(m_mapVertex.insert(make_pair(nID,pNewVertex)).first->second);

}

return *(it->second);

}

CEdge& CGraphManager::CreateEdge(int nIDFirst, int nIDSecond)

{

map<long long,CEdge*>::iterator it;

union //64 bit helper

{

//Par ordenado para VxV (m_nIDFirst,m_nIDSecond)

struct

{

Centro de Enseñanza Técnica Industrial. Inteligencia Artificial

19 Pedro Arturo Cornejo Torres

//Primer vértice (32 bits)

int m_nIDFirst;

//Segundo vértice (32 bits)

int m_nIDSecond;

};

long long m_llID; //Llave de la arista (64 bits)

};

//Conformar la clave de 64 bits

m_nIDFirst=nIDFirst;

m_nIDSecond=m_nIDSecond;

//Buscar la arista

it=m_mapEdge.find(m_llID);

if(it==m_mapEdge.end()) //La arista no existe, crear una nueva

{

CEdge* pNewEdge=new CEdge(nIDFirst,nIDSecond);

return

*(m_mapEdge.insert(make_pair(m_llID,pNewEdge)).first->second);

}

return *(it->second);

}

CGraph& CGraphManager::CreateGraph(int nIDGraph)

{

map<int,CGraph*>::iterator it;

it=m_mapGraph.find(nIDGraph);

if(it==m_mapGraph.end()) //El grafo no existe, crear uno nuevo

{

CGraph* pNewGraph=new CGraph(nIDGraph,this);

return

*(m_mapGraph.insert(make_pair(nIDGraph,pNewGraph)).first->second);

}

return *(it->second);

}

void CGraphManager::DestroyGraph(int nIDGraph)

{

map<int,CGraph*>::iterator it;

it=m_mapGraph.find(nIDGraph);

if(it!=m_mapGraph.end()) //El grafo existe, eliminarlo

{

delete it->second;

m_mapGraph.erase(it);

}

}

La implementación de todo el marco de trabajo de grafos está disponible para su

descarga desde el repositorio de documentos de éste curso. En esa solución de Visual Studio 2008, encontrará un programa principal que le servirá de ejemplo de uso.

Grado o valencia de un vértice

Dado un grafo G(V, E), el grado o valencia d(v) de un vértice v ∈ V es el número de aristas que inciden sobre ese vértice. Se dice que una arista incide en un vértice si

para una arista e ∈ E se da que v ∈ e. Es decir, una arista incide en un vértice si tiene como elemento a ese vértice.

Formalmente la valencia de un vértice se define como:

Centro de Enseñanza Técnica Industrial. Inteligencia Artificial

20 Pedro Arturo Cornejo Torres

d(v):=|{(a,b) ∈ E | a = v ∨ b = v, v ∈ V}| Nota: | | significa cardinalidad, el tamaño de un conjunto, como ya se estudió. Ejemplo 9: Sea G(V, E) el siguiente grafo no dirijido:

En este caso d(2) = 2, d(4) = 1, d(5) = 3, d(6) = 0 Cuando se tienen grafos dirigidos:

El grado de entrada d+(v) para v ∈ V, se define como:

d+(v):=|{(a, b) ∈ E | a = v, v ∈ V}|

y el grado de salida d-(v) para v ∈ V, se define como:

d-(v):=|{(a, b) ∈ E | b = v, v ∈ V}|

Ejemplo 10: Sea G(V,E) el siguiente grafo dirigido o digrafo.

Los conjuntos de éste digrafo son: V= {1, 2, 3, 4 ,5, 6, 7} E = {(1, 2), (1, 4), (1, 7), (2, 3), (3, 1), (4, 6), (5, 1), (6, 1), (7, 5)} En este caso d+(1) = 3, d+(2) = 1, d+(7) = 1 d-(4) = 1, d-(5) = 1, d-(3) = 1

3

2

7

6

5

4

1

Centro de Enseñanza Técnica Industrial. Inteligencia Artificial

21 Pedro Arturo Cornejo Torres

Adyacencia de vértice

Sea G(V, E) un grafo. Se dice que dos vértices v,w ∈ V son adyacentes si existe una arista (a, b) ∈ E que las relacione. La adyacencia Adj(v) ⊆ E de un vértice v ∈ V, es el conjunto de las aristas {(a, b) ∈ E | a = v ∨ b = v, v ∈ V}. En esta definición es semejante a la definición de grado o valencia, sin embargo, ahora no interesa el número de aristas, sino cuáles aristas.

De manera semejante, en caso de utilizar dígrafos, resultan interesantes las

siguientes definiciones: Adyacencia de Entrada.

Adj+(v) := {(a, b) ∈ E | a = v, v ∈ V}

Adyacencia de Salida.

Adj-(v) := {{(a, b) ∈ E | b = v, v ∈ V}

Resulta claro que a partir estas definiciones: Adj(v) = Adj+(v) ∪ Adj-(v) y d(v) = d+(v) + d-(v) Tarea:

Implemente en alguna plataforma de programación los grafos que se definen en los ejemplos 9 y 10 y resuélvanse los siguientes problemas mediante computador:

Para cada vértice en el grafo del ejemplo 9 (grafo no dirigido) a) Los conjuntos de adyacencia por vértice. b) Las valencias. Para cada vértice en el grafo del ejemplo 10 (dígrafo) a) Los conjuntos de adyacencia de entrada. b) Los conjuntos de adyacencia de salida. c) Los conjuntos de adyacencia. d) Las valencias de salida e) Las valencias de entrada f) Las valencias. Revisión de trabajos terminados, próximo martes 21 de octubre de 2008. Si tiene dudas, envíelas al foro de dudas del curso en línea.

Centro de Enseñanza Técnica Industrial. Inteligencia Artificial

22 Pedro Arturo Cornejo Torres

Unidad 2 Introducción a la Inteligencia Artificial

Búsquedas Ciegas.

Búsqueda en Profundidad

La Búsqueda en profundidad (en inglés DFS o Depth First Search) es un algoritmo que permite recorrer todos los vértices de un grafo o árbol de manera ordenada, pero no uniforme. Su funcionamiento consiste en ir expandiendo todos y cada uno de los vértices que va localizando, de forma recurrente, en un camino concreto. Cuando ya no quedan más vértices que visitar en dicho camino, regresa (Backtracking), de modo que repite el mismo proceso con cada uno de los hermanos del vértice que ya ha procesado. La expansión es el conjunto de vértices que se obtienen del conjunto de adyacencia de salida. Es decir, la relación de adyacencia de salida de un vértice v, expande a v. En este caso formalmente para un grafo G(V, E), la expansión se define como:

expansion(v):={ b ∈ V | (a, b) ∈ Adj-(v) } Con el objetivo de poder conocer las trayectorias “backtraking” generadas por la búsqueda, es necesario definir la siguiente propiedad del vértice.

parent(v):= Es un vértice w ∈ V, tal que w es padre de v. Al ser propiedad de los vértices, debe agregarse una nueva variable miembro a la clase CVertex para indicar la llave del vértice que es padre de éste vértice. También, es recomendable agregar dos métodos de acceso a la propiedad, en este caso, los métodos GetParent, SetParent. GetParent lee la variable miembro para leer la llave del vértice padre y SetParent para establecer el nuevo padre. Para el control de visitas a los nodos, será necesario, también agregar una nueva variable miembro de tipo booleano que indique si el nodo ha sido o no visitado por el algoritmo de búsqueda. visit(v) := Es verdadero si el nodo ha sido visitado, falso en caso de no haber sido visitado. Del mismo modo en que la propiedad parent, deberá agregar esa bandera como parte de la clase CVertex, luego crear los métodos de acceso a dicha bandera: SetVisit y GetVisit. Otros nombres más claros para estos métodos pudieran ser: SetVisited y AlreadyVisited respectivamente. Con las definiciones anteriores, es posible establecer el siguiente método de búsqueda en profundidad:

Centro de Enseñanza Técnica Industrial. Inteligencia Artificial

23 Pedro Arturo Cornejo Torres

Algoritmo DFS: Sea G(V,E) un grafo cualquiera. Sean source,dest ∈ V dos vértices de origen y meta respectivamente, y para todo vértice v ∈ V, visit(v):=false inicialmente, entonces la búsqueda en profundidad se define como:

DeepFirstSearch(G, source, dest) ∈ Boolean for each v ∈ expansion(source) do if not visit(v) then visit(v) := true; //Marcar el vértice como visitado. parent(v):= source; //Marcar el padre del vértice para backtraking. if v is equal to dest then return true; // v es alcanzable desde source, solución base. endif if DeepFirstSearch(G, v, dest) then //Recurrencia DFS return true; //Se ha encontrado solución de source a dest endif

endif

endfor

return false; //No se ha encontrado solución Una vez que se tiene una respuesta afirmativa del algoritmo, la trayectoria se puede iterar al invertir el backtraking que comienza en dest y termina en source. Esto se puede hacer de manera recurrente haciendo uso de la propiedad parent. La impresión de la trayectoria queda de tarea para el lector. Características del algoritmo.

• Es completo ya que el algoritmo tiene la habilidad de encontrar una solución si es que existe y si no existe una solución, éste indica que no esposible encontrar una solución.

• No es óptimo, ya que el algoritmo eventualmente no encuentra la solución optima.

Sin embargo, es claro que el algoritmo es muy fácil de implementar y su consumo de memoria es reducido.

Búsqueda en Amplitud

La búsqueda en amplitud (en inglés Breadth First Search o BFS) para recorrer o buscar elementos en un grafo. Formalmente el algoritmo BFS de búsqueda sin información o ciega, el cual, explora los nodos sistemáticamente y de forma exaustiva como en DFS. Utilizando las propiedades de los vértices que definimos para la búsqueda en profundidad, es posible formalizar el algoritmo BFS. Además, definiremos una estructura de datos “cola” o “queue” para almacenar los vértices que aún no se les ha expandido.

Centro de Enseñanza Técnica Industrial. Inteligencia Artificial

24 Pedro Arturo Cornejo Torres

Algoritmo BFS: Sea G(V, E) un grafo cualquiera y source, dest ∈ V dos vértices origen y destino respectivamente, para todo vértice v ∈ V, visit(v):=false inicialmente y sea Q ⊆ V una cola vacía, entonces la búsqueda en amplitud se define como:

BreadthFirstSearch(G, source, dest) ∈ Boolean Q.Push(source); //Encolar a source visit(source):=true; //Marcar como visitado a source while Q ≠ ∅ do v:=Q.Pull(); //Extraer de la cola a v

for each w ∈ expansion(v) do if not visit(w) then visit(w):=true; //Marcar como visitado parent(w):=v; //Establecer ancestro Q.Push(w) if w is equal to dest then return true; //Solución se ha encontrado

endif

endif

endfor

endwhile

return false; //No se ha encontrado solución y todo el grafo ha sido analizado.

Nota: Los métodos push y pull a los cuales se hace referencia en el algoritmo anterior, corresponden a la semántica de operación de una cola. Estos nombres también se utilizan indistintamente en caso de pilas. Para evitar ambigüedades se acostumbra definir previamente el tipo de contenedor a utilizar. Características del algoritmo:

• Es completo, porque el algoritmo es capaz de encontrar la solución si es que ésta exista y reporta la imposibilidad de encontrarla en caso de no existir solución.

• Es óptima, ya que siempre encuentra la trayectoria más corta entre todas las soluciones posibles.

Este algoritmo también es sencillo de implementar ya que a diferencia de DSF, BFS requiere de una cola para analizar el grafo. EXAMEN PRÁCTICO DEL SEGUNDO PARCIAL Implemente en alguna plataforma de programación, los algoritmos de búsquedas ciegas que ya han sido descritos y pruébelos en el siguiente grafo no dirigido primero y luego con el segundo grafo. Cuando los algoritmos reporten la existencia de la solución, imprima la secuencia de vértices que comienza en el origen hasta terminar en el destino.

Centro de Enseñanza Técnica Industrial. Inteligencia Artificial

25 Pedro Arturo Cornejo Torres

Este examen es para entregarse el próximo martes 4 de noviembre de 2008.