← Martes 25 — Análisis y notación asintótica

Pregunta central

¿Cómo convierte una buena representación de datos el pseudocódigo de Gale–Shapley en un algoritmo y qué patrones producen los tiempos , , , , y exponencial?

Respuesta corta

El número de iteraciones no basta: hay que controlar el costo de cada operación. En Gale–Shapley, arreglos de parejas, una cola de hospitales libres, apuntadores next y tablas inversas rank hacen que cada propuesta cueste . Como hay propuestas y el preprocesamiento cuesta , el total es . Los demás ejemplos muestran que explotar orden, invariantes y descomposición reduce drásticamente el espacio de búsqueda.

Mapa de la clase

flowchart TB
  GS["Pseudocódigo Gale–Shapley"] --> DS["Elegir estructuras de datos"]
  DS --> P["Cada propuesta: O(1)"]
  P --> TOTAL["O(n²) propuestas ⇒ Θ(n²)"]
  TOTAL --> CAT["Catálogo de crecimientos"]
  CAT --> RED["Reducir candidatos"]
  CAT --> ORD["Explotar orden"]
  CAT --> DIV["Dividir el problema"]
  RED --> FAST["Mejores algoritmos"]
  ORD --> FAST
  DIV --> FAST

1. Implementación eficiente de Gale–Shapley

La versión conceptual es:

