No alcanza con almacenar valores.
También debemos decidir:
Un TDA define:
qué datos tenemos y qué podemos hacer con ellos
pero no necesariamente:
cómo están implementadas esas operaciones
Define la interfaz y el comportamiento.
TDA
|
----------------
| |
Datos Operaciones
Decide cómo representarlos internamente.
arreglo
lista enlazada
...
Podríamos definir operaciones como:
agregar(x)
retirar(x)
buscar(x)
cantidad()
Otra selección de operaciones podría definir otro TDA.
Ej.:
[ Guardarropa ]
El programa que utiliza un TDA debería depender de:
sus operaciones ->
y no de:
sus detalles internos ->
Ej.:
[ Guardarropa ]
guardarropa.retirar("camiseta azul")Podríamos cambiar:
Lista
↓
implementación con arreglo
por:
Lista
↓
implementación con nodos
sin cambiar el código que usa el TDA...
Ej.:
[ Guardarropa ]
por:
Ropero
Vestidor
Ej.:
[ Guardarropa ]
Iniciamos con operaciones básicas:
agregar(x)
retirar(x)
buscar(x)
cantidad()
Agregamos más operaciones:
listarPorColor(color)
listarPorTipo(tipo)
retirarUltimaGuardada()
vaciar()
guardarropa.vaciar()Una lista:
A₀, A₁, A₂, ... , Aₙ₋₁
tiene:
posición 0 1 2 3 4
┌────┬────┬────┬────┬────┐
lista │ 34 │ 12 │ 52 │ 16 │ 12 │
└────┴────┴────┴────┴────┘
¿Es lo mismo buscar un elemento que acceder a una posición?
find(x)
findKth(k)
insert(x, pos)
remove(x)
next(pos)
previous(pos)
makeEmpty()
El TDA especifica las operaciones.
Lista inicial:
34, 12, 52, 16, 12
find(52)
insert(X, 2)
remove(52)
Arreglo
Lista enlazada
Mismo concepto abstracto.
índice 0 1 2 3
┌──────┬──────┬──────┬──────┐
│ A₀ │ A₁ │ A₂ │ A₃ │
└──────┴──────┴──────┴──────┘
Los elementos están organizados por posición.
findKth(k) → O(1)
Big O es siempre 1 para cualquier elemento en la posición k
Queremos insertar X al principio:
ANTES
[A][B][C][D]
Hay que hacer lugar:
DESPUÉS
[X][A][B][C][D]
O(1)
O(N)
porque puede ser necesario desplazar elementos.
Los elementos no necesitan estar contiguos en memoria.
┌───────┬──────┐
│ A₀ │ next │──┐
└───────┴──────┘ │
▼
┌───────┬──────┐
│ A₁ │ next │──┐
└───────┴──────┘ │
▼
┌───────┬──────┐
│ A₂ │ NULL │
└───────┴──────┘
Para acceder al elemento número 3:
inicio
↓
[A] → [B] → [C] → [D]
Debemos recorrer los nodos.
findKth(i)O(i)
Supongamos:
[A] → [B] → [C]
Queremos insertar X después de A.
[A] ──┐
▼
[X] → [B] → [C]
No desplazamos todos los elementos.
| Operación | Arreglo | Lista enlazada |
|---|---|---|
| Acceso por posición | O(1) | O(N) |
| Buscar elemento | O(N) | O(N) |
| Insertar al frente | O(N) | O(1) |
| Eliminar al frente | O(N) | O(1) |
Para insertar/eliminar en O(1) en una lista enlazada debemos tener localizada la posición correspondiente.
Conviene cuando predominan:
Conviene cuando predominan:
Existe una estructura:
Elegir una estructura de datos implica preguntarse:
¿Qué operaciones voy a realizar con mayor frecuencia?
Last In, First Out
┌─────┐
top → │ C │ ← último en entrar
├─────┤
│ B │
├─────┤
│ A │
└─────┘
El último elemento que entra es el primero que sale.
push(x)
agrega un elemento.
pop()
elimina el elemento del tope.
top()
consulta el elemento del tope.
isEmpty()
makeEmpty()
Partimos de:
[ ]
Ejecutamos:
push(10)
push(20)
push(30)
pop()
push(40)
top()
top()?Podemos utilizar una lista enlazada:
push → insertar al frente
pop → eliminar al frente
top → consultar el frente
Todas estas operaciones pueden realizarse en:
O(1)
topOfStack
↓
┌────┬────┬────┬────┬────┐
│ A │ B │ C │ │ │
└────┴────┴────┴────┴────┘
0 1 2 3 4
push → avanzar topOfStack
pop → retroceder topOfStack
top → acceder a topOfStack
First In, First Out
entrada
↓
[D] [C] [B] [A]
↑
salida
El primero que entra es el primero que sale.
enqueue(x)
inserta al final.
dequeue()
elimina y retorna el elemento del frente.
También:
isEmpty()
makeEmpty()
Partimos de:
[ ]
Ejecutamos:
enqueue(10)
enqueue(20)
enqueue(30)
dequeue()
enqueue(40)
dequeue()
push pop
↓ ↑
[C] ← último en entrar
[B]
[A] ← primero en entrar
LIFO
enqueue → [C][B][A] → dequeue
FIFO
Podemos mantener dos referencias:
front back
↓ ↓
[A] ─────→ [B] ─────→ [C] ─────→ [D]
Entonces:
enqueue → agregar al final
dequeue → eliminar al frente
Ambas operaciones:
O(1)
Tenemos un problema.
[A][B][C][D][ ][ ]
↑
front
Después de varios dequeue:
[ ][ ][C][D][ ][ ]
↑
front
¿Tenemos que desplazar C y D?
Índice: 0 1 2 3 4 5
┌────┬────┬────┬────┬────┬────┐
│ G │ │ C │ D │ E │ F │
└────┴────┴────┴────┴────┴────┘
↑ ↑
back front
Cuando llega al final del arreglo, la siguiente insersión es en 0.
Orden lógico de la cola:
C → D → E → F → G
Se hace circular el recorrido de los punteros back y from
Operaciones quedan siempre en O(1)
theArray
front
back
currentSize
Con esas variables podemos distinguir:
Nodo
┌───────────────┐
│ dato │
├───────────────┤
│ siguiente ────┼──→
└───────────────┘
Dos cosas:
ESTRUCTURA Nodo
dato: entero
siguiente: Nodo
FIN ESTRUCTURA
Y una lista puede comenzar simplemente con:
ESTRUCTURA Lista
primerElemento: Nodo
FIN ESTRUCTURA
FUNCIÓN crearNodo(valor)
nodo ← nuevo Nodo
nodo.dato ← valor
nodo.siguiente ← NULL
RETORNAR nodo
FIN FUNCIÓN
[dato | NULL]
Primera pregunta:
SI lista.primerElemento = NULL
Entonces:
lista.primerElemento ← nuevoNodo
Caso trivial.
Tenemos que encontrar el último:
actual ← lista.primerElemento
MIENTRAS actual.siguiente ≠ NULL
actual ← actual.siguiente
FIN MIENTRAS
Luego:
actual.siguiente ← nuevoNodo
Antes:
[A] → [B] → [C] → NULL
Creamos:
[X] → NULL
Recorremos:
A → B → C
Finalmente:
[A] → [B] → [C] → [X] → NULL
La inserción físicamente requiere:
C.next ← X
un cambio muy pequeño.
Pero...
C?[A] → [B] → [C]
No queremos memorizar una tabla.
Queremos poder explicar:
¿Por qué esta operación cuesta O(1)?
o:
¿Por qué esta operación necesita recorrer N elementos?
En grupos, dibujen qué ocurre paso a paso.
10, 20, 30
insertar 15 después de 10
eliminar 30
push A
push B
push C
pop
push D
enqueue A
enqueue B
enqueue C
dequeue
enqueue D
Para el ejercicio anterior:
arreglo
y luego como
nodos enlazados
No es aprender:
"las listas enlazadas son mejores"
ni:
"los arreglos son más rápidos"
El objetivo es poder decir:
para estas operaciones y bajo estas condiciones, elegiría esta representación porque...
Una estructura puede mejorar una operación...
acceso rápido
↕
inserción rápida
simplicidad
↕
flexibilidad
memoria
↕
tiempo
Define qué podemos hacer.
Define cómo representamos los datos.
Define cómo realizamos una operación.
Ante un problema nuevo:
Se trata de poder:
dibujar → simular → comparar → justificar
Weiss, M. A.
Data Structures and Algorithm Analysis in C++
4th ed., 2014
TDA · Listas · Pilas · Colas