Ejemplos 5
Árbol generador
ALGORÍTMICA Y COMPLEJIDAD
Grados en Ing. Informática y Matemáticas
Universidad de Cantabria
Camilo Palazuelos Calderón
Definimos la función MallaImpar
, que devuelve un grafo de malla $\mathcal{G} = (V, E)$ de tamaño $n \times n$, recibido el entero positivo impar $n$ como parámetro.
import networkx as nx
def MallaImpar(n):
if not n % 2:
n += 1
return nx.grid_2d_graph(n, n), n
G, n = MallaImpar(3)
Definimos la función Mostrar
, que representa gráficamente el grafo de malla $\mathcal{G} = (V, E)$ de tamaño $n \times n$, recibidos $\mathcal{G}$ y $n$ como parámetros.
import matplotlib.pyplot as plt
def Mostrar(G, n, width = 1.0, edgec = 'k'):
plt.figure(figsize = (n, n))
nx.draw(G, pos = {(x, y) : (y, -x) for x, y in G.nodes()}, node_size = 500,
node_color = '#ffffff', width = width, edge_color = edgec)
ax = plt.gca()
ax.collections[0].set_edgecolor('#000000')
Mostrar(G, n)
Definimos la función Buscar
, que construye un árbol generador del grafo de malla $\mathcal{G} = (V, E)$ de tamaño $n \times n$ buscando en profundidad a partir del vértice $x \in V$, recibidos $\mathcal{G}$, $n$ y $x$ como parámetros. La función Vecinos
determina el orden en que se apilan los vecinos de cada vértice.
def Buscar(G, n, x = None):
if x is None:
x = (n // 2, n // 2)
Marcado = {v : False for v in G.nodes()}
Aristas = {e : False for e in G.edges()}
B = [(x, None)]
while B:
(v, u) = B.pop()
if not Marcado[v]:
Marcado[v] = True
if u is not None:
if (u, v) in Aristas.keys():
Aristas[(u, v)] = True
else:
Aristas[(v, u)] = True
for w in Vecinos(v, n):
B.append((w, v))
return Aristas
def Vecinos(v, n):
x, y, ord, V = v[0], v[1], None, []
if x + y < n - 1:
if x < y:
ord = ['↑', '→', '↓', '←']
else:
ord = ['↑', '→', '←', '↓']
elif x + y > n - 1:
if x < y:
ord = ['↓', '←', '→', '↑']
else:
ord = ['↓', '←', '↑', '→']
else:
if x < y:
ord = ['↑', '↓', '→', '←']
else:
ord = ['↑', '↓', '←', '→']
Añadir(x, y, n, ord, V)
return V
def Añadir(x, y, n, ord, V):
for mov in ord:
if mov == '←':
if y > 0:
V.append((x, y - 1))
elif mov == '↑':
if x > 0:
V.append((x - 1, y))
elif mov == '→':
if y < n - 1:
V.append((x, y + 1))
elif mov == '↓':
if x < n - 1:
V.append((x + 1, y))
Probamos la función Vecinos
con el vértice central.
Vecinos((n // 2, n // 2), n)
[(0, 1), (2, 1), (1, 0), (1, 2)]
Probamos la función Buscar
con los parámetros por defecto.
A = Buscar(G, n)
[e for e in A.keys() if A[e]]
[((0, 0), (1, 0)), ((0, 0), (0, 1)), ((0, 1), (0, 2)), ((0, 2), (1, 2)), ((1, 0), (2, 0)), ((1, 1), (1, 2)), ((2, 0), (2, 1)), ((2, 1), (2, 2))]
Probamos la función Mostrar
con los parámetros adecuados para resaltar sobre el grafo de malla el árbol generador construido.
Mostrar(G, n,
width = [ 10.0 if A[e] else 1.0 for e in G.edges()],
edgec = ['#458df9' if A[e] else '#000000' for e in G.edges()])
Reunimos el código anterior en la clase ÁrbolGenerador
.
class ÁrbolGenerador:
def __init__(self, n):
self.G, self.n = MallaImpar(n)
self.buscar()
def buscar(self, x = None):
self.A = Buscar(self.G, self.n, x)
def mostrar(self):
Mostrar(self.G, self.n,
width = [ 10.0 if self.A[e] else 1.0 for e in self.G.edges()],
edgec = ['#458df9' if self.A[e] else '#000000' for e in self.G.edges()])
Probamos la clase ÁrbolGenerador
con un grafo de malla de tamaño $5 \times 5$.
árbol = ÁrbolGenerador(5)
árbol.mostrar()
Construimos otro árbol generador del mismo grafo partiendo del vértice de la esquina superior izquierda.
árbol.buscar((0, 0))
árbol.mostrar()
Evaluamos el coste temporal empírico de Buscar
con $n$ desde $50$ hasta $500$ (de $50$ en $50$).
import time
m = 500
T = [0]
for n in range(m // 10, m + 1, m // 10):
t = time.time()
árbol = ÁrbolGenerador(n)
T.append(time.time() - t)
del árbol
#print(f'Iteración {n // (m // 10)} de 10')
Dibujamos una gráfica con el tiempo de ejecución $T(n)$ de Buscar
en función de $n$.
import numpy as np
with plt.style.context('ggplot'):
plt.plot(np.array(T), label = '$T(n) \in O(n^2)$')
plt.xticks(
[x for x in range(0, 11, 2)],
[x * 50 for x in range(0, 11, 2)])
plt.xlabel( '$n$', fontsize = 12)
plt.ylabel('$T(n)$ en s', fontsize = 12)
plt.title('Complejidad temporal')
plt.legend()
plt.show()
Nota: El coste temporal de Buscar
en profundidad es $O(|V| + |E|)$. Para todo grafo de malla $\mathcal{G} = (V, E)$ de tamaño $n \times n$, se cumple que $|V| = n^2$ y que $|E| = 2n (n - 1)$; de ahí que, en este caso, $O(|V| + |E|) = O(n^2)$.