GALE–SHAPLEY(preferencias de n hospitales y n estudiantes)
    M ← ∅
    mientras exista un hospital h libre:
        s ← estudiante favorita de h a quien aún no propone
 
        si s está libre:
            agregar (h, s) a M
        si no, si s prefiere h sobre su pareja h':
            reemplazar (h', s) por (h, s)
        en otro caso:
            s rechaza a h
 
    devolver M

De la clase anterior sabemos que ninguna pareja hospital–estudiante recibe dos propuestas. Por eso hay a lo sumo . Pero esto solo prueba un total si encontrar y procesar cada propuesta cuesta .

Paso 1: representar el emparejamiento

Se indexan hospitales y estudiantes de a y se guardan las parejas en dos arreglos:

  • student[h] = s: estudiante emparejada con ;
  • hospital[s] = h: hospital emparejado con ;
  • el valor 0 significa “libre”.

Así, consultar, agregar, quitar o reemplazar una pareja modifica un número constante de celdas:

Paso 2: encontrar un hospital libre

Mantener los hospitales libres en una cola o pila evita recorrer el arreglo completo en cada iteración.

Paso 3: encontrar la siguiente propuesta

Para cada hospital se guarda:

  • prefH[h]: su lista ordenada de estudiantes;
  • next[h]: índice de la siguiente estudiante a quien no ha propuesto.

La siguiente candidata es

s ← prefH[h][next[h]]
next[h] ← next[h] + 1

El apuntador solo avanza; nunca vuelve a examinar una propuesta anterior.

Paso 4: comparar preferencias con una tabla inversa

Supongamos que una estudiante guarda esta lista:

Posición 12345678
pref[i]83714562

Buscar linealmente la posición de un hospital costaría por comparación. Se construye una lista inversa:

para i ← 1 hasta n:
    rank[pref[i]] ← i

El resultado es:

Hospital 12345678
rank[h]48256731

Ahora una comparación se vuelve una desigualdad entre dos accesos directos:

Por ejemplo, rank[4] = 5 < rank[6] = 7, así que la estudiante prefiere el hospital 4 sobre el 6.

La cuenta completa, paso a paso

NecesidadRepresentaciónCosto
Encontrar hospital libreCola o pila
Consultar pareja de student[h]
Consultar pareja de hospital[s]
Obtener siguiente propuestaprefH[h] + next[h]
Comparar dos hospitales para rankS[s][h]
Cambiar una parejaActualizar ambos arreglos

Análisis total

  1. Construir tablas rank de longitud cuesta .
  2. Hay a lo sumo propuestas.
  3. Procesar una propuesta cuesta .

Por tanto,

La presentación también afirma una cota inferior: en el peor caso, cualquier algoritmo que encuentre un emparejamiento estable debe consultar posiciones de las preferencias. Junto con la implementación:

Idea transferible

Una tabla de preprocesamiento puede pagar una sola vez para convertir hasta consultas de costo en consultas de costo . Sin rank, el análisis ingenuo podría subir hasta .


2. Familias comunes de tiempos de ejecución

Mueve para observar por qué las familias polinomiales y las exponenciales terminan separándose enormemente. Selecciona una fila para ver el patrón algorítmico que produce ese crecimiento.

OrdenEjemplo de las diapositivasPatrón principal
Acceder a A[i]Una cantidad fija de operaciones
Búsqueda binariaEliminar una fracción constante
Mezcla; TARGET-SUMRecorrer cada elemento pocas veces
Mergesort; mayor intervalo vacío niveles con trabajo lineal
Enumerar pares; 3-SUM mejoradoDos decisiones de tamaño
3-SUM directoEnumerar ternas
Conjunto independiente de tamaño fijo Enumerar subconjuntos de tamaño constante
ExponencialConjunto independiente máximo; TSP exhaustivoEnumerar subconjuntos o permutaciones

: tiempo constante

El número de operaciones está acotado por una constante que no depende de . En word RAM, ejemplos habituales son:

  • una rama condicional;
  • una operación aritmética/lógica sobre una palabra;
  • declarar o inicializar una variable;
  • seguir un enlace;
  • acceder a A[i];
  • comparar o intercambiar dos posiciones.

Constante no significa instantáneo

Una operación puede tardar 1 o 10 000 unidades y seguir siendo si esa cantidad no crece con .


3. Tiempo lineal:

Mezclar dos listas ordenadas

Sean y dos listas ordenadas. Se comparan sus primeros elementos pendientes y se extrae el menor.

i ← 1; j ← 1
mientras ambas listas tengan elementos:
    si A[i] ≤ B[j]: anexar A[i]; i ← i + 1
    en otro caso: anexar B[j]; j ← j + 1
anexar el resto de la lista no vacía

Cada iteración consume definitivamente un elemento. Con elementos, se hacen a lo sumo comparaciones:

TARGET-SUM con dos apuntadores

Dado un arreglo ordenado de enteros distintos y un objetivo , queremos dos valores cuya suma sea .

i ← 1
j ← n
mientras i < j:
    s ← A[i] + A[j]
    si s = T: devolver (A[i], A[j])
    si s < T: i ← i + 1
    si s > T: j ← j - 1
devolver "no existe"
flowchart TB
  C["comparar A[i] + A[j] con T"] -->|"igual"| F["encontrado"]
  C -->|"menor"| I["incrementar i"]
  C -->|"mayor"| J["decrementar j"]
  I --> C
  J --> C

Invariante: si existe una solución, ninguna usa una posición que ya quedó a la izquierda de o a la derecha de .

Por qué es seguro mover un apuntador:

  • Si , emparejar con cualquier elemento entre y solo produce una suma menor o igual; se descarta.
  • Si , emparejar con cualquier elemento entre y solo produce una suma mayor o igual; se descarta.

Cada paso reduce el intervalo en una posición, así que hay pasos. Probar todos los pares habría costado .


4. Tiempo logarítmico:

Búsqueda binaria

lo ← 1; hi ← n
mientras lo ≤ hi:
    mid ← ⌊(lo + hi) / 2⌋
    si x < A[mid]: hi ← mid - 1
    si x > A[mid]: lo ← mid + 1
    en otro caso: devolver mid
devolver -1

Invariante: si está en el arreglo, entonces está dentro de .

Después de iteraciones quedan a lo sumo

candidatos. Para llegar a uno:

Por tanto, .

Arreglo ordenado y rotado

Ejemplo:

[80, 85, 90, 95, 20, 30, 35, 50, 60, 65, 67, 75]

La diapositiva propone:

  1. Encontrar en el índice del elemento más pequeño.
  2. Aplicar búsqueda binaria en o .

Para localizar el mínimo se mantiene el invariante mientras el intervalo contiene la ruptura. Comparar A[mid] con A[hi] determina qué mitad la contiene.

La suma sigue siendo logarítmica:


5. Tiempo lineal-logarítmico:

Mergesort

Mergesort divide el arreglo por la mitad hasta llegar a subarreglos unitarios y luego mezcla resultados ordenados.

flowchart TB
  A["n elementos"] --> L["n/2"]
  A --> R["n/2"]
  L --> LL["n/4"]
  L --> LR["n/4"]
  R --> RL["n/4"]
  R --> RR["n/4"]
  LL --> COST["trabajo total por nivel: Θ(n)"]
  LR --> COST
  RL --> COST
  RR --> COST
  COST --> TOTAL["log₂ n niveles ⇒ Θ(n log n)"]

La profundidad es porque el tamaño se divide entre dos en cada nivel; la mezcla procesa elementos por nivel.

Mayor intervalo vacío

Dadas marcas de tiempo , se busca el intervalo más largo sin llegadas.

  1. Ordenar las marcas: .
  2. Recorrer diferencias consecutivas: .
  3. Conservar la máxima diferencia: por elemento.

Por la regla de sumas,


6. Tiempo cuadrático y cúbico

Par de puntos más cercano

Enumerar todos los pares con examina

candidatos. La diapositiva advierte que esta aparente necesidad es una ilusión: existe un algoritmo mediante divide y vencerás. Una estrategia obvia no demuestra una cota inferior del problema.

3-SUM directo:

para i ← 1 hasta n:
    para j ← i + 1 hasta n:
        para k ← j + 1 hasta n:
            si A[i] + A[j] + A[k] = 0:
                devolver la terna

Se enumeran ternas.

3-SUM mejorado:

  1. Ordenar en .
  2. Para cada , resolver TARGET-SUM con objetivo sin reutilizar .
  3. Cada ejecución de dos apuntadores cuesta y se repite veces.

Patrón de mejora

Cambiar “tres elecciones independientes” por “una elección + un subproblema lineal” elimina un factor .

La diapositiva menciona una mejora teórica subcuadrática por factores logarítmicos y la conjetura de que no existe un algoritmo para ninguna constante .


7. Polinomial frente a exponencial

Conjunto independiente de tamaño fijo

Si es una constante, se pueden enumerar todos los subconjuntos de vértices y verificar cada uno:

Para esto es polinomial, aunque probablemente no sea práctico.

Conjunto independiente máximo

Si el tamaño ya no está fijado, hay que considerar todos los subconjuntos del conjunto de vértices:

Verificar cada subconjunto de manera directa lleva al algoritmo de la diapositiva.

TSP euclidiano por fuerza bruta

Enumerar los órdenes de visita y calcular la longitud de cada recorrido cuesta

Como

el crecimiento queda muy lejos del tiempo polinomial.

Convención de las diapositivas

Las diapositivas usan tiempo exponencial en el sentido amplio

Con esta convención, , y quedan incluidos, pero ninguna de estas opciones es equivalente por sí sola:

  • excluye, por ejemplo, ;
  • para alguna constante incluye , pero excluye .

Por eso la respuesta del quiz 4 es D: ninguna.


Método práctico para analizar pseudocódigo

flowchart TB
  A["1. Definir el tamaño"] --> B["2. Fijar operaciones primitivas"]
  B --> C["3. Contar iteraciones"]
  C --> D["4. Multiplicar bloques anidados"]
  D --> E["5. Sumar bloques consecutivos"]
  E --> F["6. Incluir el costo de las estructuras"]
  F --> G["7. Simplificar con O, Ω o Θ"]
  G --> H["8. Buscar orden o invariantes desaprovechados"]

Atajos que deben justificarse:

  • ciclo que avanza de uno en uno hasta : ;
  • ciclo que duplica o divide el índice: ;
  • dos ciclos independientes de longitud : usualmente ;
  • enumerar subconjuntos: ;
  • enumerar permutaciones: ;
  • ordenar y luego recorrer: .

No todos los ciclos anidados son automáticamente cuadráticos. Por ejemplo,

para i ← 1 hasta n:
    j ← 1
    mientras j ≤ n:
        j ← 2j

realiza veces un ciclo de , por lo que cuesta .

Errores frecuentes

  • Concluir para Gale–Shapley solo porque hay propuestas, sin analizar el costo de encontrarlas y procesarlas.
  • Recorrer la lista de preferencias para cada comparación en vez de construir rank.
  • Suponer que dos ciclos visualmente anidados siempre producen iteraciones.
  • Olvidar el costo de ordenar antes de aplicar dos apuntadores.
  • Confundir una cota de un algoritmo ingenuo con una cota inferior del problema.
  • Creer que es polinomial cuando crece como parte de la entrada; la definición exige constante.
  • Llamar a simplemente ; crece más rápido.

Autoevaluación

  • Puedo explicar el propósito de student, hospital, next y rank.
  • Puedo derivar para Gale–Shapley, incluyendo el preprocesamiento.
  • Puedo demostrar el invariante de TARGET-SUM y su costo lineal.
  • Puedo explicar por qué búsqueda binaria tarda .
  • Puedo derivar para mergesort por niveles.
  • Puedo transformar 3-SUM de a .
  • Puedo distinguir enumerar subconjuntos de tamaño fijo, todos los subconjuntos y todas las permutaciones.

Resumen después de clase

La eficiencia de una implementación depende tanto del algoritmo como de la representación. Gale–Shapley alcanza porque preprocesa las preferencias y convierte cada propuesta en una secuencia de accesos directos. El catálogo de tiempos muestra patrones reutilizables: dos apuntadores explotan orden para lograr tiempo lineal; la búsqueda binaria reduce candidatos a la mitad; mergesort combina niveles con trabajo lineal; y la fuerza bruta sobre pares, ternas, subconjuntos o permutaciones produce crecimiento cuadrático, cúbico o exponencial. El principio general es buscar estructura antes de enumerar posibilidades.

Referencias

  • Kevin Wayne, Algorithm Analysis, diapositivas 25–49, actualización del 16 de diciembre de 2021.
  • Jon Kleinberg y Éva Tardos, Algorithm Design, capítulo 2.
  • Conexión previa: Gale–Shapley: corrección y optimalidad.