Skip to Content

Introducción a los grafos

Introducción

Los grafos se pueden usar para representar muchas cosas, desde imágenes hasta señales inalámbricas, pero una de las analogías más simples es un mapa. Consideremos un mapa con varias ciudades y caminos bidireccionales que las conectan. Algunos problemas relacionados con grafos son:

  1. ¿La ciudad AA está conectada con la ciudad BB? Consideremos que una región es un grupo de ciudades tal que cada ciudad del grupo puede alcanzar a cualquier otra ciudad de ese grupo, pero a ninguna otra. ¿Cuántas regiones hay en este mapa, y qué ciudades están en cada región? (USACO Plata)

  2. ¿Cuál es la distancia más corta que hay que recorrer para ir de la ciudad AA a la ciudad BB? (USACO Oro)

Para USACO Bronce, alcanza con aprender lo básico de cómo se representan los grafos (por lo general, listas de adyacencia).

Recursos
FuenteRecursoNotas
CSAIntroduction to Graphs

interactivo

CSAGraph Representations

interactivo — listas y matrices de adyacencia

CSAGraph Editor

usá esta herramienta para visualizar tus propios grafos

CPH11 - Basics of Graphs

terminología y representación de grafos

IUSACO10.1 to 10.3 - Graph Theory

conceptos básicos y representación de grafos, árboles

PAPS6.4 - Graphs

matrices de adyacencia, listas, mapas

Construir listas de adyacencia

Los grafos suelen darse como entrada en el siguiente formato:

  • La primera línea contiene la cantidad de nodos NN y la cantidad de aristas MM.
  • Luego siguen MM líneas, cada una con un par de enteros que especifica una arista del grafo.

Por ejemplo, el grafo no dirigido dado en el recurso de CSAcademy de arriba  se representaría con la siguiente entrada:

6 10 2 4 0 2 0 4 0 5 5 3 2 3 1 3 4 5 4 1 1 5

y se podría visualizar en el editor de grafos de CSAcademy :

El siguiente código representa el grafo con listas de adyacencia. Una vez que tenemos el grafo en esta representación, es fácil imprimir la cantidad de vecinos de un nodo, o iterar sobre los vecinos de un nodo.

