Volver al blog

Estructuras de Datos en la universidad: complejidad, listas, pilas, árboles y ejercicios resueltos en Java

Estructuras de Datos en la universidad: complejidad, listas, pilas, árboles y ejercicios resueltos en Java

Estructuras de Datos (en muchos planes, Algoritmos y Estructuras de Datos) es la asignatura de segundo de Ingeniería Informática en la que dejas de usar listas, pilas, colas, árboles y tablas hash ya hechas y pasas a implementarlas tú con nodos y referencias, y a justificar cuánto cuesta cada operación con la notación O. Para aprobarla necesitas tres cosas: implementar desde cero una lista enlazada, una pila, una cola y un árbol binario de búsqueda que funcionen también en los casos límite, saber calcular la complejidad de un fragmento de código y elegir la estructura adecuada cuando el enunciado te da un problema y no el nombre de la estructura.

Lo difícil no es la cantidad de temario. Es que el código parece correcto hasta que la lista está vacía, tiene un solo elemento o borras el último, y que la complejidad no se aprende memorizando una tabla. Ya contamos por qué es la asignatura filtro de segundo; aquí va el detalle: qué entra, la tabla de complejidades, cinco ejercicios resueltos en Java, los errores que más vemos en clase y cómo prepararla.

Qué entra en Estructuras de Datos

El nombre, el curso y el lenguaje cambian según la universidad. Lo normal en España es darla en segundo, en Java, C++ (a menudo con la STL) o C. Unas incluyen grafos; otras los dejan, junto con los esquemas algorítmicos, para una asignatura posterior de Algoritmia. Revisa tu guía docente, pero los bloques suelen ser estos:

Bloque Qué te piden Lo que más falla
Análisis de algoritmos Notación O, mejor y peor caso, contar operaciones, coste de algoritmos recursivos Confundir O(n) con O(n log n) y no saber justificarlo
Tipos abstractos de datos (TAD) Separar la especificación (qué operaciones hay) de la implementación Usar la colección de Java en lugar de implementar el TAD
Estructuras lineales Listas enlazadas simples y dobles, pilas, colas, con array y con nodos Perder la referencia al resto de la lista al insertar o borrar
Árboles Árbol binario, árbol binario de búsqueda (ABB), recorridos, altura; AVL en muchas universidades Recursión sin caso base o con el caso base mal puesto
Colas de prioridad Montículo binario (heap), insertar y extraer el mínimo, heapsort Calcular mal los índices de hijo y padre en el array
Tablas hash Función hash, colisiones, encadenamiento, direccionamiento abierto No redefinir equals y hashCode a la vez
Grafos Matriz y listas de adyacencia, recorridos en anchura y profundidad, caminos mínimos según la universidad Olvidar marcar los nodos visitados

Listas, pilas, colas y ABB son lo que más se repite en la parte práctica, y lo que cubren los ejercicios de abajo. Si llegas con la base de primero floja, empieza por Fundamentos de Programación: aquí se da por hecho que dominas bucles, recursividad y clases.

Qué es un TAD (y por qué no vale usar LinkedList)

Un tipo abstracto de datos (TAD) es un tipo definido por las operaciones que ofrece y lo que hace cada una, sin decir cómo se guardan los datos por dentro. La pila es un TAD con las operaciones apilar, desapilar, cima y estaVacia; se puede implementar con un array o con nodos enlazados, y quien la usa no tiene por qué saber cuál de las dos.

En Java, el TAD es la interfaz y la implementación es la clase. Si el enunciado dice "implementa una pila", usar java.util.Stack, ArrayDeque o LinkedList es no hacer el ejercicio: las colecciones de Java son para resolver un problema, no para construir la estructura. Si clases, genéricos e interfaces todavía te bailan, repasa antes la programación orientada a objetos explicada con un ejemplo.

Complejidad de las estructuras de datos: la tabla que tienes que saber explicar

La notación O (Big O) describe cómo crece el número de operaciones de un algoritmo cuando crece el tamaño de la entrada, n, quedándose con el término que más pesa y sin constantes. O(1) es coste constante, O(log n) logarítmico, O(n) lineal, O(n log n) lo típico de las buenas ordenaciones y O(n²) lo de dos bucles anidados sobre los mismos datos.

