Unidad 4

Fundamentos en Ciencias de la Computación

TDA · Listas · Pilas · Colas

Fundamentos en Ciencias de la Computación

Introducción a los
Tipos de Datos Abstractos

La pregunta de hoy

¿Cómo organizamos datos para poder operar con ellos?

No alcanza con almacenar valores.

También debemos decidir:

  • qué operaciones necesitamos
  • cuánto cuestan
  • qué restricciones aceptamos
  • qué implementación resulta conveniente

Fundamentos en Ciencias de la Computación

Tipo de Dato Abstracto

TDA

Un TDA define:

qué datos tenemos y qué podemos hacer con ellos

pero no necesariamente:

cómo están implementadas esas operaciones

Fundamentos en Ciencias de la Computación

El "qué" y el "cómo"

TDA

Define la interfaz y el comportamiento.

           TDA
            |
     ----------------
     |              |
  Datos         Operaciones

Implementación

Decide cómo representarlos internamente.

arreglo
lista enlazada
...

Ejemplo: TDA Conjunto

Podríamos definir operaciones como:

agregar(x)
retirar(x)
buscar(x)
cantidad()

Otra selección de operaciones podría definir otro TDA.

Las operaciones son una decisión de diseño

Fundamentos en Ciencias de la Computación

Ej.:
[ Guardarropa ]

Una idea fundamental

Encapsulamiento

El programa que utiliza un TDA debería depender de:

sus operaciones            ->

y no de:

sus detalles internos   ->

Fundamentos en Ciencias de la Computación

Ej.:
[ Guardarropa ]

  • Cajas apiladas.
  • Cajones de una cómoda.
  • Un ropero con estantes.
  • Un sistema de perchas.
guardarropa.retirar("camiseta azul")

¿Por qué importa?

Podríamos cambiar:

Lista
  ↓
implementación con arreglo

por:

Lista
  ↓
implementación con nodos

sin cambiar el código que usa el TDA...

...si mantenemos la misma interfaz.

Fundamentos en Ciencias de la Computación

Ej.:
[ Guardarropa ]

por:

Ropero

Vestidor

¿Por qué importa?

Fundamentos en Ciencias de la Computación

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()

Fundamentos en Ciencias de la Computación

1. Listas: conceptos y operaciones

Primer TDA: Lista

Una lista:

A₀, A₁, A₂, ... , Aₙ₋₁

tiene:

  • un orden
  • una primera posición
  • una última posición
  • una posición asociada a cada elemento

Fundamentos en Ciencias de la Computación

Pensemos una lista

posición    0    1    2    3    4
          ┌────┬────┬────┬────┬────┐
lista     │ 34 │ 12 │ 52 │ 16 │ 12 │
          └────┴────┴────┴────┴────┘

Pregunta

¿Es lo mismo buscar un elemento que acceder a una posición?

Fundamentos en Ciencias de la Computación

Operaciones sobre una lista

find(x)
findKth(k)

insert(x, pos)
remove(x)

next(pos)
previous(pos)

makeEmpty()

El TDA especifica las operaciones.

Todavía no dijimos cómo se implementan.

Hagámoslo juntos

Lista inicial:

34, 12, 52, 16, 12

¿Qué ocurre?

find(52)
insert(X, 2)
remove(52)

Fundamentos en Ciencias de la Computación

Fundamentos en Ciencias de la Computación

2. Implementación de listas

Una lista, dos implementaciones

Opción A

Arreglo

Opción B

Lista enlazada

Mismo concepto abstracto.

Distintos costos.

Fundamentos en Ciencias de la Computación

Lista implementada con arreglo

índice     0      1      2      3
        ┌──────┬──────┬──────┬──────┐
        │  A₀  │  A₁  │  A₂  │  A₃  │
        └──────┴──────┴──────┴──────┘

Los elementos están organizados por posición.

Ventaja importante

findKth(k) → O(1)

Fundamentos en Ciencias de la Computación

Big O es siempre 1 para cualquier elemento en la posición k

¿Dónde aparece el costo?

Queremos insertar X al principio:

ANTES

[A][B][C][D]

Hay que hacer lugar:

DESPUÉS

[X][A][B][C][D]