#include <iostream> #include <vector> using namespace std; int main() { int N, M; cin >> N >> M; vector<vector<int>> adj(N); for (int i = 0; i < M; ++i) { int u, v; cin >> u >> v; adj.at(u).push_back(v); adj.at(v).push_back(u); } { int u = 1; // imprimir la cantidad de vértices adyacentes a u cout << "deg(u) = " << adj.at(u).size() << endl; // imprimir todas las aristas que tienen a u como extremo for (int v : adj.at(u)) cout << "{" << u << ", " << v << "}" << "\n"; } }
import java.io.*; import java.util.*; public class Main { public static void main(String[] args) throws IOException { BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); StringTokenizer s1 = new StringTokenizer(reader.readLine()); int N = Integer.parseInt(s1.nextToken()); int M = Integer.parseInt(s1.nextToken()); List<List<Integer>> adj = new ArrayList<>(); for (int i = 0; i < N; i++) { adj.add(new ArrayList<>()); } for (int i = 0; i < M; i++) { StringTokenizer s2 = new StringTokenizer(reader.readLine()); int u = Integer.parseInt(s2.nextToken()); int v = Integer.parseInt(s2.nextToken()); adj.get(u).add(v); adj.get(v).add(u); } int u = 1; // imprimir la cantidad de vértices adyacentes a u System.out.println("deg(u) = " + adj.get(u).size()); // imprimir todas las aristas que tienen a u como extremo for (int v : adj.get(u)) { System.out.println("{" + u + ", " + v + "}"); } } }
N, M = map(int, input().split()) adj = [[] for _ in range(N)] for i in range(M): u, v = map(int, input().split()) adj[u].append(v) adj[v].append(u) u = 1 # imprimir la cantidad de vértices adyacentes a u print("deg(u) =", len(adj[u])) # imprimir todas las aristas que tienen a u como extremo for v in adj[u]: print("{" + str(u) + ", " + str(v) + "}")

Salida:

deg(u) = 3 {1, 3} {1, 4} {1, 5}

¿Cómo se ve un problema de grafos de Bronce?

Todos los problemas de abajo caen en al menos una de las siguientes dos categorías:

  • La estructura del grafo es especial (es un árbol, un camino o un ciclo).
  • Para resolver el problema, alcanza con iterar sobre la lista de adyacencia de cada vértice.

Además, el conocimiento de temas de grafos de nivel Plata suele ayudar, pero no es estrictamente necesario para resolver el problema.

Livestock Lineup

HechoFuenteNombreDificultadTagsSolución
BronzeLivestock LineupDifícilPermutation, GraphSolución

Aunque la solución prevista es probar por fuerza bruta todas las permutaciones posibles de las vacas en tiempo O(NC!)\mathcal O(N\cdot C!), podemos resolver el problema en solo O(C)\mathcal{O}(C) si representamos las restricciones con un grafo.

Solución usando grafos

Nótese que, como se garantiza que la entrada es válida, siempre vamos a terminar con “cadenas” de vacas que podemos ordenar como queramos. Usando el sample del problema, obtendríamos una representación en “cadenas” como esta:

Chains

Nótese que las vacas que no forman parte de ninguna cadena se pueden considerar cadenas propias de longitud 1 a los efectos de la implementación.

Con esta representación en mente, podemos iterar las vacas en orden lexicográfico (alfabético). Cuando visitamos una vaca que podría ser un inicio posible de una cadena (una vaca que tiene a lo sumo un vecino requerido), recorremos repetidamente a sus vecinos, agregando las vacas que visitamos al ordenamiento, hasta llegar a un extremo.

Implementación

Complejidad temporal: O(C)\mathcal{O}(C)

#include <algorithm> #include <fstream> #include <map> #include <string> #include <vector> using std::endl; using std::string; using std::vector; // BeginCodeSnip{Cow Names} // Expresión lambda — ver /general/lambda-funcs const vector<string> COWS = []() { vector<string> tmp{"Bessie", "Buttercup", "Belinda", "Beatrice", "Bella", "Blue", "Betsy", "Sue"}; // ordenar los nombres lexicográficamente std::sort(std::begin(tmp), std::end(tmp)); return tmp; }(); // EndCodeSnip int main() { std::map<string, int> cow_inds; for (int i = 0; i < COWS.size(); i++) { cow_inds[COWS[i]] = i; } std::ifstream read("lineup.in"); int req_num; read >> req_num; vector<vector<int>> neighbors(COWS.size()); for (int r = 0; r < req_num; r++) { string cow1; string cow2; string trash; read >> cow1 >> trash >> trash >> trash >> trash >> cow2; // Convertir los nombres a su índice en la lista int c1 = cow_inds[cow1]; int c2 = cow_inds[cow2]; neighbors[c1].push_back(c2); neighbors[c2].push_back(c1); } vector<int> order; vector<bool> added(COWS.size()); for (int c = 0; c < COWS.size(); c++) { if (!added[c] && neighbors[c].size() <= 1) { added[c] = true; order.push_back(c); if (neighbors[c].size() == 1) { int prev = c; int at = neighbors[c][0]; while (neighbors[at].size() == 2) { added[at] = true; order.push_back(at); int a = neighbors[at][0]; int b = neighbors[at][1]; int temp_at = a == prev ? b : a; prev = at; at = temp_at; } added[at] = true; order.push_back(at); } } } std::ofstream out("lineup.out"); for (int c : order) { out << COWS[c] << endl; } }
import java.io.*; import java.util.*; public class LineUp { // Se asume que está en orden (y lo está) static final String[] COWS = new String[] { "Beatrice", "Belinda", "Bella", "Bessie", "Betsy", "Blue", "Buttercup", "Sue"}; public static void main(String[] args) throws IOException { Map<String, Integer> cowInds = new HashMap<>(); for (int i = 0; i < COWS.length; i++) { cowInds.put(COWS[i], i); } BufferedReader read = new BufferedReader(new FileReader("lineup.in")); int reqNum = Integer.parseInt(read.readLine()); List<Integer>[] neighbors = new ArrayList[COWS.length]; for (int c = 0; c < COWS.length; c++) { neighbors[c] = new ArrayList<>(); } for (int r = 0; r < reqNum; r++) { String[] words = read.readLine().split(" "); // Convertir los nombres a su índice en la lista int cow1 = cowInds.get(words[0]); int cow2 = cowInds.get(words[words.length - 1]); neighbors[cow1].add(cow2); neighbors[cow2].add(cow1); } List<Integer> order = new ArrayList<>(); boolean[] added = new boolean[COWS.length]; for (int c = 0; c < COWS.length; c++) { /* * Comprobar que: * 1. Esta vaca todavía no se agregó. * 2. Esta vaca podría ser el inicio de una cadena. */ if (!added[c] && neighbors[c].size() <= 1) { added[c] = true; order.add(c); // Si la longitud de la cadena > 1, seguimos if (neighbors[c].size() == 1) { int prev = c; int at = neighbors[c].get(0); while (neighbors[at].size() == 2) { added[at] = true; order.add(at); int a = neighbors[at].get(0); int b = neighbors[at].get(1); int temp_at = a == prev ? b : a; prev = at; at = temp_at; } // Agregar el último elemento added[at] = true; order.add(at); } } } PrintWriter out = new PrintWriter("lineup.out"); for (int c : order) { out.println(COWS[c]); } out.close(); } }
COWS = sorted( ["Bessie", "Buttercup", "Belinda", "Beatrice", "Bella", "Blue", "Betsy", "Sue"] ) cow_inds = {c: i for i, c in enumerate(COWS)} neighbors = [[] for _ in range(len(COWS))] with open("lineup.in") as read: for _ in range(int(read.readline())): words = read.readline().strip().split() # Convertir los nombres a su índice en la lista cow1 = cow_inds[words[0]] cow2 = cow_inds[words[-1]] neighbors[cow1].append(cow2) neighbors[cow2].append(cow1) order = [] added = [False for _ in range(len(COWS))] for c in range(len(COWS)): """ Comprobar que: 1. Esta vaca todavía no se agregó. 2. Esta vaca podría ser el inicio de una cadena. """ if not added[c] and len(neighbors[c]) <= 1: added[c] = True order.append(c) # Si la longitud de la cadena > 1, seguimos if len(neighbors[c]) == 1: prev = c at = neighbors[c][0] while len(neighbors[at]) == 2: added[at] = True order.append(at) a, b = neighbors[at] at, prev = b if a == prev else a, at # Agregar el último elemento added[at] = True order.append(at) with open("lineup.out", "w") as out: for c in order: print(COWS[c], file=out)

Comprobá tu comprensión

Pregunta 1/6

¿Cuántas componentes conexas hay en el siguiente grafo?

Problemas

HechoFuenteNombreDificultadTagsSolución
BronzeThe Great RevegetationDifícilColoringSolución
BronzeMilk FactoryDifícilTree, DFSSolución
BronzeHoofballDifícilFunctional GraphSolución
BronzeSwapity SwapDifícilPermutation, CycleSolución
BronzeCow EvolutionMuy difícilTreeSolución
BronzeFamily TreeMuy difícilTreeSolución