Estructura Acceder por posición Buscar un valor Insertar Borrar
Array dinámico (ArrayList) O(1) O(n); O(log n) si está ordenado y usas búsqueda binaria Al final O(1) amortizado; al principio o en medio O(n) Al final O(1); al principio o en medio O(n)
Lista enlazada simple O(n) O(n) Al principio O(1); al final O(1) si guardas el último nodo, O(n) si no Al principio O(1); un valor concreto O(n)
Pila (array o nodos) Solo la cima, O(1) No es su uso Apilar O(1) Desapilar O(1)
Cola (array circular o nodos) Solo el frente, O(1) No es su uso Encolar O(1) Desencolar O(1)
ABB no equilibrado No aplica O(log n) en promedio, O(n) en el peor caso O(log n) en promedio, O(n) en el peor caso O(log n) en promedio, O(n) en el peor caso
ABB equilibrado (AVL, rojinegro) No aplica O(log n) O(log n) O(log n)
Tabla hash (HashMap) No aplica O(1) en promedio, O(n) en el peor caso O(1) en promedio O(1) en promedio
Montículo binario Ver el mínimo O(1) O(n) O(log n) Extraer el mínimo O(log n)

Tres matices que preguntan mucho. El ABB no equilibrado es O(n) en el peor caso porque, si insertas las claves ordenadas, degenera en una lista (lo verás en el ejercicio 4). El O(1) de la tabla hash es en promedio: si todas las claves caen en la misma posición, buscar es recorrer una lista. Y "O(1) amortizado" significa que, de vez en cuando, el ArrayList se llena y copia todo a un array mayor en O(n), pero repartido entre todas las inserciones sale constante.

Cinco ejercicios resueltos de estructuras de datos en Java

Casi todas las prácticas y exámenes son variaciones de estos. Están en Java 17. Intenta cada uno antes de mirar la solución y dibuja los nodos en papel.

1. Lista enlazada simple: insertar al principio, al final y borrar

Enunciado: implementa una lista enlazada simple genérica con las operaciones insertarPrincipio, insertarFinal, borrar (la primera aparición de un valor) y tamano. No puedes usar ninguna colección de Java.

public class ListaEnlazada<T> {

    private static class Nodo<T> {
        T dato;
        Nodo<T> siguiente;

        Nodo(T dato, Nodo<T> siguiente) {
            this.dato = dato;
            this.siguiente = siguiente;
        }
    }

    private Nodo<T> primero;
    private int tamano;

    public void insertarPrincipio(T dato) {
        primero = new Nodo<>(dato, primero);   // el nuevo apunta al antiguo primero
        tamano++;
    }

    public void insertarFinal(T dato) {
        Nodo<T> nuevo = new Nodo<>(dato, null);
        if (primero == null) {                 // lista vacía
            primero = nuevo;
        } else {
            Nodo<T> actual = primero;
            while (actual.siguiente != null) { // avanza hasta el último
                actual = actual.siguiente;
            }
            actual.siguiente = nuevo;
        }
        tamano++;
    }

    public boolean borrar(T dato) {
        if (primero == null) {
            return false;
        }
        if (primero.dato.equals(dato)) {       // el que se borra es el primero
            primero = primero.siguiente;
            tamano--;
            return true;
        }
        Nodo<T> anterior = primero;
        while (anterior.siguiente != null && !anterior.siguiente.dato.equals(dato)) {
            anterior = anterior.siguiente;
        }
        if (anterior.siguiente == null) {      // no estaba
            return false;
        }
        anterior.siguiente = anterior.siguiente.siguiente; // se salta el nodo
        tamano--;
        return true;
    }

    public int tamano() {
        return tamano;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder("[");
        for (Nodo<T> n = primero; n != null; n = n.siguiente) {
            sb.append(n.dato);
            if (n.siguiente != null) {
                sb.append(", ");
            }
        }
        return sb.append("]").toString();
    }

    public static void main(String[] args) {
        ListaEnlazada<Integer> lista = new ListaEnlazada<>();
        lista.insertarFinal(2);
        lista.insertarFinal(3);
        lista.insertarPrincipio(1);
        System.out.println(lista + " tamaño " + lista.tamano()); // [1, 2, 3] tamaño 3
        lista.borrar(1);   // primero
        lista.borrar(3);   // último
        lista.borrar(99);  // no existe
        System.out.println(lista + " tamaño " + lista.tamano()); // [2] tamaño 1
        lista.borrar(2);   // único elemento
        System.out.println(lista + " tamaño " + lista.tamano()); // [] tamaño 0
    }
}

