Los árboles de decisiones son una de las primeras herramientas de Machine Learning. En esta versión hard-coded creamos un clasificador de frutas que decide según preguntas simples. El árbol tiene varias capas para que el recorrido sea interesante: la raíz pregunta “¿Color?”, luego cada rama examina tamaño, textura, olor o sabor hasta llegar a una hoja con la clasificación final.
Necesitamos un sistema que responda preguntas a partir de un objeto observado (color, tamaño, textura, olor, sabor). Cada nodo del árbol representa una regla. Las hojas contienen el nombre de la fruta. No usamos entrenamiento: las reglas están programadas a mano para ilustrar cómo funcionan los recorridos.
El árbol se modela con una estructura que almacena el tipo de pregunta, el texto descriptivo y la lista de opciones que apuntan a otros nodos. Las hojas usan el mismo struct pero marcan NODO_CLASIFICACION.
typedef enum {
NODO_COLOR,
NODO_TAMANO,
NODO_TEXTURA,
NODO_OLOR,
NODO_SABOR,
NODO_CLASIFICACION
} TipoDecision;
typedef struct Opcion {
int valor;
char etiqueta[24];
struct DecisionNode *destino;
struct Opcion *siguiente;
} Opcion;
typedef struct DecisionNode {
TipoDecision tipo;
char pregunta[64];
char clasificacion[40];
struct Opcion *primerHijo;
} DecisionNode;
DecisionNode *crearNodoPregunta(TipoDecision tipo, const char *pregunta) {
DecisionNode *n = calloc(1, sizeof(DecisionNode));
if (!n) return NULL;
n->tipo = tipo;
if (pregunta) strncpy(n->pregunta, pregunta, sizeof(n->pregunta) - 1);
return n;
}
DecisionNode *crearNodoClasificacion(const char *nombre) {
DecisionNode *n = calloc(1, sizeof(DecisionNode));
if (!n) return NULL;
n->tipo = NODO_CLASIFICACION;
strncpy(n->clasificacion, nombre, sizeof(n->clasificacion) - 1);
return n;
}
void agregarOpcion(DecisionNode *padre, int valor, const char *etiqueta, DecisionNode *destino) {
if (!padre || !destino) return;
Opcion *op = calloc(1, sizeof(Opcion));
if (!op) return;
op->valor = valor;
strncpy(op->etiqueta, etiqueta, sizeof(op->etiqueta) - 1);
op->destino = destino;
if (!padre->primerHijo) {
padre->primerHijo = op;
} else {
Opcion *actual = padre->primerHijo;
while (actual->siguiente) actual = actual->siguiente;
actual->siguiente = op;
}
}
El objeto a clasificar se almacena en un struct Objeto. Cada atributo se expresa como un enumerado para evitar comparaciones de cadenas.
typedef enum { COLOR_ROJO, COLOR_AMARILLO, COLOR_VERDE } Color;
typedef enum { TAMANO_PEQUENO, TAMANO_MEDIANO, TAMANO_GRANDE } Tamano;
typedef enum { TEXTURA_LISA, TEXTURA_RUGOSA } Textura;
typedef enum { OLOR_NEUTRO, OLOR_FRAGANTE } Olor;
typedef enum { SABOR_DULCE, SABOR_ACIDO } Sabor;
typedef struct {
Color color;
Tamano tamano;
Textura textura;
Olor olor;
Sabor sabor;
} Objeto;
Partimos de la pregunta Color? y agregamos ramas lo suficientemente profundas para que el árbol cubra varias frutas.
DecisionNode *crearArbolFrutas(void) {
DecisionNode *raiz = crearNodoPregunta(NODO_COLOR, "Color?");
DecisionNode *nRojo = crearNodoPregunta(NODO_TAMANO, "Tamanio?");
DecisionNode *nAmarillo = crearNodoPregunta(NODO_TEXTURA, "Textura?");
DecisionNode *nVerde = crearNodoPregunta(NODO_SABOR, "Sabor?");
agregarOpcion(raiz, COLOR_ROJO, "Rojo", nRojo);
agregarOpcion(raiz, COLOR_AMARILLO, "Amarillo", nAmarillo);
agregarOpcion(raiz, COLOR_VERDE, "Verde", nVerde);
DecisionNode *rojoGrande = crearNodoPregunta(NODO_TEXTURA, "Textura?");
DecisionNode *rojoMediano = crearNodoPregunta(NODO_OLOR, "Olor?");
DecisionNode *rojoPequeno = crearNodoPregunta(NODO_SABOR, "Sabor?");
agregarOpcion(nRojo, TAMANO_GRANDE, "Grande", rojoGrande);
agregarOpcion(nRojo, TAMANO_MEDIANO, "Mediano", rojoMediano);
agregarOpcion(nRojo, TAMANO_PEQUENO, "Pequeno", rojoPequeno);
agregarOpcion(rojoGrande, TEXTURA_LISA, "Lisa", crearNodoClasificacion("Manzana Crimson"));
DecisionNode *rojoGrandeRugosa = crearNodoPregunta(NODO_SABOR, "Sabor?");
agregarOpcion(rojoGrande, TEXTURA_RUGOSA, "Rugosa", rojoGrandeRugosa);
agregarOpcion(rojoGrandeRugosa, SABOR_DULCE, "Dulce", crearNodoClasificacion("Granada dulce"));
agregarOpcion(rojoGrandeRugosa, SABOR_ACIDO, "Acido", crearNodoClasificacion("Pomelo rosado"));
agregarOpcion(rojoMediano, OLOR_FRAGANTE, "Fragante", crearNodoClasificacion("Frutilla fragante"));
DecisionNode *rojoMedianoNeutro = crearNodoPregunta(NODO_SABOR, "Sabor?");
agregarOpcion(rojoMediano, OLOR_NEUTRO, "Neutro", rojoMedianoNeutro);
agregarOpcion(rojoMedianoNeutro, SABOR_DULCE, "Dulce", crearNodoClasificacion("Ciruela roja"));
agregarOpcion(rojoMedianoNeutro, SABOR_ACIDO, "Acido", crearNodoClasificacion("Arandano rojo"));
DecisionNode *rojoPequenoTextura = crearNodoPregunta(NODO_TEXTURA, "Textura?");
agregarOpcion(rojoPequeno, SABOR_DULCE, "Dulce", rojoPequenoTextura);
agregarOpcion(rojoPequeno, SABOR_ACIDO, "Acido", crearNodoClasificacion("Grosella roja"));
agregarOpcion(rojoPequenoTextura, TEXTURA_LISA, "Lisa", crearNodoClasificacion("Cereza"));
agregarOpcion(rojoPequenoTextura, TEXTURA_RUGOSA, "Rugosa", crearNodoClasificacion("Frambuesa"));
DecisionNode *amarilloLisa = crearNodoPregunta(NODO_TAMANO, "Tamanio?");
DecisionNode *amarilloRugosa = crearNodoPregunta(NODO_SABOR, "Sabor?");
agregarOpcion(nAmarillo, TEXTURA_LISA, "Lisa", amarilloLisa);
agregarOpcion(nAmarillo, TEXTURA_RUGOSA, "Rugosa", amarilloRugosa);
agregarOpcion(amarilloLisa, TAMANO_GRANDE, "Grande", crearNodoClasificacion("Papaya dorada"));
agregarOpcion(amarilloLisa, TAMANO_MEDIANO, "Mediano", crearNodoClasificacion("Mango ataulfo"));
agregarOpcion(amarilloLisa, TAMANO_PEQUENO, "Pequeno", crearNodoClasificacion("Banana baby"));
agregarOpcion(amarilloRugosa, SABOR_DULCE, "Dulce", crearNodoClasificacion("Maracuya amarilla"));
agregarOpcion(amarilloRugosa, SABOR_ACIDO, "Acido", crearNodoClasificacion("Limon rugoso"));
DecisionNode *verdeAcido = crearNodoPregunta(NODO_TAMANO, "Tamanio?");
DecisionNode *verdeDulce = crearNodoPregunta(NODO_TEXTURA, "Textura?");
agregarOpcion(nVerde, SABOR_ACIDO, "Acido", verdeAcido);
agregarOpcion(nVerde, SABOR_DULCE, "Dulce", verdeDulce);
agregarOpcion(verdeAcido, TAMANO_PEQUENO, "Pequeno", crearNodoClasificacion("Lima"));
DecisionNode *verdeAcidoGrande = crearNodoPregunta(NODO_TEXTURA, "Textura?");
agregarOpcion(verdeAcido, TAMANO_GRANDE, "Grande", verdeAcidoGrande);
agregarOpcion(verdeAcidoGrande, TEXTURA_LISA, "Lisa", crearNodoClasificacion("Manzana verde"));
agregarOpcion(verdeAcidoGrande, TEXTURA_RUGOSA, "Rugosa", crearNodoClasificacion("Pomelo verde"));
agregarOpcion(verdeDulce, TEXTURA_LISA, "Lisa", crearNodoClasificacion("Uva verde"));
DecisionNode *verdeDulceRugosa = crearNodoPregunta(NODO_TAMANO, "Tamanio?");
agregarOpcion(verdeDulce, TEXTURA_RUGOSA, "Rugosa", verdeDulceRugosa);
agregarOpcion(verdeDulceRugosa, TAMANO_PEQUENO, "Pequeno", crearNodoClasificacion("Kiwi"));
agregarOpcion(verdeDulceRugosa, TAMANO_MEDIANO, "Mediano", crearNodoClasificacion("Melon verde"));
return raiz;
}
Mantenemos la función clasificar para pruebas automáticas con structs y agregamos clasificarInteractivo para que la aplicación pregunte al usuario y permita elegir la opción correspondiente en cada nodo.
int valorAtributo(const Objeto *obj, TipoDecision tipo) {
switch (tipo) {
case NODO_COLOR: return obj->color;
case NODO_TAMANO: return obj->tamano;
case NODO_TEXTURA: return obj->textura;
case NODO_OLOR: return obj->olor;
case NODO_SABOR: return obj->sabor;
default: return -1;
}
}
const char *clasificar(const DecisionNode *nodo, const Objeto *obj) {
if (!nodo || !obj) return "Desconocido";
if (nodo->tipo == NODO_CLASIFICACION) {
return nodo->clasificacion;
}
int valor = valorAtributo(obj, nodo->tipo);
for (Opcion *op = nodo->primerHijo; op; op = op->siguiente) {
if (op->valor == valor) {
return clasificar(op->destino, obj);
}
}
return "Desconocido";
}
const char *clasificarInteractivo(DecisionNode *nodo) {
if (!nodo) return "Desconocido";
DecisionNode *actual = nodo;
char entrada[32];
while (actual && actual->tipo != NODO_CLASIFICACION) {
printf("\n%s\n", actual->pregunta);
int indice = 1;
for (Opcion *op = actual->primerHijo; op; op = op->siguiente, ++indice) {
printf(" %d) %s\n", indice, op->etiqueta);
}
printf("Selecciona una opcion: ");
if (!fgets(entrada, sizeof(entrada), stdin)) return "Entrada invalida";
int seleccion = (int)strtol(entrada, NULL, 10);
if (seleccion <= 0) {
puts("Opcion invalida, intenta de nuevo.");
continue;
}
Opcion *elegida = actual->primerHijo;
for (int i = 1; elegida && i < seleccion; ++i) {
elegida = elegida->siguiente;
}
if (!elegida) {
puts("Seleccion fuera de rango.");
continue;
}
actual = elegida->destino;
}
return actual ? actual->clasificacion : "Desconocido";
}
El siguiente programa arma el árbol, clasifica varios objetos, ofrece una sesión interactiva de preguntas y limpia la memoria al final.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#ifdef _WIN32
#include <windows.h>
#endif
typedef enum { COLOR_ROJO, COLOR_AMARILLO, COLOR_VERDE } Color;
typedef enum { TAMANO_PEQUENO, TAMANO_MEDIANO, TAMANO_GRANDE } Tamano;
typedef enum { TEXTURA_LISA, TEXTURA_RUGOSA } Textura;
typedef enum { OLOR_NEUTRO, OLOR_FRAGANTE } Olor;
typedef enum { SABOR_DULCE, SABOR_ACIDO } Sabor;
typedef struct {
Color color;
Tamano tamano;
Textura textura;
Olor olor;
Sabor sabor;
} Objeto;
typedef enum {
NODO_COLOR,
NODO_TAMANO,
NODO_TEXTURA,
NODO_OLOR,
NODO_SABOR,
NODO_CLASIFICACION
} TipoDecision;
typedef struct DecisionNode DecisionNode;
typedef struct Opcion {
int valor;
char etiqueta[24];
DecisionNode *destino;
struct Opcion *siguiente;
} Opcion;
struct DecisionNode {
TipoDecision tipo;
char pregunta[64];
char clasificacion[40];
Opcion *primerHijo;
};
DecisionNode *crearNodoPregunta(TipoDecision tipo, const char *pregunta) {
DecisionNode *n = calloc(1, sizeof(DecisionNode));
if (!n) return NULL;
n->tipo = tipo;
if (pregunta) strncpy(n->pregunta, pregunta, sizeof(n->pregunta) - 1);
return n;
}
DecisionNode *crearNodoClasificacion(const char *nombre) {
DecisionNode *n = calloc(1, sizeof(DecisionNode));
if (!n) return NULL;
n->tipo = NODO_CLASIFICACION;
strncpy(n->clasificacion, nombre, sizeof(n->clasificacion) - 1);
return n;
}
void agregarOpcion(DecisionNode *padre, int valor, const char *etiqueta, DecisionNode *destino) {
if (!padre || !destino) return;
Opcion *op = calloc(1, sizeof(Opcion));
if (!op) return;
op->valor = valor;
strncpy(op->etiqueta, etiqueta, sizeof(op->etiqueta) - 1);
op->destino = destino;
if (!padre->primerHijo) {
padre->primerHijo = op;
} else {
Opcion *actual = padre->primerHijo;
while (actual->siguiente) actual = actual->siguiente;
actual->siguiente = op;
}
}
int valorAtributo(const Objeto *obj, TipoDecision tipo) {
switch (tipo) {
case NODO_COLOR: return obj->color;
case NODO_TAMANO: return obj->tamano;
case NODO_TEXTURA: return obj->textura;
case NODO_OLOR: return obj->olor;
case NODO_SABOR: return obj->sabor;
default: return -1;
}
}
const char *clasificar(const DecisionNode *nodo, const Objeto *obj) {
if (!nodo || !obj) return "Desconocido";
if (nodo->tipo == NODO_CLASIFICACION) return nodo->clasificacion;
int valor = valorAtributo(obj, nodo->tipo);
for (Opcion *op = nodo->primerHijo; op; op = op->siguiente) {
if (op->valor == valor) return clasificar(op->destino, obj);
}
return "Desconocido";
}
const char *clasificarInteractivo(DecisionNode *nodo) {
if (!nodo) return "Desconocido";
DecisionNode *actual = nodo;
char entrada[32];
while (actual && actual->tipo != NODO_CLASIFICACION) {
printf("\n%s\n", actual->pregunta);
int indice = 1;
for (Opcion *op = actual->primerHijo; op; op = op->siguiente, ++indice) {
printf(" %d) %s\n", indice, op->etiqueta);
}
printf("Selecciona una opcion: ");
if (!fgets(entrada, sizeof(entrada), stdin)) return "Entrada invalida";
long seleccion = strtol(entrada, NULL, 10);
if (seleccion <= 0) {
puts("Opcion invalida, intenta nuevamente.");
continue;
}
Opcion *opcionElegida = actual->primerHijo;
for (int i = 1; opcionElegida && i < seleccion; ++i) {
opcionElegida = opcionElegida->siguiente;
}
if (!opcionElegida) {
puts("Seleccion fuera de rango.");
continue;
}
actual = opcionElegida->destino;
}
return actual ? actual->clasificacion : "Desconocido";
}
DecisionNode *crearArbolFrutas(void) {
DecisionNode *raiz = crearNodoPregunta(NODO_COLOR, "Color?");
DecisionNode *nRojo = crearNodoPregunta(NODO_TAMANO, "Tamanio?");
DecisionNode *nAmarillo = crearNodoPregunta(NODO_TEXTURA, "Textura?");
DecisionNode *nVerde = crearNodoPregunta(NODO_SABOR, "Sabor?");
agregarOpcion(raiz, COLOR_ROJO, "Rojo", nRojo);
agregarOpcion(raiz, COLOR_AMARILLO, "Amarillo", nAmarillo);
agregarOpcion(raiz, COLOR_VERDE, "Verde", nVerde);
DecisionNode *rojoGrande = crearNodoPregunta(NODO_TEXTURA, "Textura?");
DecisionNode *rojoMediano = crearNodoPregunta(NODO_OLOR, "Olor?");
DecisionNode *rojoPequeno = crearNodoPregunta(NODO_SABOR, "Sabor?");
agregarOpcion(nRojo, TAMANO_GRANDE, "Grande", rojoGrande);
agregarOpcion(nRojo, TAMANO_MEDIANO, "Mediano", rojoMediano);
agregarOpcion(nRojo, TAMANO_PEQUENO, "Pequeno", rojoPequeno);
agregarOpcion(rojoGrande, TEXTURA_LISA, "Lisa", crearNodoClasificacion("Manzana Crimson"));
DecisionNode *rojoGrandeRugosa = crearNodoPregunta(NODO_SABOR, "Sabor?");
agregarOpcion(rojoGrande, TEXTURA_RUGOSA, "Rugosa", rojoGrandeRugosa);
agregarOpcion(rojoGrandeRugosa, SABOR_DULCE, "Dulce", crearNodoClasificacion("Granada dulce"));
agregarOpcion(rojoGrandeRugosa, SABOR_ACIDO, "Acido", crearNodoClasificacion("Pomelo rosado"));
agregarOpcion(rojoMediano, OLOR_FRAGANTE, "Fragante", crearNodoClasificacion("Frutilla fragante"));
DecisionNode *rojoMedianoNeutro = crearNodoPregunta(NODO_SABOR, "Sabor?");
agregarOpcion(rojoMediano, OLOR_NEUTRO, "Neutro", rojoMedianoNeutro);
agregarOpcion(rojoMedianoNeutro, SABOR_DULCE, "Dulce", crearNodoClasificacion("Ciruela roja"));
agregarOpcion(rojoMedianoNeutro, SABOR_ACIDO, "Acido", crearNodoClasificacion("Arandano rojo"));
DecisionNode *rojoPequenoTextura = crearNodoPregunta(NODO_TEXTURA, "Textura?");
agregarOpcion(rojoPequeno, SABOR_DULCE, "Dulce", rojoPequenoTextura);
agregarOpcion(rojoPequeno, SABOR_ACIDO, "Acido", crearNodoClasificacion("Grosella roja"));
agregarOpcion(rojoPequenoTextura, TEXTURA_LISA, "Lisa", crearNodoClasificacion("Cereza"));
agregarOpcion(rojoPequenoTextura, TEXTURA_RUGOSA, "Rugosa", crearNodoClasificacion("Frambuesa"));
DecisionNode *amarilloLisa = crearNodoPregunta(NODO_TAMANO, "Tamanio?");
DecisionNode *amarilloRugosa = crearNodoPregunta(NODO_SABOR, "Sabor?");
agregarOpcion(nAmarillo, TEXTURA_LISA, "Lisa", amarilloLisa);
agregarOpcion(nAmarillo, TEXTURA_RUGOSA, "Rugosa", amarilloRugosa);
agregarOpcion(amarilloLisa, TAMANO_GRANDE, "Grande", crearNodoClasificacion("Papaya dorada"));
agregarOpcion(amarilloLisa, TAMANO_MEDIANO, "Mediano", crearNodoClasificacion("Mango ataulfo"));
agregarOpcion(amarilloLisa, TAMANO_PEQUENO, "Pequeno", crearNodoClasificacion("Banana baby"));
agregarOpcion(amarilloRugosa, SABOR_DULCE, "Dulce", crearNodoClasificacion("Maracuya amarilla"));
agregarOpcion(amarilloRugosa, SABOR_ACIDO, "Acido", crearNodoClasificacion("Limon rugoso"));
DecisionNode *verdeAcido = crearNodoPregunta(NODO_TAMANO, "Tamanio?");
DecisionNode *verdeDulce = crearNodoPregunta(NODO_TEXTURA, "Textura?");
agregarOpcion(nVerde, SABOR_ACIDO, "Acido", verdeAcido);
agregarOpcion(nVerde, SABOR_DULCE, "Dulce", verdeDulce);
agregarOpcion(verdeAcido, TAMANO_PEQUENO, "Pequeno", crearNodoClasificacion("Lima"));
DecisionNode *verdeAcidoGrande = crearNodoPregunta(NODO_TEXTURA, "Textura?");
agregarOpcion(verdeAcido, TAMANO_GRANDE, "Grande", verdeAcidoGrande);
agregarOpcion(verdeAcidoGrande, TEXTURA_LISA, "Lisa", crearNodoClasificacion("Manzana verde"));
agregarOpcion(verdeAcidoGrande, TEXTURA_RUGOSA, "Rugosa", crearNodoClasificacion("Pomelo verde"));
agregarOpcion(verdeDulce, TEXTURA_LISA, "Lisa", crearNodoClasificacion("Uva verde"));
DecisionNode *verdeDulceRugosa = crearNodoPregunta(NODO_TAMANO, "Tamanio?");
agregarOpcion(verdeDulce, TEXTURA_RUGOSA, "Rugosa", verdeDulceRugosa);
agregarOpcion(verdeDulceRugosa, TAMANO_PEQUENO, "Pequeno", crearNodoClasificacion("Kiwi"));
agregarOpcion(verdeDulceRugosa, TAMANO_MEDIANO, "Mediano", crearNodoClasificacion("Melon verde"));
return raiz;
}
void liberarArbol(DecisionNode *nodo) {
if (!nodo) return;
for (Opcion *op = nodo->primerHijo; op; op = op->siguiente) {
liberarArbol(op->destino);
}
Opcion *actual = nodo->primerHijo;
while (actual) {
Opcion *sig = actual->siguiente;
free(actual);
actual = sig;
}
free(nodo);
}
int main(void) {
#ifdef _WIN32
SetConsoleOutputCP(CP_UTF8);
SetConsoleCP(CP_UTF8);
#endif
srand((unsigned)time(NULL));
DecisionNode *raiz = crearArbolFrutas();
Objeto muestras[] = {
{COLOR_ROJO, TAMANO_GRANDE, TEXTURA_LISA, OLOR_NEUTRO, SABOR_DULCE},
{COLOR_ROJO, TAMANO_PEQUENO, TEXTURA_RUGOSA, OLOR_NEUTRO, SABOR_DULCE},
{COLOR_AMARILLO, TAMANO_MEDIANO, TEXTURA_LISA, OLOR_NEUTRO, SABOR_DULCE},
{COLOR_AMARILLO, TAMANO_PEQUENO, TEXTURA_RUGOSA, OLOR_NEUTRO, SABOR_ACIDO},
{COLOR_VERDE, TAMANO_PEQUENO, TEXTURA_RUGOSA, OLOR_NEUTRO, SABOR_DULCE},
{COLOR_VERDE, TAMANO_GRANDE, TEXTURA_LISA, OLOR_NEUTRO, SABOR_ACIDO}
};
const char *nombres[] = {"Caso A", "Caso B", "Caso C", "Caso D", "Caso E", "Caso F"};
puts("=== Clasificacion automatica ===");
for (size_t i = 0; i < sizeof(muestras)/sizeof(muestras[0]); ++i) {
printf("%s => %s\n", nombres[i], clasificar(raiz, &muestras[i]));
}
puts("\n=== Sesion interactiva ===");
const char *resultado = clasificarInteractivo(raiz);
printf("Resultado: %s\n", resultado);
liberarArbol(raiz);
return 0;
}