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.

In [ ]:
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.

In [ ]:
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)
No description has been provided for this image

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.

In [ ]:
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.

In [ ]:
Vecinos((n // 2, n // 2), n)
Out[ ]:
[(0, 1), (2, 1), (1, 0), (1, 2)]

Probamos la función Buscar con los parámetros por defecto.

In [ ]:
A = Buscar(G, n)
[e for e in A.keys() if A[e]]
Out[ ]:
[((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.

In [ ]:
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()])
No description has been provided for this image

Reunimos el código anterior en la clase ÁrbolGenerador.

In [ ]:
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$.

In [ ]:
árbol = ÁrbolGenerador(5)
árbol.mostrar()
No description has been provided for this image

Construimos otro árbol generador del mismo grafo partiendo del vértice de la esquina superior izquierda.

In [ ]:
árbol.buscar((0, 0))
árbol.mostrar()
No description has been provided for this image

Evaluamos el coste temporal empírico de Buscar con $n$ desde $50$ hasta $500$ (de $50$ en $50$).

In [ ]:
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$.

In [ ]:
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()
No description has been provided for this image

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)$.