6 - Operaciones básicas (visión introductoria)

Cada estructura de datos ofrece operaciones esenciales que determinan su utilidad real. Comprenderlas a nivel conceptual permite estimar costos, detectar cuellos de botella y escoger la variante más adecuada para cada problema. En esta sección repasamos las acciones más frecuentes y analizamos cómo cambian según el tipo de estructura.

Antes de implementar, conviene identificar qué combinación de operaciones dominará al resto. Elegir una lista enlazada por su facilidad de inserción no sirve de mucho si la mayoría de las operaciones serán búsquedas aleatorias; del mismo modo, un array contiguo brilla cuando las consultas son más importantes que las modificaciones.

6. Operaciones básicas (visión introductoria)

Las siguientes subsecciones cubren insertar, eliminar, buscar, acceder, recorrer y ordenar de manera introductoria. Cada punto incluye observaciones sobre la complejidad y consejos prácticos para llevar estas ideas a código en el mundo real.

6.1 Inserción

Insertar significa crear un nuevo nodo o desplazar elementos para hacerle lugar. En arrays y vectores contiguos la inserción al final suele ser O(1) amortizado, mientras que insertar en el medio obliga a mover todo lo que está a la derecha. En listas enlazadas, agregar cerca de un nodo conocido no cuesta más que reasignar punteros.

  • Estrategias comunes: insertar al final (append), al inicio (prepend) o en orden según una clave.
  • Reservas anticipadas: funciones como reserve() en C++ o ensureCapacity() en Java minimizan la cantidad de realocaciones.
  • Validaciones: verificar límites y duplicados (en conjuntos) evita corromper invariantes.

6.2 Eliminación

Eliminar implica retirar un elemento y cerrar el espacio que deja. En estructuras contiguas es necesario desplazar los elementos posteriores; en estructuras enlazadas alcanza con saltar el nodo a eliminar. También es importante liberar recursos asociados para prevenir fugas de memoria, sobre todo cuando guardamos objetos pesados o descriptores de archivos.

function eliminarElemento(lista, indice) {
  if (indice < 0 || indice >= lista.length) return false;
  for (let i = indice; i < lista.length - 1; i++) {
    lista[i] = lista[i + 1];
  }
  lista.length -= 1; // Contrae el array
  return true;
}

En estructuras de prioridad (como heaps) la eliminación general obliga a reacomodar el árbol para mantener el orden parcial. Por eso muchos contenedores solo exponen la eliminación de extremos (pop) y no de posiciones arbitrarias.

6.3 Búsqueda

La búsqueda es la operación que localiza un elemento a partir de una clave o atributo. Puede ser secuencial o aprovechar alguna estructura adicional como índices, tablas hash o árboles balanceados. Los algoritmos de búsqueda binaria requieren colecciones ordenadas y dividen el espacio en mitades sucesivas.

  • Lineal: recorre todos los elementos hasta encontrar coincidencia; es sencilla pero costosa en colecciones enormes.
  • Basada en hashing: calcula una posición probable para la clave y solo compara unos pocos elementos.
  • Indexada: estructuras como TreeMap o std::map garantizan tiempos logarítmicos al mantener el orden en todo momento.

6.4 Acceso

Acceder es leer o modificar un elemento conocido. En arrays basta con calcular la dirección base más el desplazamiento y se obtiene un tiempo O(1). En estructuras enlazadas primero debemos recorrer desde el inicio o desde un iterador guardado. Cuando el acceso aleatorio es crítico conviene elegir estructuras contiguas o, en su defecto, mantener tablas auxiliares de punteros.

Algunas colecciones podrían exponer solo iteradores (como las colas), lo que obliga a seguir las reglas del productor/consumidor. Respetar esas interfaces evita condiciones de carrera y accesos invalidos a nodos que podrían reciclarse.

6.5 Recorrido / Iteración

Recorrer significa visitar todos los elementos en un orden definido. Arrays y listas ofrecen recorridos secuenciales; árboles y grafos pueden requerir estrategias depth-first o breadth-first. En cualquier escenario conviene conocer la complejidad temporal y evitar trabajo extra por cada nodo.

  • Iteradores: abstraen los detalles internos y permiten escribir bucles uniformes (for...of, iteradores de C++).
  • Recorridos perezosos: generan datos sobre la marcha para no cargar todos los elementos a la vez, ideal en flujos infinitos.
  • Visitas consistentes: en estructuras mutables debemos fijar reglas (copy-on-write, bloqueos) para que el recorrido vea un estado coherente.

6.6 Ordenación (solo conceptual)

Ordenar reacomoda elementos según una relación (numérica, alfabética, por fecha). Los algoritmos disponibles van desde los simples O(n^2) como burbuja hasta opciones O(n log n) como mergesort o quicksort. Antes de elegir uno debemos evaluar si la colección cabe en memoria, si el criterio de comparación es estable y si el contenedor ofrece atajos (por ejemplo, listas doblemente enlazadas con sort nativo).

En sistemas interactivos es común combinar ordenación parcial con estructuras auxiliares: mantener una heap para recuperar el menor de un conjunto, o usar ordenamientos incrementales que solo ajustan las regiones afectadas por nuevos datos. Aunque esta sección es conceptual, más adelante enlazaremos estos principios con implementaciones concretas.