¿Cuántos elementos tuvimos que mover?

Fundamentos en Ciencias de la Computación

Arreglos

Acceso por posición

O(1)

Insertar/eliminar cerca del inicio

O(N)

porque puede ser necesario desplazar elementos.

Fundamentos en Ciencias de la Computación

Otra representación

Lista enlazada

Los elementos no necesitan estar contiguos en memoria.

┌───────┬──────┐
│  A₀   │ next │──┐
└───────┴──────┘  │
                   ▼
             ┌───────┬──────┐
             │  A₁   │ next │──┐
             └───────┴──────┘  │
                                ▼
                          ┌───────┬──────┐
                          │  A₂   │ NULL │
                          └───────┴──────┘

Ahora cambia el problema

Para acceder al elemento número 3:

inicio
  ↓
[A] → [B] → [C] → [D]

Debemos recorrer los nodos.

findKth(i)

O(i)

Fundamentos en Ciencias de la Computación

Pero insertar puede ser barato

Supongamos:

[A] → [B] → [C]

Queremos insertar X después de A.

[A] ──┐
      ▼
     [X] → [B] → [C]

No desplazamos todos los elementos.

Cambiamos enlaces.

Fundamentos en Ciencias de la Computación

Arreglo vs. lista enlazada

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.

Fundamentos en Ciencias de la Computación

Entonces...

¿Cuál es mejor?

Arreglo

Conviene cuando predominan:

  • accesos por posición
  • recorridos
  • inserciones al final

Lista enlazada

Conviene cuando predominan:

  • inserciones
  • eliminaciones
  • cambios de enlaces

Fundamentos en Ciencias de la Computación

No existe una estructura "mejor"

Existe una estructura:

más adecuada para determinadas operaciones

Elegir una estructura de datos implica preguntarse:

¿Qué operaciones voy a realizar con mayor frecuencia?

Fundamentos en Ciencias de la Computación

Fundamentos en Ciencias de la Computación

3. Pilas (stack): LIFO

Stack — Pila

LIFO

Last In, First Out

       ┌─────┐
top →  │  C  │  ← último en entrar
       ├─────┤
       │  B  │
       ├─────┤
       │  A  │
       └─────┘

El último elemento que entra es el primero que sale.

Operaciones de una pila

push(x)

agrega un elemento.

pop()

elimina el elemento del tope.

top()

consulta el elemento del tope.

isEmpty()
makeEmpty()

Simulemos una pila

Partimos de:

[ ]

Ejecutamos:

push(10)
push(20)
push(30)

pop()

push(40)

top()

¿Cómo queda la pila?

¿Qué retorna top()?

Implementar una pila

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)

También podemos usar un arreglo

        topOfStack
             ↓
┌────┬────┬────┬────┬────┐
│ A  │ B  │ C  │    │    │
└────┴────┴────┴────┴────┘
  0    1    2    3    4
push → avanzar topOfStack
pop  → retroceder topOfStack
top  → acceder a topOfStack

Fundamentos en Ciencias de la Computación

4. Pilas (stack): FIFO

Queue — Cola

FIFO

First In, First Out

            entrada
               ↓
        [D] [C] [B] [A]
                     ↑
                   salida

El primero que entra es el primero que sale.

Operaciones de una cola

enqueue(x)

inserta al final.

dequeue()

elimina y retorna el elemento del frente.

También:

isEmpty()
makeEmpty()

Simulemos una cola

Partimos de:

[ ]

Ejecutamos:

enqueue(10)
enqueue(20)
enqueue(30)

dequeue()

enqueue(40)

dequeue()

¿Qué elementos quedan?

¿En qué orden?

Pila vs. Cola:  síntesis

Pila

push  pop
 ↓     ↑
   [C]   ← último en entrar
   [B]
   [A]   ← primero en entrar
   

LIFO

Cola


enqueue → [C][B][A] → dequeue

FIFO

Cola con lista enlazada

Podemos mantener dos referencias:

front                         back
  ↓                             ↓
[A] ─────→ [B] ─────→ [C] ─────→ [D]

Entonces:

enqueue → agregar al final
dequeue → eliminar al frente

Ambas operaciones:

