12 - Conexión con algoritmos (visión global)

Algoritmos y estructuras de datos se diseñan en conjunto: un algoritmo requiere operaciones eficientes sobre una estructura específica, y la estructura se justifica por los algoritmos que habilita. Entender esta simbiosis evita decisiones arbitrarias y obliga a pensar en flujos completos.

En esta sección conectamos las piezas principales para detectar patrones: qué algoritmo usa cada estructura, qué garantías aprovecha y cuándo conviene combinar varias para cumplir requisitos de negocio.

12. Conexión con algoritmos

El objetivo es desarrollar criterio: si conoces la forma del dato y las operaciones dominantes, puedes deducir qué algoritmos son aplicables sin memorizar listas infinitas.

12.1 No hay algoritmos sin estructuras

Un algoritmo describe pasos, pero necesita un soporte físico donde almacenar estados intermedios, restricciones y resultados. Las estructuras definen ese soporte y delimitan el costo de cada paso.

  • Un algoritmo de ordenamiento como quicksort redefine punteros sobre arreglos contiguos; el mismo enfoque pierde efectividad en listas enlazadas por la ausencia de acceso indexado.
  • Las estructuras definen invariantes (ej.: heap binario mantiene el elemento máximo arriba) que simplifican la prueba de corrección del algoritmo.
  • Modificar el algoritmo implica ajustar la estructura o sus operaciones auxiliares (por ejemplo, agregar un contador de frecuencia a un HashMap para soportar consultas adicionales).

12.2 Cómo cada estructura soporta un conjunto de algoritmos específicos

La compatibilidad entre la forma del dato y el algoritmo determina la eficiencia. Algunas combinaciones clásicas ilustran el porqué:

  • Árboles → recorridos: preorden, inorden y postorden permiten visitar nodos sin estructuras extra, aprovechando los punteros a los hijos. En un árbol binario de búsqueda, el recorrido inorden devuelve elementos ordenados.
  • Grafos → BFS/DFS: tanto la Búsqueda en Anchura (BFS) como la Búsqueda en Profundidad (DFS) dependen de listas de adyacencia y del soporte de una cola o pila auxiliares. Elegir matrices de adyacencia altera el costo espacial y el rendimiento en grafos dispersos.
  • Heaps → Dijkstra: el algoritmo de Dijkstra requiere extraer el próximo nodo con distancia mínima, tarea que un heap binario o cola de prioridad realiza en O(log n) conservando la consistencia del conjunto de pendientes.
  • Hashing → búsquedas rápidas: tablas hash con buena función de dispersión permiten inserciones y consultas promedio O(1). Se usan como base para caches, tablas de símbolos o conteo de eventos.

Cuando un algoritmo no encaja bien, la alternativa suele ser transformar los datos (por ejemplo, convertir un grafo en árbol de expansión mínima) para aprovechar propiedades donde sí existen algoritmos eficientes.

12.3 Ejemplos conceptuales sin entrar en detalle

Estos fragmentos sencillos muestran cómo la elección de la estructura resuelve el problema sin agregar complejidad innecesaria:

function balanceado(expresion) {
  const pila = [];
  for (const char of expresion) {
    if (char === '(') pila.push(char);
    if (char === ')') {
      if (!pila.pop()) return false;
    }
  }
  return pila.length === 0;
}

La pila funciona porque su operación push/pop modela directamente la apertura y cierre de paréntesis; cualquier otra estructura complicaría la comprobación.

def contar_palabras(texto):
  frecuencias = {}
  for palabra in texto.split():
    frecuencias[palabra] = frecuencias.get(palabra, 0) + 1
  return frecuencias

El diccionario actúa como tabla hash para acumular ocurrencias en tiempo promedio constante. Sin esa estructura, el algoritmo debería buscar cada palabra en una lista, elevando el costo a O(n2).

Al elegir una estructura compatible logras algoritmos predecibles, medibles y mucho más fáciles de mantener.