Estructuras de datos avanzadas, caminos más cortos, MST, flujo máximo y más.
Espacio: O(V + E)
Mejor para: grafos dispersos
A: [B, C] B: [D] C: [D] D: []
Espacio: O(V²)
Mejor para: grafos densos, lookup O(1)
A B C D A 0 1 1 0 B 0 0 0 1 C 0 0 0 1 D 0 0 0 0
| Algoritmo | Tiempo | Espacio |
|---|---|---|
| DFS / BFS | O(V + E) | O(V) |
| Topological Sort | O(V + E) | O(V) |
| Dijkstra | O((V+E) log V) | O(V) |
| Bellman-Ford | O(VE) | O(V) |
| Floyd-Warshall | O(V³) | O(V²) |
| Kruskal's MST | O(E log E) | O(V) |
| Prim's MST | O(E log V) | O(V) |
| TSP (DP) | O(n² 2ⁿ) | O(n 2ⁿ) |
vector<int> bfs(vector<vector<int>>& graph, int start) {
int n = graph.size();
vector<int> order;
vector<bool> visited(n, false);
queue<int> q;
q.push(start);
visited[start] = true;
while (!q.empty()) {
int u = q.front(); q.pop();
order.push_back(u);
for (int v : graph[u]) {
if (!visited[v]) {
visited[v] = true;
q.push(v);
}
}
}
return order;
}
Enfoque greedy: siempre expandir el vértice no visitado más cercano.
vector<int> dijkstra(vector<vector<pair<int,int>>>& graph, int src) {
int n = graph.size();
vector<int> dist(n, INT_MAX);
priority_queue<pair<int,int>, vector<pair<int,int>>,
greater<pair<int,int>>> pq;
dist[src] = 0;
pq.push({0, src});
while (!pq.empty()) {
auto [d, u] = pq.top(); pq.pop();
if (d > dist[u]) continue;
for (auto& [v, w] : graph[u]) {
if (dist[u] + w < dist[v]) {
dist[v] = dist[u] + w;
pq.push({dist[v], v});
}
}
}
return dist;
}
import heapq
def dijkstra(graph, src):
n = len(graph)
dist = [float('inf')] * n
dist[src] = 0
pq = [(0, src)]
while pq:
d, u = heapq.heappop(pq)
if d > dist[u]:
continue
for v, w in graph[u]:
if dist[u] + w < dist[v]:
dist[v] = dist[u] + w
heapq.heappush(pq, (dist[v], v))
return dist
Para cada vértice k, actualizar todos los caminos considerando k como intermediario:
void floydWarshall(vector<vector<int>>& dist) {
int n = dist.size();
for (int k = 0; k < n; k++) {
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (dist[i][k] != INT_MAX && dist[k][j] != INT_MAX) {
dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]);
}
}
}
}
// Detectar ciclos negativos
for (int i = 0; i < n; i++) {
if (dist[i][i] < 0) {
cout << "Ciclo negativo detectado" << endl;
return;
}
}
}
Crece el MST un vértice a la vez
Tiempo: O(E log V)
Mejor para: grafos densos
Ordena aristas y usa Union-Find
Tiempo: O(E log E)
Mejor para: grafos dispersos
class UnionFind {
vector<int> parent, rank;
public:
UnionFind(int n) : parent(n), rank(n, 0) {
iota(parent.begin(), parent.end(), 0);
}
int find(int x) {
if (parent[x] != x) parent[x] = find(parent[x]);
return parent[x];
}
bool unite(int x, int y) {
int px = find(x), py = find(y);
if (px == py) return false;
if (rank[px] < rank[py]) swap(px, py);
parent[py] = px;
if (rank[px] == rank[py]) rank[px]++;
return true;
}
};
int kruskal(int n, vector<tuple<int,int,int>>& edges) {
sort(edges.begin(), edges.end());
UnionFind uf(n);
int mstWeight = 0, count = 0;
for (auto& [w, u, v] : edges) {
if (uf.unite(u, v)) {
mstWeight += w;
if (++count == n - 1) break;
}
}
return mstWeight;
}
class UnionFind:
def __init__(self, n):
self.parent = list(range(n))
self.rank = [0] * n
def find(self, x):
if self.parent[x] != x:
self.parent[x] = self.find(self.parent[x])
return self.parent[x]
def unite(self, x, y):
px, py = self.find(x), self.find(y)
if px == py: return False
if self.rank[px] < self.rank[py]:
px, py = py, px
self.parent[py] = px
if self.rank[px] == self.rank[py]:
self.rank[px] += 1
return True
def kruskal(n, edges):
edges.sort()
uf = UnionFind(n)
mst_weight, count = 0, 0
for w, u, v in edges:
if uf.unite(u, v):
mst_weight += w
count += 1
if count == n - 1: break
return mst_weight
donde mask representa ciudades visitadas e i es la ciudad actual.
int tsp(vector<vector<int>>& dist) {
int n = dist.size();
vector<vector<int>> dp(1 << n, vector<int>(n, INT_MAX));
// Base case: empezar desde ciudad 0
dp[1][0] = 0;
for (int mask = 0; mask < (1 << n); mask++) {
for (int u = 0; u < n; u++) {
if (!(mask & (1 << u)) || dp[mask][u] == INT_MAX) continue;
for (int v = 0; v < n; v++) {
if (mask & (1 << v)) continue;
int newMask = mask | (1 << v);
dp[newMask][v] = min(dp[newMask][v], dp[mask][u] + dist[u][v]);
}
}
}
// Encontrar mínimo para regresar al inicio
int result = INT_MAX;
int finalMask = (1 << n) - 1;
for (int i = 1; i < n; i++) {
if (dp[finalMask][i] != INT_MAX) {
result = min(result, dp[finalMask][i] + dist[i][0]);
}
}
return result;
}
class MaxFlow {
vector<vector<int>> capacity, flow;
vector<bool> visited;
int n;
int dfs(int u, int t, int minFlow) {
if (u == t) return minFlow;
visited[u] = true;
for (int v = 0; v < n; v++) {
if (!visited[v] && capacity[u][v] > flow[u][v]) {
int bottleneck = min(minFlow, capacity[u][v] - flow[u][v]);
int result = dfs(v, t, bottleneck);
if (result > 0) {
flow[u][v] += result;
flow[v][u] -= result;
return result;
}
}
}
return 0;
}
public:
int maxFlow(int s, int t) {
int totalFlow = 0;
while (true) {
fill(visited.begin(), visited.end(), false);
int pathFlow = dfs(s, t, INT_MAX);
if (pathFlow == 0) break;
totalFlow += pathFlow;
}
return totalFlow;
}
};
Encontrar el número cromático es NP-hard para grafos generales. El algoritmo greedy da una solución válida pero no necesariamente óptima.
1. ¿Cuál es la complejidad temporal de Dijkstra con heap?
2. ¿Qué algoritmo NO funciona con pesos negativos?
3. ¿Qué estructura usa Kruskal's algorithm?
4. La complejidad del TSP con DP bitmask es:
5. El Max-Flow Min-Cut Theorem establece que:
6. BFS encuentra camino más corto cuando: