18. Composición de relaciones

La composición de dos relaciones conecta elementos que están enlazados a través de un intermediario. Es el mecanismo que permite encadenar vínculos y recorrer estructuras en varios pasos.

18.1 Introducción

Cuando dos relaciones comparten un conjunto intermedio, podemos combinarlas para obtener una nueva relación que va directamente del conjunto de partida al conjunto de llegada.

Esta operación, llamada composición, aparece con frecuencia en programación: encadenar funciones, recorrer grafos en múltiples pasos, combinar consultas en bases de datos o analizar dependencias transitivas son situaciones donde la composición de relaciones es la herramienta central.

18.2 Definición

Sean R una relación de A en B y S una relación de B en C. La composición de R y S, escrita S ∘ R, es la relación de A en C definida por:

S ∘ R = {(a, c) | existe b ∈ B tal que (a, b) ∈ R y (b, c) ∈ S}

El elemento b actúa como intermediario: pertenece al codominio de R y al dominio de S al mismo tiempo.

Nota sobre notación: en algunos textos se escribe R ∘ S en lugar de S ∘ R. En este curso usamos la notación en la que la relación aplicada primero se escribe a la derecha.

18.3 Ejemplo básico

Sea A = {1, 2, 3}, B = {a, b}, C = {x, y}.

R = {(1, a), (2, a), (3, b)} S = {(a, x), (b, x), (b, y)}

Para calcular S ∘ R buscamos todos los pares (elemento de A, elemento de C) conectados por un intermediario en B:

(1, a) ∈ R y (a, x) ∈ S → (1, x) ∈ S ∘ R (2, a) ∈ R y (a, x) ∈ S → (2, x) ∈ S ∘ R (3, b) ∈ R y (b, x) ∈ S → (3, x) ∈ S ∘ R (3, b) ∈ R y (b, y) ∈ S → (3, y) ∈ S ∘ R S ∘ R = {(1, x), (2, x), (3, x), (3, y)}

18.4 Composición en JavaScript

Podemos calcular la composición de dos relaciones con un algoritmo que busca intermediarios comunes.

const R = [[1, 'a'], [2, 'a'], [3, 'b']];
const S = [['a', 'x'], ['b', 'x'], ['b', 'y']];

function componer(R, S) {
  const resultado = [];

  for (const [a, b] of R) {
    for (const [b2, c] of S) {
      if (b === b2) {
        const yaDuplicado = resultado.some(([x, y]) => x === a && y === c);
        if (!yaDuplicado) resultado.push([a, c]);
      }
    }
  }

  return resultado;
}

console.log(componer(R, S));

La función recorre todos los pares de R y S, y empareja aquellos cuyo elemento intermedio coincide.

18.5 Composición sobre el mismo conjunto

Cuando R y S son relaciones sobre el mismo conjunto A, la composición también es una relación sobre A. En este caso suele escribirse para la composición de R consigo misma.

R² = R ∘ R = {(a, c) | existe b tal que (a, b) ∈ R y (b, c) ∈ R}

Ejemplo: Sea R = {(1, 2), (2, 3), (3, 4)} sobre A = {1, 2, 3, 4}.

R² = {(1, 3), (2, 4)} R³ = {(1, 4)}

Cada potencia avanza un paso más en la cadena de conexiones.

18.6 Representación matricial de la composición

Si representamos las relaciones como matrices booleanas, la composición equivale a un producto booleano de matrices: en lugar de multiplicar con suma y producto ordinarios, usamos OR y AND.

(M_S ∘ M_R)[i][j] = OR sobre todos los k de (M_R[i][k] AND M_S[k][j])
function productoBooleanno(M1, M2) {
  const n = M1.length;
  const resultado = Array.from({ length: n }, () => Array(n).fill(false));

  for (let i = 0; i < n; i++) {
    for (let j = 0; j < n; j++) {
      for (let k = 0; k < n; k++) {
        if (M1[i][k] && M2[k][j]) {
          resultado[i][j] = true;
          break;
        }
      }
    }
  }

  return resultado;
}

// Relación R: (1→2), (2→3), (3→4) en A = {1,2,3,4}
const MR = [
  [false, true,  false, false],
  [false, false, true,  false],
  [false, false, false, true ],
  [false, false, false, false]
];

