===== Árbol generador ===== Un árbol generador de un grafo, es un subgrafo conexo del mismo, que contiene a todos los vértices y es un árbol. Estas propiedades son equivalente a decir que es un árbol que contiene todos los nodos del grafo en cuestión, y a partir del cual se puede llegar al grafo agregando aristas. Por ejemplo: {{:algoritmos-oia:graph_5_.png?400|}} $\rightarrow$ {{:algoritmos-oia:graph_6_.png?400|}} ==== Árbol Generador Mínimo ==== El árbol generador mínimo (AGM, o "MST" por sus siglas en inglés) de un grafo con aristas con pesos, es el grafo generador cuya suma de las aristas del árbol es mínimo dentro de todas las sumas de aristas de árboles generadores. En el caso de arriba, sería: {{ :algoritmos-oia:graph_7_.png?400 |}} ==== Cómo encontrar el AGM ==== Hay dos algoritmos conocidos para encontrar el AGM: el de Prim y el de Kruskal. Voy a contar la idea básica de ambos, sin detallar las demostraciones. Para conocerlas bien, pueden ver el link de wikipedia de [[https://en.wikipedia.org/wiki/Prim%27s_algorithm|Prim]] y [[https://en.wikipedia.org/wiki/Kruskal%27s_algorithm|Kruskal]]. ==== Prim ==== **Idea**: Inicializar un árbol con un solo nodo, cualquiera. Ir agregando de a un eje: Agrego el eje más chico de todos los que unen al árbol con nodos que no están todavía en el árbol. **Ejemplo con el grafo de arriba**: Supongamos que agarramos el nodo $4$ como el árbol inicial. De todas las aristas que conectan al $4$ con otros nodos, la de valor más bajo es la arista de valor $3$, que une al $4$ con el $1$. Luego, podemos elegir la arista $(4,2)$ ó la arista $(1,5)$, ambas de valor $4$. Supongamos que agregamos la arista $(1,5)$ (es indistinto, con cualquier de las dos funciona igual). Ahora sí, la arista de valor más bajo que conecta a alguno de los nodos $4,1,5$ con los otros, es la arista $(4,2)$, de valor $4$. Entonces en nuestro árbol están los nodos $4,1,5,2$. Luego, agregamos al nodo $0$ mediante la arista $(2,0)$ de valor $4$. Y por último, el $3$ no está en el árbol, por lo que debo agregar la arista $(2,3)$. Finalmente termino con el AGM de la imagen anterior. === Implementación === Este algoritmo, se puede ver como similar al algoritmo de [[algoritmos-oia:grafos:dijkstra|Dijkstra]], sólo que en vez de agarrar el nodo no recorrido de distancia más chica, agarramos el nodo fuera del árbol de distancia al árbol más chica. #include using namespace std; #define forn(i,n) for(int i=0; i<(int)(n); i++) int main(){ int n, m; // ingreso cantidad de nodos y de aristas cin>>n>>m; vector< vector< pair > > grafo(n); map< pair, int> longitudArista; forn(i, m){ int u, v, longitud; cin>>u>>v>>longitud; grafo[u].push_back(make_pair(longitud, v)); grafo[v].push_back(make_pair(longitud, u)); longitudArista[make_pair(u,v)] = longitud; longitudArista[make_pair(v,u)] = longitud; // Guardamos esto para poder obtener la longitud dados los vértices } priority_queue< pair, vector < pair > , greater< pair > > pq; int INF=1e9; int src = 0; // Tomo el nodo 0 como source, puede ser cualquiera vector key(n, INF); vector parent(n, -1); vector enAGM(n, false); pq.push(make_pair(0, src)); key[src] = 0; while (!pq.empty()){ int u = pq.top().second; pq.pop(); enAGM[u] = true; // Así como marcábamos el visitado en Dijkstra, ahora lo marcamos como que ya está en el AGM, para no generar ciclos forn(i, grafo[u].size()){ int v = grafo[u][i].second; int weight = grafo[u][i].first; if (enAGM[v] == false && key[v] > weight){ // Si ahora el nodo v está más cerca que lo que teníamos antes, actualizamos el valor en la priority_queue key[v] = weight; pq.push(make_pair(key[v], v)); parent[v] = u; // Al actualizar, también marcamos que el mejor camino para llegar a v es desde u } } } int tamanioAGM = 0; forn(i, n){ if(i!=src){ // El parent del src queda en -1 porque nunca lo actualizamos, enAGM[src] vale true desde el principio cout< #include using namespace std; #define forn(i,n) for(int i=0; i<(int)(n); i++) vector padre, altura; void init(int n){ forn(i, n){ padre.push_back(i); altura.push_back(1); } } int find(int x){ if(padre[x]!=x){ padre[x]=find(padre[x]); } return padre[x]; } void merge(int x, int y){ int px=find(x), py=find(y); if(altura[px]>n>>m; // inicializo los n Disjoin-Sets que tienen como padre a sí mismos y tamanio 1. init(n); vector< arista > aristas; forn(i, m){ arista a; cin>>a.u>>a.v>>a.longitud; aristas.push_back(a); } sort(aristas.begin(), aristas.end(), compareAristas); vector elegidas; int pesoMinimo=0; forn(i, m){ arista a = aristas[i]; if(!connected(a.u, a.v)){ merge(a.u, a.v); elegidas.push_back(a); pesoMinimo+=a.longitud; } } cout<<"Peso minimo: "< Se puede probar con el grafo del ejemplo, y se puede cambiar la función "compareAristas" para que si tienen misma longitud priorize la de menor $u$, o la de mayor $u$, para ver qué árbol elige (en un grafo con más de un AGM, por ejemplo se puede cambiar la longitud de la arista $(4, 5)$ de $5$ a $4$) y ver que el peso sigue siendo el mismo. === Complejidad === El algoritmo ordena las aristas, y luego las recorre y hace un par de finds y quizás un merge, que tienen complejidad muy baja, por lo que la complejidad del algoritmo termina siendo de $\large O(E*log(E))$