SLIDE 1 / 8

1. ¿Qué es un TAD? La Interfaz Frente a la Memoria

Un Tipo Abstracto de Dato (TAD) define QUÉ operaciones se pueden hacer (contrato lógico), mientras que la estructura de datos define CÓMO se organizan los bytes en la RAM (implementación física).

TAD Lista<T> (Contrato) agregar(T) | eliminar(idx) obtener(idx) | tamano() Lista Secuencial (Array) Memoria: Bloque contiguo Acceso por índice: O(1) Inserción al frente: O(N) Lista Enlazada (Nodos) Memoria: Nodos dispersos Acceso por índice: O(N) Inserción al frente: O(1)
Concepto Arquitectónico
Contrato Lógico: interface List<T>
Regla de Oro: Programar contra la interfaz
// El cliente declara la interfaz TAD
List<String> lista = new ArrayList<>();
// O cambia la implementación física sin romper nada:
List<String> lista = new LinkedList<>();

2. Memoria Contigua vs Nodos Dispersos en el Heap

En un array, las casillas son adyacentes: saltar al índice 3 se calcula matemáticamente en O(1). En una lista enlazada, los nodos están desparramados en direcciones aleatorias del Heap: para llegar al índice 3 hay que caminar 3 punteros en O(N).

1. Array: Memoria Contigua (Dirección base 0x1000 + i * 4) [0] "Ana" [1] "Leo" [2] "Dibu" [3] "Lau" 2. Lista Enlazada: Direcciones Aleatorias en el Heap @0x3A20 "Ana" → @0x8F14 "Leo" → @0x10B8 "Dibu" → @0x9C44 "Lau"
Simulador de Acceso
Array (Acceso Directo): 1 salto en O(1)
Lista (Caminata de Punteros): 3 saltos en O(N)
// Array: Aritmética de memoria directa
array[3]; // base + 3 * sizeof(elem) -> O(1)

// Lista Enlazada: Desreferenciar punteros
cabeza.siguiente.siguiente.siguiente; // O(N)

3. Anatomía de un Nodo en Memoria

Un nodo es una clase autorreferencial en el Heap con dos compartimentos: la carga útil (dato) y el puntero al próximo nodo (siguiente). El último nodo de la cadena tiene su puntero en null (tierra).

Nodo<String> @0x4B20 T dato "Ana" siguiente Próximo Nodo @0x5C00
Estructura Autorreferencial
Tipo del Puntero: Nodo<T> siguiente
Terminación: siguiente == null
class Nodo<T> {
  T dato;
  Nodo<T> siguiente; // Puntero al mismo tipo

  public Nodo(T dato) {
    this.dato = dato;
    this.siguiente = null;
  }
}

4. Inserción al Inicio: El Baile de Punteros en O(1)

Insertar al frente es inmediato: 1. Crear nuevo nodo, 2. Enlazar nuevo.siguiente = cabeza, 3. Mover cabeza = nuevo. ¡Si invertís el orden de los pasos 2 y 3, perdés toda la lista existente!

cabeza @0x1000 "A" "B" null nuevo "X"
Secuencia de Pasos
Paso Actual: 1. Nace nuevo = new Nodo("X")
Sentencia Java: Nodo nuevo = new Nodo("X");
public void agregarAlInicio(T dato) {
  Nodo<T> nuevo = new Nodo<>(dato);
  nuevo.siguiente = cabeza; // PASO 1: Enlazar primero
  cabeza = nuevo;           // PASO 2: Mover cabeza
}

5. Inserción al Final y Recorrido en O(N)

Para agregar al final sin puntero tail, es obligatorio recorrer toda la lista usando un cursor auxiliar mientras actual.siguiente != null. El costo es O(N) proporcional a la cantidad de elementos.

"A" "B" "C" null actual
Caminata del Cursor
Condición del bucle: while (actual.siguiente != null)
Posición Cursor: actual = Nodo "A"
Nodo actual = cabeza;
while (actual.siguiente != null) {
  actual = actual.siguiente; // Avanza O(N)
}
actual.siguiente = nuevo;

6. Eliminación Intermedia: El Puente de Punteros

Para quitar un nodo intermedio, la flecha del nodo anterior se desvía y conecta directamente con el siguiente: anterior.siguiente = actual.siguiente. Al no tener referencias entrantes, el nodo eliminado es limpiado por el Garbage Collector.

"10" "20" ✕ "30" null
Acción de Puente
Sentencia Clave: anterior.siguiente = actual.siguiente;
Estado Nodo 20: Enlazado en la cadena
anterior.siguiente = actual.siguiente;
actual.siguiente = null; // Limpieza defensiva
// El recolector de basura (GC) destruye el nodo huérfano

7. Variantes: Doblemente Enlazada y Circular

La lista doblemente enlazada agrega un puntero `anterior` en cada nodo permitiendo recorridos bidireccionales y eliminación en O(1) conociendo el nodo. La lista circular enlaza el último nodo de vuelta a la cabeza.

"A" "B"
Modo de Lista
Punteros por Nodo: anterior + siguiente (2 punteros)
Ventaja Clave: Recorrido bidireccional
class NodoDoble<T> {
  T dato;
  NodoDoble<T> anterior;
  NodoDoble<T> siguiente;
}

8. Tabla de Complejidad Big-O: Array vs Lista

Elegir la estructura correcta depende del patrón de uso. Si hacés muchas lecturas por índice aleatorio, el Array es el rey. Si hacés inserciones y eliminaciones continuas al frente, la Lista Enlazada no tiene rival.

Operación ArrayList (Array) LinkedList (Nodos) Acceso get(i) O(1) O(N) Insertar al inicio O(N) O(1) Insertar al final O(1)* O(1)** Búsqueda contains() O(N) O(N) * Amortizado en ArrayList al duplicar. ** Con puntero tail.
Veredicto de Diseño
Lecturas masivas: Usar ArrayList
Inserción frente masiva: Usar LinkedList / Deque
// El 95% de los casos en Java se resuelven con ArrayList
// debido a la localidad espacial y caché L1/L2.