const MR2 = productoBooleanno(MR, MR);
console.log(MR2);

El producto booleano de matrices es equivalente al cálculo algebraico de la composición.

18.7 Propiedades de la composición

Propiedad ¿Se cumple? Observación
Asociativa (T ∘ S) ∘ R = T ∘ (S ∘ R)
Conmutativa No en general S ∘ R y R ∘ S pueden ser distintas
Elemento neutro La relación identidad I cumple I ∘ R = R ∘ I = R
Distributiva sobre unión R ∘ (S ∪ T) = (R ∘ S) ∪ (R ∘ T)

18.8 Relación identidad como neutro

La relación identidad sobre A es I_A = {(a, a) | a ∈ A}. Actúa como elemento neutro en la composición: componer cualquier relación con la identidad la deja igual.

I_A ∘ R = R ∘ I_A = R

Esto es análogo al papel del número 1 en la multiplicación ordinaria.

18.9 Ejemplo completo paso a paso

Calculemos para R = {(1, 2), (1, 3), (2, 4), (3, 4)} sobre A = {1, 2, 3, 4}.

Buscamos todos los pares (a, c) donde existe un b intermedio:

(1, 2) y (2, 4) → (1, 4) (1, 3) y (3, 4) → (1, 4) ← ya existe R² = {(1, 4)}

Aunque hay dos caminos de 1 a 4, el par (1, 4) aparece una sola vez en la composición.

18.10 Aplicaciones en informática

Área Uso de la composición Ejemplo concreto
Grafos Alcanzabilidad en k pasos ¿Hay camino de A a B en exactamente 2 saltos?
Bases de datos JOIN entre tablas Unir empleados con proyectos a través de asignaciones
Programación funcional Composición de funciones f ∘ g(x) = f(g(x))
Redes Encaminamiento en múltiples saltos Ruta de un paquete a través de routers intermedios
Compiladores Análisis de llamadas transitivas ¿La función A llama a C a través de B?

18.11 Composición y clausura transitiva

La clausura transitiva de una relación R puede definirse usando potencias de composición:

t(R) = R ∪ R² ∪ R³ ∪ R⁴ ∪ ...

Cada potencia Rⁿ contiene los pares conectados por caminos de exactamente n pasos. La unión de todas las potencias da todos los pares alcanzables por cualquier camino.

function potencia(conjunto, relacion, n) {
  let resultado = [...relacion];
  let actual = [...relacion];

  for (let i = 1; i < n; i++) {
    actual = componer(actual, relacion);
    for (const par of actual) {
      const existe = resultado.some(([x, y]) => x === par[0] && y === par[1]);
      if (!existe) resultado.push(par);
    }
  }

  return resultado;
}

// t(R) aproximada hasta R^n (n = tamaño del conjunto)
function clausuraTransitivaPotencias(conjunto, relacion) {
  return potencia(conjunto, relacion, conjunto.length);
}

const A = [1, 2, 3, 4];
const R = [[1, 2], [2, 3], [3, 4]];
console.log(clausuraTransitivaPotencias(A, R));

18.12 Errores comunes

  • Invertir el orden de las relaciones al componer: S ∘ R aplica primero R, luego S.
  • Confundir la composición con la unión o intersección de relaciones.
  • Olvidar que el dominio de S debe coincidir con el codominio de R.
  • Incluir pares duplicados en el resultado de la composición.
  • Suponer que la composición es siempre conmutativa por analogía con la multiplicación numérica.

18.13 Qué debes recordar de este tema

  • La composición S ∘ R conecta pares (a, c) mediante un intermediario b.
  • Para que la composición sea posible, el codominio de R debe coincidir con el dominio de S.
  • La composición es asociativa pero no conmutativa en general.
  • La relación identidad es el elemento neutro de la composición.
  • Matricialmente, la composición equivale al producto booleano de matrices.
  • La clausura transitiva puede construirse como unión de potencias de composición.

18.14 Conclusión

La composición de relaciones es una operación fundamental que permite combinar relaciones para obtener nuevas conexiones indirectas. Aparece de forma natural en el encadenamiento de funciones, el recorrido de grafos, las consultas JOIN en bases de datos y el análisis de dependencias.

En el próximo tema estudiaremos las relaciones inversas, que permiten recorrer una relación en el sentido contrario, intercambiando el papel de dominio y codominio.