SHOW:
|
|
- or go back to the newest paste.
1 | import random | |
2 | import heapq | |
3 | import math | |
4 | from time import time | |
5 | from collections import deque | |
6 | ||
7 | visitar_nulo = lambda a,b,c,d: True | |
8 | heuristica_nula = lambda actual,destino: 0 | |
9 | ||
10 | class Grafo(object): | |
11 | '''Clase que representa un grafo. El grafo puede ser dirigido, o no, y puede | |
12 | no indicarsele peso a las aristas (se comportara como peso = 1). | |
13 | Implementado como "diccionario de diccionarios" | |
14 | ''' | |
15 | ||
16 | def __init__(self, es_dirigido = False): | |
17 | '''Crea el grafo. El parametro 'es_dirigido' indica si sera dirigido, o no.''' | |
18 | self.es_dirigido = es_dirigido | |
19 | self.cant_vertices = 0 | |
20 | self.vertices = {} | |
21 | self.personajes = {} | |
22 | self.nodos = {} | |
23 | self.cant_aristas = 0 | |
24 | ||
25 | def __iter__(self): | |
26 | '''Devuelve un iterador para el grafo''' | |
27 | return iter(self.vertices) | |
28 | ||
29 | def __len__(self): | |
30 | '''Devuelve la cantidad de vertices del grafo''' | |
31 | return self.cant_vertices | |
32 | ||
33 | - | def keys(self): |
33 | + | |
34 | - | '''Devuelve una lista de identificadores de vertices. Iterar sobre |
34 | + | |
35 | - | ellos es equivalente a iterar sobre el grafo. |
35 | + | |
36 | ya se encuentre asociado a un vertice, se actualizara el valor. | |
37 | - | return self.vertices.keys() |
37 | + | |
38 | ||
39 | if not id in self.nodos: | |
40 | self.vertices[id] = {} | |
41 | self.personajes[valor] = id | |
42 | self.cant_vertices += 1 | |
43 | self.nodos[id] = Nodo_Grafo(id, valor) | |
44 | self.nodos[id].nombre = valor | |
45 | ||
46 | def __contains__(self, id): | |
47 | ''' Determina si el grafo contiene un vertice con el identificador indicado.''' | |
48 | return id in self.vertices.keys() | |
49 | ||
50 | def cantidad_aristas(self): | |
51 | return self.cant_aristas | |
52 | ||
53 | - | def __delitem__(self, id): |
53 | + | |
54 | - | '''Elimina el vertice del grafo. Si no existe el identificador en el grafo, |
54 | + | |
55 | - | lanzara KeyError. |
55 | + | |
56 | - | Borra tambien todas las aristas que salian y entraban al vertice en cuestion. |
56 | + | |
57 | * desde y hasta: identificadores de vertices dentro del grafo. Si alguno | |
58 | de estos no existe dentro del grafo, lanzara KeyError. | |
59 | * Peso: valor de peso que toma la conexion. Si no se indica, valdra 1. | |
60 | - | raise KeyError("No se encontro la clave") |
60 | + | |
61 | - | for i in self.vertices: |
61 | + | |
62 | - | if id in self.vertices[i]: |
62 | + | |
63 | - | self.vertices[i].pop(id) |
63 | + | |
64 | - | self.vertices.pop(id) |
64 | + | |
65 | - | self.nodos.pop(id) |
65 | + | |
66 | if self.es_dirigido: | |
67 | self.vertices[hasta][desde] = peso | |
68 | ||
69 | def adyacentes(self, id): | |
70 | '''Devuelve una lista con los vertices (identificadores) adyacentes | |
71 | al indicado. Si no existe el vertice, se lanzara KeyError | |
72 | ''' | |
73 | if not id in self.vertices: | |
74 | raise KeyError("No se encontro el vertice") | |
75 | return list(self.vertices[id].keys()) | |
76 | ||
77 | def obtener_personaje(self, id): | |
78 | '''Devuelve el valor de la id''' | |
79 | if not id in self.nodos: | |
80 | raise KeyError("No se encontro la id") | |
81 | return self.nodos[id].nombre | |
82 | ||
83 | def grado(self): | |
84 | '''Devuelve el grado total del grafo''' | |
85 | grado_total = 0 | |
86 | for vertice in self.vertices: | |
87 | for adyacente in Grafo.adyacentes(self, vertice): | |
88 | grado_total += 1 | |
89 | return grado_total | |
90 | ||
91 | - | def borrar_arista(self, desde, hasta): |
91 | + | |
92 | - | '''Borra una arista que conecta los vertices indicados. |
92 | + | |
93 | if not valor in self.personajes: | |
94 | - | * desde y hasta: identificadores de vertices dentro del grafo. |
94 | + | |
95 | - | Si alguno de estos no existe dentro del grafo, lanzara KeyError. |
95 | + | |
96 | - | En caso de no existir la arista, se lanzara ValueError. |
96 | + | |
97 | ||
98 | def random_walk(self, largo, origen = None, pesado = False): | |
99 | ''' Devuelve una lista con un recorrido aleatorio de grafo. | |
100 | - | if not hasta in self.vertices[desde][1]: |
100 | + | |
101 | - | raise ValueError("No existe la arista") |
101 | + | |
102 | - | if not self.es_dirigido: |
102 | + | |
103 | - | self.vertices[hasta].pop(desde) |
103 | + | |
104 | - | self.vertices[desde].pop(hasta) |
104 | + | |
105 | las probabilidades de movernos de un vertice a uno de sus vecinos (False = todo equiprobable). | |
106 | - | def obtener_peso_arista(self, desde, hasta): |
106 | + | |
107 | - | '''Obtiene el peso de la arista que va desde el vertice 'desde', hasta el vertice 'hasta'. |
107 | + | |
108 | ''' | |
109 | - | * desde y hasta: identificadores de vertices dentro del grafo. |
109 | + | |
110 | - | Si alguno de estos no existe dentro del grafo, lanzara KeyError. |
110 | + | |
111 | - | En caso de no existir la union consultada, se devuelve None. |
111 | + | |
112 | cont_recorrido = 0 | |
113 | if origen: | |
114 | if not origen in self.vertices: | |
115 | - | if not hasta in self.vertices[desde]: return None |
115 | + | |
116 | - | return self.vertices[desde][hasta] |
116 | + | |
117 | else: | |
118 | lista_claves = list(self.vertices.keys()) | |
119 | vertice_random = random.choice(lista_claves) | |
120 | while not vertice_random: | |
121 | vertice_random = random.choice(lista_claves) | |
122 | recorrido.append(vertice_random) | |
123 | ||
124 | vertice_partida = recorrido[cont_recorrido] | |
125 | ||
126 | while cont_recorrido < largo: | |
127 | if not vertice_partida: break | |
128 | diccionario_adyacentes = self.vertices[vertice_partida] | |
129 | if pesado: | |
130 | vertice_random = vertice_random_por_peso(diccionario_adyacentes) | |
131 | else: | |
132 | lista_claves = list(self.vertices[vertice_partida].keys()) | |
133 | vertice_random = random.choice(lista_claves) | |
134 | ||
135 | recorrido.append(vertice_random) | |
136 | cont_recorrido+= 1 | |
137 | vertice_partida = recorrido[cont_recorrido] | |
138 | ||
139 | ||
140 | return recorrido | |
141 | ||
142 | def dijkstra(self, origen, destino, minimo = True): | |
143 | ||
144 | '''Devuelve el recorrido dependiendo si es maximo o minimo desde el | |
145 | origen hasta el destino, aplicando el algoritmo de Dijkstra. | |
146 | Parametros: | |
147 | * minimo: indica si el camino devuelto sera el minimo si minimo es True | |
148 | devolvera el maximo si es False | |
149 | * origen y destino: identificadores de vertices dentro del grafo. | |
150 | Si alguno de estos no existe dentro del grafo, lanzara KeyError. | |
151 | Devuelve: | |
152 | * Listado de vertices (identificadores) ordenado con el recorrido, incluyendo | |
153 | a los vertices de origen y destino. En caso que no exista camino entre | |
154 | el origen y el destino, se devuelve None. | |
155 | ''' | |
156 | heap = [] | |
157 | camino = [] | |
158 | for vertice in self.vertices: | |
159 | nodo = self.nodos[vertice] | |
160 | nodo.visitado = False | |
161 | nodo.distancia = math.inf | |
162 | if vertice == origen: | |
163 | heapq.heappush(heap,nodo) | |
164 | nodo.distancia = 0 | |
165 | while heap: | |
166 | nodo = heapq.heappop(heap) | |
167 | nodo.visitado = True | |
168 | vertice = nodo.vertice | |
169 | dist = nodo.distancia | |
170 | for adyacente in self.vertices[vertice]: | |
171 | if not adyacente: continue | |
172 | peso = self.vertices[vertice][adyacente] | |
173 | nodo_ad = self.nodos[adyacente] | |
174 | if not nodo_ad.visitado and nodo_ad.distancia > (dist + peso): | |
175 | ||
176 | if minimo: | |
177 | nodo_ad.cambiar_dist(dist + peso) | |
178 | else: | |
179 | nodo_ad.cambiar_dist(dist + 1/peso) | |
180 | nodo_ad.cambiar_padre(vertice) | |
181 | heapq.heappush(heap,nodo_ad) | |
182 | ||
183 | vertice = destino | |
184 | camino.append(vertice) | |
185 | while vertice != origen: | |
186 | padre = self.nodos[vertice].padre | |
187 | camino.append(padre) | |
188 | vertice = padre | |
189 | return camino | |
190 | ||
191 | ||
192 | ||
193 | def bfs(self, inicio = None, visitar = None, extra = None): | |
194 | ||
195 | '''Realiza un recorrido BFS dentro del grafo | |
196 | Parametros: | |
197 | - inicio: identificador del vertice que se usa como inicio. Si se indica un vertice, el recorrido se comenzara en dicho vertice, | |
198 | y solo se seguira hasta donde se pueda (no se seguira con los vertices que falten visitar) | |
199 | Salida: | |
200 | Tupla (padres, orden), donde : | |
201 | - 'padres' es un diccionario que indica para un identificador, cual es el identificador del vertice padre en el recorrido BFS (None si es el inicio) | |
202 | - 'orden' es un diccionario que indica para un identificador, cual es su orden en el recorrido BFS | |
203 | ''' | |
204 | padres = {} | |
205 | orden = {} | |
206 | ||
207 | for nodo in self.nodos: | |
208 | nodo = self.nodos[nodo] | |
209 | nodo.visitado = False | |
210 | padres[nodo.vertice] = None | |
211 | orden[nodo.vertice] = math.inf | |
212 | ||
213 | if inicio: | |
214 | nodo = self.nodos[inicio] | |
215 | vertice = inicio | |
216 | else: | |
217 | nodos = list(self.nodos.keys()) | |
218 | vertice = random.choice(nodos) | |
219 | nodo = self.nodos[vertice] | |
220 | vertice_origen = vertice | |
221 | padres[vertice] = None | |
222 | nodo.visitado = True | |
223 | orden[vertice] = 0 | |
224 | cola = deque([]) | |
225 | cola.append(nodo) | |
226 | while cola: | |
227 | nodo = cola.popleft() | |
228 | vertice = nodo.vertice | |
229 | if visitar and not visitar(nodo,extra): break | |
230 | for adyacente in self.vertices[vertice]: | |
231 | nodo_ad = self.nodos[adyacente] | |
232 | if nodo_ad.visitado == False: | |
233 | nodo_ad.visitado = True | |
234 | cola.append(nodo_ad) | |
235 | orden[adyacente] = orden[vertice] + 1 | |
236 | padres[adyacente] = vertice | |
237 | ||
238 | return (orden, padres, vertice_origen) | |
239 | ||
240 | ||
241 | ||
242 | class Nodo_Grafo(): | |
243 | ||
244 | def __init__(self, vertice, nombre, distancia = 0, visitado = False, padre = None): | |
245 | self.vertice = vertice | |
246 | self.distancia = distancia | |
247 | self.visitado = visitado | |
248 | self.padre = padre | |
249 | self.nombre = nombre | |
250 | self.label_act = int(vertice) | |
251 | ||
252 | ||
253 | def visitado(self): | |
254 | return self.visitado | |
255 | ||
256 | def cambiar_dist(self, distancia_nueva): | |
257 | self.distancia = distancia_nueva | |
258 | ||
259 | def cambiar_padre(self, padre_nuevo): | |
260 | self.padre = padre_nuevo | |
261 | ||
262 | def __lt__(self,otro): | |
263 | return self.distancia < otro.distancia | |
264 | ||
265 | def __gt__(self,otro): | |
266 | return self.distancia > otro.distancia | |
267 | ||
268 | def __eq__(self,otro): | |
269 | return self.distancia == otro.distancia | |
270 | ||
271 | def __str__(self): | |
272 | return str(self.vertice)+"; "+str(self.distancia) | |
273 | ||
274 | ||
275 | ||
276 | def vertice_random_por_peso(diccionario_adyacentes): | |
277 | '''Devuelve un vertice adyacente random, con altas prioridades de que su peso sea mayor al de los demas''' | |
278 | ||
279 | total = sum(diccionario_adyacentes.values()) | |
280 | rand = random.uniform(0, total) | |
281 | acum = 0 | |
282 | for vertice, peso_arista in diccionario_adyacentes.items(): | |
283 | if acum + peso_arista >= rand: | |
284 | return vertice | |
285 | acum += peso_arista |