Cada elemento de un conjunto puede pertenecer o no a un subconjunto. Esta decisión binaria permite contar todos los subconjuntos con una potencia de 2.
Un subconjunto se forma eligiendo algunos elementos de un conjunto original. Puede contener todos los elementos, ninguno o cualquier cantidad intermedia.
Contar subconjuntos es una aplicación central de la combinatoria porque aparece en selección de características, agrupación de datos, búsqueda y análisis de configuraciones.
Para cada elemento existen dos opciones independientes:
Si el conjunto tiene n elementos, el principio del producto produce:
Entre todos los subconjuntos siempre aparecen dos casos especiales:
Hay exactamente un subconjunto vacío y exactamente un subconjunto que contiene todos los elementos.
Escribe un conjunto pequeño y elige si quieres mostrar todos sus subconjuntos o solo los de un tamaño determinado.
Si queremos contar solamente los subconjuntos de tamaño k, utilizamos un coeficiente binomial:
Por ejemplo, un conjunto de 5 elementos tiene:
Todos los subconjuntos se agrupan según su tamaño. Por eso:
El lado derecho suma los subconjuntos de cada tamaño y el lado izquierdo cuenta directamente las dos decisiones de cada elemento.
Podemos representar cada subconjunto mediante una máscara de inclusión: cada posición indica si un elemento se incluye o no.
function subconjuntos(elementos) {
const resultados = [];
const cantidad = 2 ** elementos.length;
for (let mascara = 0; mascara < cantidad; mascara += 1) {
const subconjunto = [];
elementos.forEach((elemento, indice) => {
if (mascara & (1 << indice)) subconjunto.push(elemento);
});
resultados.push(subconjunto);
}
return resultados;
}
console.log(subconjuntos(["A", "B", "C"]));
Con 3 elementos, las máscaras van de 0 a 7 y se generan 23 = 8 subconjuntos.
Para generar solo subconjuntos de un tamaño determinado, podemos utilizar una construcción recursiva que elige o descarta elementos hasta completar k.
function deTamañoK(elementos, k, inicio = 0, actual = [], resultados = []) {
if (actual.length === k) {
resultados.push([...actual]);
return resultados;
}
for (let indice = inicio; indice < elementos.length; indice += 1) {
actual.push(elementos[indice]);
deTamañoK(elementos, k, indice + 1, actual, resultados);
actual.pop();
}
return resultados;
}
console.log(deTamañoK(["A", "B", "C", "D"], 2));
Podemos exigir que un subconjunto incluya un elemento, excluya otro o tenga un tamaño específico.
Las dos cantidades son iguales por simetría: cada subconjunto que contiene A corresponde a uno que no lo contiene al quitar o agregar A.
Un algoritmo que prueba todos los subconjuntos puede tener un espacio de búsqueda de tamaño 2n. Este crecimiento puede ser manejable para n pequeño, pero rápidamente se vuelve grande.
El conteo combinatorio permite estimar el costo antes de ejecutar la búsqueda.
Contar subconjuntos consiste en reconocer que cada elemento puede incluirse o excluirse. Esta decisión binaria produce 2n posibilidades, mientras que fijar el tamaño de la selección conduce a los coeficientes binomiales.
En el próximo tema estudiaremos el conteo de particiones, donde los elementos se agrupan en partes o bloques.