Lo que hay que entender: en insertarPrincipio el nuevo nodo se crea apuntando al antiguo primero y después primero pasa a ser el nuevo; al revés, pierdes el resto de la lista. Para borrar recorres con anterior y miras anterior.siguiente, porque en una lista simple no puedes volver atrás. El primer nodo es un caso aparte porque no tiene anterior. El main prueba justo los casos límite: el primero, el último, uno que no existe y el único que queda. insertarFinal es O(n); si te lo piden en O(1), añade un atributo ultimo y mantenlo al día en todas las operaciones.

En C++ la lógica es la misma con punteros, pero la memoria de cada nodo la liberas tú.

struct Nodo {
    int dato;
    Nodo* siguiente;
};

void insertarPrincipio(Nodo*& primero, int dato) {
    primero = new Nodo{dato, primero};
}

void borrarPrincipio(Nodo*& primero) {
    if (primero == nullptr) return;
    Nodo* aBorrar = primero;
    primero = primero->siguiente;
    delete aBorrar;               // en C++ la memoria la liberas tú
}

El Nodo*& permite que la función cambie el primero de quien la llama; sin el & modificas una copia.

2. Pila con nodos y comprobar paréntesis equilibrados

Enunciado: implementa el TAD Pila con nodos enlazados y úsalo para comprobar si los paréntesis, corchetes y llaves de una expresión están equilibrados.

import java.util.NoSuchElementException;

public class Pila<T> {

    private static class Nodo<T> {
        final T dato;
        final Nodo<T> debajo;

        Nodo(T dato, Nodo<T> debajo) {
            this.dato = dato;
            this.debajo = debajo;
        }
    }

    private Nodo<T> cima;

    public void apilar(T dato) {
        cima = new Nodo<>(dato, cima);
    }

    public T desapilar() {
        if (estaVacia()) {
            throw new NoSuchElementException("Pila vacía");
        }
        T dato = cima.dato;
        cima = cima.debajo;
        return dato;
    }

    public T cima() {
        if (estaVacia()) {
            throw new NoSuchElementException("Pila vacía");
        }
        return cima.dato;
    }

    public boolean estaVacia() {
        return cima == null;
    }
}
public class Parentesis {

    static boolean equilibrada(String expresion) {
        Pila<Character> pila = new Pila<>();
        for (char c : expresion.toCharArray()) {
            if (c == '(' || c == '[' || c == '{') {
                pila.apilar(c);
            } else if (c == ')' || c == ']' || c == '}') {
                if (pila.estaVacia()) {
                    return false;                       // cierra sin haber abierto
                }
                char abierto = pila.desapilar();
                if ((c == ')' && abierto != '(')
                        || (c == ']' && abierto != '[')
                        || (c == '}' && abierto != '{')) {
                    return false;                       // no casan
                }
            }
        }
        return pila.estaVacia();                        // no puede quedar nada abierto
    }

    public static void main(String[] args) {
        String[] pruebas = {"{[a + b] * (c - d)}", "([)]", "((a + b)", "a + b)", ""};
        for (String p : pruebas) {
            System.out.println("\"" + p + "\" -> " + equilibrada(p));
        }
    }
}

Salida: true, false, false, false y true.