O(1)

Y... ¿ una cola en un arreglo ?

Tenemos un problema.

[A][B][C][D][ ][ ]
 ↑
front

Después de varios dequeue:

[ ][ ][C][D][ ][ ]
       ↑
     front

¿Tenemos que desplazar C y D?

No necesariamente.

Arreglo circular

Í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)

Una cola necesita recordar

theArray
front
back
currentSize

Con esas variables podemos distinguir:

  • dónde comienza
  • dónde termina
  • cuántos elementos contiene

Fundamentos en Ciencias de la Computación

5. Implementación y pseudocódigo

Volvamos al nivel más bajo

¿Qué es un nodo?

Nodo
┌───────────────┐
│ dato          │
├───────────────┤
│ siguiente ────┼──→
└───────────────┘

Dos cosas:

  1. un valor
  2. una referencia al siguiente nodo

Pseudocódigo

ESTRUCTURA Nodo
    dato: entero
    siguiente: Nodo
FIN ESTRUCTURA

Y una lista puede comenzar simplemente con:

ESTRUCTURA Lista
    primerElemento: Nodo
FIN ESTRUCTURA

Crear un nodo

FUNCIÓN crearNodo(valor)
    nodo ← nuevo Nodo
    nodo.dato ← valor
    nodo.siguiente ← NULL

    RETORNAR nodo
FIN FUNCIÓN

¿Qué estructura obtenemos inmediatamente después?

[dato | NULL]

Insertar al final

Primera pregunta:

¿La lista está vacía?

SI lista.primerElemento = NULL

Entonces:

lista.primerElemento ← nuevoNodo

Caso trivial.

¿Y si ya hay elementos?

Tenemos que encontrar el último:

actual ← lista.primerElemento

MIENTRAS actual.siguiente ≠ NULL
    actual ← actual.siguiente
FIN MIENTRAS

Luego:

actual.siguiente ← nuevoNodo

Dibujemos la ejecución

Antes:

[A] → [B] → [C] → NULL

Creamos:

[X] → NULL

Recorremos:

A → B → C

Finalmente:

[A] → [B] → [C] → [X] → NULL

Una pregunta importante

La inserción físicamente requiere:

C.next ← X

un cambio muy pequeño.

Pero...

¿cuánto costó encontrar C?

[A] → [B] → [C]

La representación influye en el costo total.

Fundamentos en Ciencias de la Computación

6. Análisis y elección de estructuras

Pensar en complejidad

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?

Actividad

En grupos, dibujen qué ocurre paso a paso.

Grupo 1 — Lista

10, 20, 30
insertar 15 después de 10
eliminar 30

Grupo 2 — Pila

push A
push B
push C
pop
push D

Grupo 3 — Cola

enqueue A
enqueue B
enqueue C
dequeue
enqueue D

Ahora cambien de representación

Para el ejercicio anterior:

dibujen los datos como

arreglo

y luego como

nodos enlazados

¿Qué operaciones requieren mover datos?

¿Cuáles requieren cambiar referencias?

El verdadero objetivo

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...

Nada es gratis

Una estructura puede mejorar una operación...

...a cambio de empeorar otra.

acceso rápido
      ↕
inserción rápida
simplicidad
      ↕
flexibilidad
memoria
      ↕
tiempo

Idea para llevarse

TDA

Define qué podemos hacer.

Estructura de datos

Define cómo representamos los datos.

Algoritmo

Define cómo realizamos una operación.

La pregunta final

Ante un problema nuevo:

  1. ¿Qué datos necesito representar?
  2. ¿Qué operaciones necesito?
  3. ¿Cuáles son las operaciones frecuentes?
  4. ¿Cuánto cuesta cada una?
  5. ¿Qué estructura representa mejor ese problema?

Estructuras de datos

No se trata de memorizar.

Se trata de poder:

dibujar → simular → comparar → justificar

Referencias

Weiss, M. A.
Data Structures and Algorithm Analysis in C++
4th ed., 2014

Fundamentos en Ciencias de la Computación

Estructuras de datos

TDA · Listas · Pilas · Colas

Estructuras de datos

By Rodolfo Pilas

Estructuras de datos

  • 151