Lo que hay que entender: la pila con nodos es la lista del ejercicio 1 insertando y borrando solo por el principio, por eso todo es O(1). El algoritmo tiene tres formas de fallar y las tres tienen que estar: cerrar con la pila vacía (a + b)), cerrar algo que no casa con el último abierto (([)]) y dejar algo sin cerrar (((a + b)). Mucha gente olvida la última y devuelve true al acabar el bucle.

3. Cola con array circular

Enunciado: implementa una cola de capacidad fija sobre un array, de forma que encolar y desencolar sean O(1).

import java.util.NoSuchElementException;

public class ColaCircular<T> {

    private final T[] datos;
    private int frente;   // posición del primero
    private int tamano;

    @SuppressWarnings("unchecked")
    public ColaCircular(int capacidad) {
        datos = (T[]) new Object[capacidad];
    }

    public void encolar(T dato) {
        if (tamano == datos.length) {
            throw new IllegalStateException("Cola llena");
        }
        int fin = (frente + tamano) % datos.length;   // primera posición libre
        datos[fin] = dato;
        tamano++;
    }

    public T desencolar() {
        if (tamano == 0) {
            throw new NoSuchElementException("Cola vacía");
        }
        T dato = datos[frente];
        datos[frente] = null;                         // no dejes referencias colgando
        frente = (frente + 1) % datos.length;
        tamano--;
        return dato;
    }

    public boolean estaVacia() {
        return tamano == 0;
    }

    public static void main(String[] args) {
        ColaCircular<String> cola = new ColaCircular<>(3);
        cola.encolar("Ana");
        cola.encolar("Luis");
        cola.encolar("Marta");
        System.out.println(cola.desencolar());   // Ana
        cola.encolar("Pablo");                   // ocupa la posición 0, que había quedado libre
        while (!cola.estaVacia()) {
            System.out.println(cola.desencolar()); // Luis, Marta, Pablo
        }
    }
}

Lo que hay que entender: la versión ingenua desplaza todos los elementos a la izquierda al desencolar, y eso es O(n). Con el array circular no se mueve nada: avanza frente y el % hace que, al llegar al final del array, se vuelva a la posición 0. Guardar tamano resuelve cómo distinguir cola llena de cola vacía, porque en ambos casos frente y final pueden coincidir. (T[]) new Object[capacidad] es la forma habitual de crear un array genérico, ya que new T[capacidad] no compila.

4. Árbol binario de búsqueda: insertar, buscar, inorden y altura

Enunciado: implementa un árbol binario de búsqueda de enteros sin duplicados con las operaciones insertar, contiene, recorrido inorden y altura. Comprueba qué pasa con la altura si insertas las claves ya ordenadas.

public class ArbolBinarioBusqueda {

    private static class Nodo {
        int clave;
        Nodo izq, der;

        Nodo(int clave) {
            this.clave = clave;
        }
    }

    private Nodo raiz;

    public void insertar(int clave) {
        raiz = insertar(raiz, clave);
    }

    private Nodo insertar(Nodo nodo, int clave) {
        if (nodo == null) {                       // caso base: hueco encontrado
            return new Nodo(clave);
        }
        if (clave < nodo.clave) {
            nodo.izq = insertar(nodo.izq, clave);
        } else if (clave > nodo.clave) {
            nodo.der = insertar(nodo.der, clave);
        }                                         // si es igual, no se duplica
        return nodo;
    }

    public boolean contiene(int clave) {
        Nodo actual = raiz;
        while (actual != null) {
            if (clave == actual.clave) {
                return true;
            }
            actual = clave < actual.clave ? actual.izq : actual.der;
        }
        return false;
    }

    public void inorden() {
        inorden(raiz);
        System.out.println();
    }

    private void inorden(Nodo nodo) {
        if (nodo == null) {
            return;
        }
        inorden(nodo.izq);
        System.out.print(nodo.clave + " ");
        inorden(nodo.der);
    }

    public int altura() {
        return altura(raiz);
    }

    private int altura(Nodo nodo) {
        if (nodo == null) {
            return 0;                             // árbol vacío: altura 0
        }
        return 1 + Math.max(altura(nodo.izq), altura(nodo.der));
    }

    public static void main(String[] args) {
        ArbolBinarioBusqueda abb = new ArbolBinarioBusqueda();
        for (int c : new int[] {50, 30, 70, 20, 40, 60, 80}) {
            abb.insertar(c);
        }
        abb.inorden();                                  // 20 30 40 50 60 70 80
        System.out.println(abb.contiene(60));           // true
        System.out.println(abb.contiene(65));           // false
        System.out.println("Altura: " + abb.altura());  // 3

        ArbolBinarioBusqueda degenerado = new ArbolBinarioBusqueda();
        for (int c = 1; c <= 7; c++) {
            degenerado.insertar(c);                     // entran ya ordenadas
        }
        System.out.println("Altura degenerado: " + degenerado.altura()); // 7
    }
}

Lo que hay que entender: el patrón nodo.izq = insertar(nodo.izq, clave) es la clave. El método devuelve el subárbol modificado y el padre lo vuelve a enganchar, así que el árbol vacío se resuelve solo, sin un if aparte. El inorden de un ABB siempre sale ordenado, y es la forma más rápida de comprobar que tu inserción está bien.

Siete claves dan altura 3 en buen orden y altura 7 si entran ordenadas: el segundo árbol es una lista disfrazada y buscar en él es O(n). Por eso existen los árboles equilibrados como el AVL, que usan rotaciones para mantener la altura en O(log n). Ojo con el convenio: aquí el árbol vacío tiene altura 0 y una hoja, 1; algunos profesores usan -1 y 0.

5. Calcular la complejidad de fragmentos de código

Enunciado: indica la complejidad en el peor caso de cada método en función de n (el tamaño del array o el valor del parámetro) y justifícala.

public class Complejidad {

    // A
    static int sumaPares(int[] v) {
        int suma = 0;
        for (int i = 0; i < v.length; i += 2) {
            suma += v[i];
        }
        return suma;
    }

    // B
    static boolean hayRepetidos(int[] v) {
        for (int i = 0; i < v.length; i++) {
            for (int j = i + 1; j < v.length; j++) {
                if (v[i] == v[j]) {
                    return true;
                }
            }
        }
        return false;
    }

    // C
    static int pasos(int n) {
        int contador = 0;
        for (int i = n; i > 1; i /= 2) {
            contador++;
        }
        return contador;
    }

    // D
    static int bucleMixto(int n) {
        int contador = 0;
        for (int i = 0; i < n; i++) {
            for (int j = 1; j < n; j *= 2) {
                contador++;
            }
        }
        return contador;
    }

    // E
    static long fib(int n) {
        if (n <= 1) {
            return n;
        }
        return fib(n - 1) + fib(n - 2);
    }
}
Fragmento Complejidad Por qué
A O(n) n/2 vueltas; las constantes se eliminan
B O(n²) Sin repetidos hace n(n-1)/2 comparaciones: que el interno empiece en i + 1 no lo salva
C O(log n) i se divide entre 2 en cada vuelta
D O(n log n) n vueltas externas, y en cada una unas log₂ n internas porque j se duplica
E O(2ⁿ) Cada llamada genera dos más y repite cálculos (la cota exacta crece como 1,618ⁿ)

Lo que hay que entender: no cuentes bucles, mira cómo cambia la variable de control. Si suma o resta una cantidad fija, las vueltas son proporcionales a n; si multiplica o divide, a log n. Puedes comprobarlo contando iteraciones con n = 1.000, 2.000 y 4.000: en B se multiplican casi por 4 (499.500, 1.999.000, 7.998.000), en C suman una (9, 10, 11) y en D algo más que se duplican (10.000, 22.000, 48.000). En los recursivos, mira cuántas llamadas hace cada llamada y cuánto se reduce el problema: la altura del ejercicio 4 es O(n) y una búsqueda binaria recursiva, O(log n).

Los errores que más suspenden en Estructuras de Datos

Perder la referencia al resto de la lista. Escribir primero = nuevo; antes de nuevo.siguiente = primero; deja la lista con un solo nodo y el resto inaccesible. Antes de reasignar una referencia, pregúntate qué nodos dejan de estar apuntados.

No tratar la estructura vacía ni la de un elemento. Es donde fallan casi todas las implementaciones: borrar el único nodo, desencolar de una cola vacía, buscar en un árbol sin raíz. Pruébalo siempre antes de entregar.

NullPointerException al recorrer nodos. Ocurre al leer actual.siguiente.dato cuando actual.siguiente es null. En el while, comprueba el null primero: && no evalúa la segunda parte si la primera es falsa. Si te pasa a menudo, mira cómo depurar código paso a paso y sigue las referencias con el depurador.

Recursión sin caso base, o con el caso base mal puesto. En árboles, el caso base casi siempre es nodo == null. Si lo olvidas, StackOverflowError; si lo pones en las hojas en lugar de en el null, el método falla con los nodos que tienen un solo hijo.

Confundir O(n) con O(n log n). Pasa sobre todo con bucles anidados en los que el interno multiplica o divide. Dos bucles no significan O(n²): mira cuánto avanza cada uno.

Usar la colección de Java cuando piden implementar el TAD. Si el enunciado dice "implementa una cola", LinkedList o ArrayDeque dentro de tu clase hace que, en muchas correcciones, ese apartado no puntúe.

No dibujar los nodos. Sin cajas y flechas en papel se tarda más y se falla en los casos límite. Además, en el examen escrito es habitual que pidan dibujar la estructura tras varias operaciones.

Redefinir equals sin hashCode en las claves de un HashMap. Dos objetos iguales según equals pero con distinto hashCode van a posiciones distintas de la tabla:

// Esta clase redefine equals pero no hashCode
Map<PuntoMal, String> mapa = new HashMap<>();
mapa.put(new PuntoMal(1, 2), "A");
System.out.println(mapa.get(new PuntoMal(1, 2)));   // null

Redefine siempre los dos a la vez (por ejemplo, con Objects.hash(x, y)) o usa un record, que los genera por ti.

Cómo preparar Estructuras de Datos

Implementa cada estructura una vez desde cero, sin mirar. Lista enlazada, pila, cola y ABB. Cuando te salgan sin apuntes, haz las variantes: lista doblemente enlazada, lista con puntero al último, cola con nodos, borrado en el ABB (el caso del nodo con dos hijos es el que más preguntan).

Escribe primero las pruebas de los casos límite. Un main que pruebe vacío, un elemento, el primero, el último y un valor que no existe. Es lo que hará tu profesor al corregir.

Calcula la complejidad de todo lo que programes. Escribe en un comentario la O de cada método y por qué. Si no sabes justificarla, aún no lo entiendes.

Practica con enunciados de problema, no de estructura. "Deshacer acciones", "atender por orden de llegada", "buscar rápido por DNI", "la tarea más urgente primero": pila, cola, tabla hash y montículo, sin que te den el nombre.

Haz exámenes de otros años con reloj, y sin IDE si el examen es en papel. Para organizar las últimas semanas, aquí tienes cómo preparar un examen práctico de programación.

Cuándo pedir ayuda

Si implementas las estructuras sin copiar, sabes por qué fallan cuando fallan y puedes justificar la complejidad de tu código, vas bien. Merece la pena buscar ayuda cuando:

  • Tu lista funciona hasta que borras el último elemento y no consigues ver por qué.
  • Entiendes la recursión cuando la lees, pero no sabes escribir la altura o el borrado del ABB sin mirar.
  • Te dan un fragmento de código y no sabes si es O(n), O(n log n) u O(n²).
  • Arrastras la asignatura de otro año y la necesitas para matricularte de las de tercero.

En nuestras clases particulares para universidad trabajamos con tus prácticas y exámenes anteriores: dibujamos juntos los nodos, ejecutamos paso a paso y lo implementas tú hasta que te sale solo. Si tu asignatura es en Java y lo que falla es el lenguaje, tienes clases de Java online; si es en C o C++ y te pierdes con los punteros, clases de C y C++ online. Son clases online uno a uno a 25 € la hora, sin matrícula ni permanencia.

Preguntas frecuentes

¿Qué es Algoritmos y Estructuras de Datos?

Es la asignatura de Ingeniería Informática que enseña a organizar los datos en memoria (listas, pilas, colas, árboles, tablas hash, grafos) y a medir con la notación O cuánto cuestan los algoritmos que los usan. Según la universidad se llama Estructuras de Datos, Estructuras de Datos y Algoritmos o Algoritmos y Estructuras de Datos, y suele darse en segundo.

¿Es difícil Estructuras de Datos?

Es una de las asignaturas que más se arrastran en segundo. Cuesta implementar las estructuras con nodos de forma que funcionen en los casos límite y justificar la complejidad. Con buena base de primero y práctica implementando cada estructura desde cero, se aprueba.

¿En qué lenguaje se da Estructuras de Datos en España?

Depende de la universidad: lo más habitual es Java o C++, y algunas usan C. Los conceptos son los mismos. La diferencia es que en C y C++ trabajas con punteros y liberas tú la memoria de los nodos, mientras que en Java lo hace el recolector de basura.

¿Qué diferencia hay entre estructuras de datos estáticas y dinámicas?

Una estructura estática tiene un tamaño fijo decidido al crearla, como un array. Una dinámica crece y decrece durante la ejecución reservando memoria por elemento, como una lista enlazada o un árbol. Las estáticas dan acceso por posición en O(1); las dinámicas no desperdician espacio e insertan sin desplazar elementos.

¿Qué es la notación Big O?

La notación O (Big O) indica cómo crece el tiempo o la memoria de un algoritmo al crecer la entrada, ignorando constantes y términos menores. Si duplicas la entrada, un O(n) tarda el doble, un O(n²) el cuádruple y un O(log n) apenas cambia. En el examen se suele pedir el peor caso.

¿Qué libro de Estructuras de Datos con ejercicios resueltos me recomiendas?

El primero es el que indique tu guía docente, porque los exámenes siguen su notación. Como complemento, "Estructuras de datos y métodos algorítmicos: 213 ejercicios resueltos", de Martí Oliet, Ortega Mallén y Verdejo, es muy usado en España para practicar. Si la asignatura es en Java, "Estructuras de datos en Java" de Mark Allen Weiss es un clásico.

WhatsApp