Una forma sencilla y rápida de aprender JAVA, observando y deduciendo cómo se comporta el lenguaje a través de ejemplos prácticos.

Archivo del blog

viernes, 28 de marzo de 2025

Visualizando tipos de aleatoriedad de puntos.

 Aplicación que muestra visualmente nueve métodos diferentes de generación de puntos aleatorios en un plano 2D. La ventana muestra una cuadrícula de 3x3, donde cada celda muestra una distribución distinta:

    1- Uniforme: Puntos distribuidos equitativamente por toda la zona.
    2- Normal (o Gaussiana): Puntos concentrados alrededor del centro de la zona, con densidad decreciente hacia los bordes.
    3- Rejilla (Aleatoria): Puntos basados en una rejilla regular, pero con una pequeña desviación aleatoria (jitter) de su posición ideal.
    4- Agrupada (Clusters): Puntos concentrados en varios "racimos" o grupos distribuidos aleatoriamente dentro de la zona.
    5- Radial: Puntos distribuidos alrededor del centro, con mayor densidad cerca del centro y dispersándose hacia afuera.
    6- Estratificada: La zona se divide en sub-celdas invisibles, y se coloca un punto aleatorio dentro de cada una, garantizando una cobertura muy uniforme.
    7- Sesgo hacia Bordes: Puntos con mayor probabilidad de aparecer cerca de los bordes de la zona.
    8- Sesgo Horizontal: Puntos distribuidos uniformemente en el eje horizontal, pero concentrados (según una curva normal) alrededor del centro vertical.
    9- Sesgo Vertical: Puntos distribuidos uniformemente en el eje vertical, pero concentrados (según una curva normal) alrededor del centro horizontal.


Código Java (RandomnessVisualizer.java):

package randomnessvisualizer;

import javax.swing.*;
import java.awt.*;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

public class RandomnessVisualizer {

    private static final int WINDOW_WIDTH = 900;
    private static final int WINDOW_HEIGHT = 900;
    private static final int NUM_POINTS_PER_ZONE = 2000;
    private static final Color LINE_COLOR = Color.BLACK;
    private static final Color POINT_COLOR = Color.BLUE;
    private static final int GRID_SIZE = 3;

    private static final Color LABEL_COLOR = Color.BLACK;
    private static final Font LABEL_FONT = new Font("SansSerif", Font.BOLD, 18);
    private static final int LABEL_TOP_PADDING = 3;
    private static final int LABEL_LEFT_PADDING = 5;

    private static final double GAUSSIAN_STD_DEV_FACTOR = 1.0 / 6.0;
    private static final double GRID_JITTER_FACTOR = 0.15;
    private static final int NUM_CLUSTERS = 4;
    private static final double CLUSTER_STD_DEV_FACTOR = 1.0 / 15.0;
    private static final double RADIAL_CONCENTRATION = 0.5;
    private static final double EDGE_BIAS_POWER = 2.0;
    private static final double LINEAR_BIAS_STD_DEV_FACTOR = 1.0 / 8.0;

    private static final String[] LABELS = {
        "1. Uniforme", "2. Normal", "3. Rejilla",
        "4. Clusters", "5. Radial", "6. Stratified",
        "7. Edge Bias", "8. H. Bias", "9. V. Bias"
    };

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            JFrame frame = new JFrame("Visualizador de Aleatoriedad (9 Zonas)");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.setResizable(false);
            DrawingPanel drawingPanel = new DrawingPanel();
            frame.add(drawingPanel);
            frame.pack();
            frame.setLocationRelativeTo(null);
            frame.setVisible(true);
        });
    }

    static class DrawingPanel extends JPanel {

        private final List<List<Point2D.Double>> zonePoints;
        private final Random random;

        public DrawingPanel() {
            this.random = new Random();
            this.zonePoints = new ArrayList<>(GRID_SIZE * GRID_SIZE);

            for (int i = 0; i < GRID_SIZE * GRID_SIZE; i++) {
                zonePoints.add(new ArrayList<>(NUM_POINTS_PER_ZONE));
            }

            generateAllZonePoints();
        }

        private void generateAllZonePoints() {
            int zoneWidth = WINDOW_WIDTH / GRID_SIZE;
            int zoneHeight = WINDOW_HEIGHT / GRID_SIZE;

            for (int row = 0; row < GRID_SIZE; row++) {
                for (int col = 0; col < GRID_SIZE; col++) {
                    int zoneIndex = row * GRID_SIZE + col;
                    int minX = col * zoneWidth;
                    int minY = row * zoneHeight;
                    List<Point2D.Double> currentZoneList = zonePoints.get(zoneIndex);
                    currentZoneList.clear();

                    switch (zoneIndex) {
                        case 0 -> generateUniformPoints(currentZoneList, minX, minY, zoneWidth, zoneHeight);
                        case 1 -> generateNormalPoints(currentZoneList, minX, minY, zoneWidth, zoneHeight);
                        case 2 -> generateGridPoints(currentZoneList, minX, minY, zoneWidth, zoneHeight);
                        case 3 -> generateClusterPoints(currentZoneList, minX, minY, zoneWidth, zoneHeight);
                        case 4 -> generateRadialPoints(currentZoneList, minX, minY, zoneWidth, zoneHeight);
                        case 5 -> generateStratifiedPoints(currentZoneList, minX, minY, zoneWidth, zoneHeight);
                        case 6 -> generateEdgeBiasPoints(currentZoneList, minX, minY, zoneWidth, zoneHeight);
                        case 7 -> generateHorizontalBiasPoints(currentZoneList, minX, minY, zoneWidth, zoneHeight);
                        case 8 -> generateVerticalBiasPoints(currentZoneList, minX, minY, zoneWidth, zoneHeight);
                        default -> generateUniformPoints(currentZoneList, minX, minY, zoneWidth, zoneHeight);
                    }
                }
            }
        }

        private void generateUniformPoints(List<Point2D.Double> points, double minX, double minY, double width, double height) {
            for (int i = 0; i < NUM_POINTS_PER_ZONE; i++) {
                double x = minX + random.nextDouble() * width;
                double y = minY + random.nextDouble() * height;
                points.add(new Point2D.Double(x, y));
            }
        }

        private void generateNormalPoints(List<Point2D.Double> points, double minX, double minY, double width, double height) {
            double centerX = minX + width / 2.0;
            double centerY = minY + height / 2.0;
            double stdDevX = width * GAUSSIAN_STD_DEV_FACTOR;
            double stdDevY = height * GAUSSIAN_STD_DEV_FACTOR;
            int count = 0;
            while (count < NUM_POINTS_PER_ZONE) {
                double x = centerX + random.nextGaussian() * stdDevX;
                double y = centerY + random.nextGaussian() * stdDevY;
                if (x >= minX && x < (minX + width) && y >= minY && y < (minY + height)) {
                    points.add(new Point2D.Double(x, y));
                    count++;
                }
            }
        }

        private void generateGridPoints(List<Point2D.Double> points, double minX, double minY, double width, double height) {
            int numGridCells = (int) Math.ceil(Math.sqrt(NUM_POINTS_PER_ZONE));
            double spacingX = width / numGridCells;
            double spacingY = height / numGridCells;
            double jitterX = spacingX * GRID_JITTER_FACTOR;
            double jitterY = spacingY * GRID_JITTER_FACTOR;
            int pointsGenerated = 0;
            outerLoop:
            for (int row = 0; row < numGridCells; row++) {
                for (int col = 0; col < numGridCells; col++) {
                    if (pointsGenerated >= NUM_POINTS_PER_ZONE) {
                        break outerLoop;
                    }
                    double idealX = minX + (col + 0.5) * spacingX;
                    double idealY = minY + (row + 0.5) * spacingY;
                    double x = idealX + (random.nextDouble() - 0.5) * 2 * jitterX;
                    double y = idealY + (random.nextDouble() - 0.5) * 2 * jitterY;
                    x = Math.max(minX, Math.min(x, minX + width - 1));
                    y = Math.max(minY, Math.min(y, minY + height - 1));
                    points.add(new Point2D.Double(x, y));
                    pointsGenerated++;
                }
            }
            while (points.size() < NUM_POINTS_PER_ZONE) {
                double x = minX + random.nextDouble() * width;
                double y = minY + random.nextDouble() * height;
                points.add(new Point2D.Double(x, y));
            }
            while (points.size() > NUM_POINTS_PER_ZONE) {
                points.remove(points.size() - 1);
            }
        }

        private void generateClusterPoints(List<Point2D.Double> points, double minX, double minY, double width, double height) {
            List<Point2D.Double> clusterCenters = new ArrayList<>(NUM_CLUSTERS);
            for (int i = 0; i < NUM_CLUSTERS; i++) {
                double cX = minX + random.nextDouble() * width;
                double cY = minY + random.nextDouble() * height;
                clusterCenters.add(new Point2D.Double(cX, cY));
            }
            double stdDevX = width * CLUSTER_STD_DEV_FACTOR;
            double stdDevY = height * CLUSTER_STD_DEV_FACTOR;
            int count = 0;
            while (count < NUM_POINTS_PER_ZONE) {
                Point2D.Double center = clusterCenters.get(random.nextInt(clusterCenters.size()));
                double x = center.x + random.nextGaussian() * stdDevX;
                double y = center.y + random.nextGaussian() * stdDevY;
                if (x >= minX && x < (minX + width) && y >= minY && y < (minY + height)) {
                    points.add(new Point2D.Double(x, y));
                    count++;
                }
            }
        }

        private void generateRadialPoints(List<Point2D.Double> points, double minX, double minY, double width, double height) {
            double centerX = minX + width / 2.0;
            double centerY = minY + height / 2.0;
            double maxRadius = Math.min(width, height) / 2.0;
            int count = 0;
            while (count < NUM_POINTS_PER_ZONE) {
                double angle = random.nextDouble() * 2 * Math.PI;
                double r = Math.pow(random.nextDouble(), 1.0 + RADIAL_CONCENTRATION) * maxRadius;
                double x = centerX + r * Math.cos(angle);
                double y = centerY + r * Math.sin(angle);
                if (x >= minX && x < (minX + width) && y >= minY && y < (minY + height)) {
                    points.add(new Point2D.Double(x, y));
                    count++;
                }
            }
        }

        private void generateStratifiedPoints(List<Point2D.Double> points, double minX, double minY, double width, double height) {
            int numCellsPerSide = (int) Math.ceil(Math.sqrt(NUM_POINTS_PER_ZONE));
            double cellWidth = width / numCellsPerSide;
            double cellHeight = height / numCellsPerSide;
            int count = 0;
            outer:
            for (int row = 0; row < numCellsPerSide; row++) {
                for (int col = 0; col < numCellsPerSide; col++) {
                    if (count >= NUM_POINTS_PER_ZONE) {
                        break outer;
                    }
                    double cellMinX = minX + col * cellWidth;
                    double cellMinY = minY + row * cellHeight;
                    double x = cellMinX + random.nextDouble() * cellWidth;
                    double y = cellMinY + random.nextDouble() * cellHeight;
                    points.add(new Point2D.Double(x, y));
                    count++;
                }
            }
            while (points.size() < NUM_POINTS_PER_ZONE) {
                double x = minX + random.nextDouble() * width;
                double y = minY + random.nextDouble() * height;
                points.add(new Point2D.Double(x, y));
            }
            while (points.size() > NUM_POINTS_PER_ZONE) {
                points.remove(points.size() - 1);
            }
        }

        private void generateEdgeBiasPoints(List<Point2D.Double> points, double minX, double minY, double width, double height) {
            double centerX = minX + width / 2.0;
            double centerY = minY + height / 2.0;
            int count = 0;
            while (count < NUM_POINTS_PER_ZONE) {
                double x = minX + random.nextDouble() * width;
                double y = minY + random.nextDouble() * height;
                double normDistX = Math.abs(x - centerX) / (width / 2.0);
                double normDistY = Math.abs(y - centerY) / (height / 2.0);
                double normDist = Math.max(normDistX, normDistY);
                if (random.nextDouble() < Math.pow(normDist, EDGE_BIAS_POWER)) {
                    points.add(new Point2D.Double(x, y));
                    count++;
                }
            }
        }

        private void generateHorizontalBiasPoints(List<Point2D.Double> points, double minX, double minY, double width, double height) {
            double centerY = minY + height / 2.0;
            double stdDevY = height * LINEAR_BIAS_STD_DEV_FACTOR;
            int count = 0;
            while (count < NUM_POINTS_PER_ZONE) {
                double x = minX + random.nextDouble() * width;
                double y = centerY + random.nextGaussian() * stdDevY;
                if (x >= minX && x < (minX + width) && y >= minY && y < (minY + height)) {
                    points.add(new Point2D.Double(x, y));
                    count++;
                }
            }
        }

        private void generateVerticalBiasPoints(List<Point2D.Double> points, double minX, double minY, double width, double height) {
            double centerX = minX + width / 2.0;
            double stdDevX = width * LINEAR_BIAS_STD_DEV_FACTOR;
            int count = 0;
            while (count < NUM_POINTS_PER_ZONE) {
                double x = centerX + random.nextGaussian() * stdDevX;
                double y = minY + random.nextDouble() * height;
                if (x >= minX && x < (minX + width) && y >= minY && y < (minY + height)) {
                    points.add(new Point2D.Double(x, y));
                    count++;
                }
            }
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g;

            g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);

            int currentWidth = getWidth();
            int currentHeight = getHeight();
            int zoneWidth = currentWidth / GRID_SIZE;
            int zoneHeight = currentHeight / GRID_SIZE;
            currentWidth = zoneWidth * GRID_SIZE;
            currentHeight = zoneHeight * GRID_SIZE;

            g2d.setColor(Color.WHITE);
            g2d.fillRect(0, 0, currentWidth, currentHeight);

            g2d.setColor(POINT_COLOR);
            zonePoints.forEach(points -> {
                drawPoints(g2d, points);
            });

            g2d.setColor(LINE_COLOR);
            g2d.setStroke(new BasicStroke(1.5f));
            for (int i = 1; i < GRID_SIZE; i++) {
                g2d.drawLine(i * zoneWidth, 0, i * zoneWidth, currentHeight);
                g2d.drawLine(0, i * zoneHeight, currentWidth, i * zoneHeight);
            }
            g2d.setStroke(new BasicStroke(1.0f));

            g2d.setColor(LABEL_COLOR);
            g2d.setFont(LABEL_FONT);
            FontMetrics fm = g2d.getFontMetrics(LABEL_FONT);
            for (int row = 0; row < GRID_SIZE; row++) {
                for (int col = 0; col < GRID_SIZE; col++) {
                    int zoneIndex = row * GRID_SIZE + col;
                    int zoneX = col * zoneWidth;
                    int zoneY = row * zoneHeight;
                    int textX = zoneX + LABEL_LEFT_PADDING;
                    int textY = zoneY + LABEL_TOP_PADDING + fm.getAscent();
                    g2d.drawString(LABELS[zoneIndex], textX, textY);
                }
            }
        }

        private void drawPoints(Graphics2D g2d, List<Point2D.Double> points) {
            points.stream().filter(p -> (p != null)).forEachOrdered(p -> {
                g2d.drawLine((int) p.x, (int) p.y, (int) p.x, (int) p.y);
            });
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(WINDOW_WIDTH, WINDOW_HEIGHT);
        }
    }
}


Resultado:



viernes, 21 de febrero de 2025

Cálculo de la percepción del tiempo de vida.

Esta fórmula intenta mostrar cómo la percepción de los años que nos quedan disminuye a medida que envejecemos, debido a que cada año representa una porción cada vez más pequeña de nuestra vida total. No es que el tiempo vaya más rápido en realidad. Es nuestra percepción la que cambia.

Existe una fórmula conceptual que ilustra cómo la percepción del tiempo restante se reduce con la edad, basada en la proporción de vida ya vivida:

P = (E - A) * (1 - (A / E))

P: Percepción de años de vida restantes. Representa cuántos años de vida se sienten que quedan, teniendo en cuenta la edad actual.
E: Esperanza de vida estimada. Es una estimación de cuántos años se espera que viva una persona, basada en promedios poblacionales.
A: Edad actual. La edad de la persona para la cual se está calculando la percepción del tiempo.


Código Java (PercepcionTiempoVida.java)

package percepciontiempovida;

import java.util.Scanner;

public class PercepcionTiempoVida {

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        System.out.println("Calculadora de Percepción del Tiempo de Vida Restante");
        System.out.println("--------------------------------------------------");

        // Obtener la edad actual del usuario
        int edadActual = obtenerEdad(scanner);

        // Obtener la esperanza de vida estimada del usuario
        int esperanzaVida = obtenerEsperanzaVida(scanner, edadActual);

        // Calcular el tiempo real restante
        int tiempoRealRestante = esperanzaVida - edadActual;

        // Calcular el tiempo percibido restante usando la fórmula conceptual
        double tiempoPercibidoRestante = calcularTiempoPercibido(edadActual, esperanzaVida);

        // Mostrar los resultados
        System.out.println("\nResultados:");
        System.out.println("------------------");
        System.out.println("Edad Actual: " + edadActual + " años");
        System.out.println("Esperanza de Vida Estimada: " + esperanzaVida + " años");
        System.out.println("Tiempo Real Restante: " + tiempoRealRestante + " años");
        System.out.printf("Tiempo Percibido Restante (aproximado): %.2f años\n", tiempoPercibidoRestante);
        System.out.println("------------------");
        System.out.println("\nRecuerda: Esta es una ESTIMACIÓN conceptual basada en la proporción de vida vivida.");
        System.out.println("La percepción del tiempo es subjetiva y puede variar.");

        scanner.close();
    }

    // Función para obtener la edad del usuario con validación básica
    public static int obtenerEdad(Scanner scanner) {
        int edad;
        while (true) {
            System.out.print("Introduce tu edad actual (en años): ");
            if (scanner.hasNextInt()) {
                edad = scanner.nextInt();
                if (edad >= 0) {
                    return edad;
                } else {
                    System.out.println("Por favor, introduce una edad válida (no negativa).");
                }
            } else {
                System.out.println("Entrada inválida. Por favor, introduce un número entero.");
                scanner.next(); // Limpiar el buffer del scanner
            }
        }
    }

    // Función para obtener la esperanza de vida con validación básica (mayor que la edad actual)
    public static int obtenerEsperanzaVida(Scanner scanner, int edadActual) {
        int esperanzaVida;
        while (true) {
            System.out.print("Introduce tu esperanza de vida estimada (en años): ");
            if (scanner.hasNextInt()) {
                esperanzaVida = scanner.nextInt();
                if (esperanzaVida > edadActual) {
                    return esperanzaVida;
                } else {
                    System.out.println("La esperanza de vida debe ser mayor que tu edad actual.");
                }
            } else {
                System.out.println("Entrada inválida. Por favor, introduce un número entero.");
                scanner.next(); // Limpiar el buffer del scanner
            }
        }
    }

    // Función para calcular el tiempo percibido restante usando la fórmula conceptual
    public static double calcularTiempoPercibido(int edadActual, int esperanzaVida) {
        if (esperanzaVida <= edadActual) {
            return 0; // Para evitar división por cero o resultados negativos inesperados
        }
        return (double) (esperanzaVida - edadActual) * (1.0 - ((double) edadActual / esperanzaVida));
    }
}


Resultado:

run:
Calculadora de Percepción del Tiempo de Vida Restante
--------------------------------------------------
Introduce tu edad actual (en años): 35
Introduce tu esperanza de vida estimada (en años): 80

Resultados:
------------------
Edad Actual: 35 años
Esperanza de Vida Estimada: 80 años
Tiempo Real Restante: 45 años
Tiempo Percibido Restante (aproximado): 25,31 años
------------------

Recuerda: Esta es una ESTIMACIÓN conceptual basada en la proporción de vida vivida.
La percepción del tiempo es subjetiva y puede variar.
BUILD SUCCESSFUL (total time: 11 seconds)

sábado, 1 de febrero de 2025

Problema del viajante de Comercio TSP (IX.2). Método Genético (GA).

Este algoritmo en Java resuelve el clásico Problema del Viajante (TSP) utilizando una estrategia evolutiva basada en Algoritmos Genéticos (AG). En esta nueva versión, se prioriza el control preciso de la ejecución y la personalización, permitiendo ajustar parámetros clave, gestionar datos de entrada y optimizar resultados mediante configuraciones adaptables.

Funcionamiento:

    > Configuración Inicial:
        Genera una matriz de distancias (simétrica/asimétrica) entre ciudades.
        Crea una población inicial de rutas aleatorias.

    > Evolución de Soluciones:
        Selección: Usa torneos para elegir las mejores rutas como padres.
        Cruce (Order Crossover): Combina segmentos de rutas para generar descendencia.
        Mutación Adaptativa: Aplica swap o inversión de segmentos para diversificar rutas.
        Élitismo: Conserva las mejores soluciones entre generaciones.

    > Optimización Dinámica:
        Ajusta automáticamente la tasa de mutación si no hay mejoras recientes.
        Permite límite de tiempo (ej: 30 segundos) para equilibrar calidad y eficiencia.

Características Destacadas:

    ✅ Matrices Personalizables: Genera matrices aleatorias con rangos de distancias definidos.
    ✅ Parámetros Adaptativos: Mutación variable y ajuste de población para evitar estancamientos.
    ✅ Informes Ejecutivos: Guarda resultados detallados (tiempo, mejor ruta, parámetros).
    ✅ Interactividad: Menú intuitivo para configurar y ejecutar el algoritmo.



Código Java (TSPGeneticAlgorithm.java):

package tspgeneticalgorithm;

import java.io.FileWriter;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.InputMismatchException;
import java.util.List;
import java.util.Locale;
import java.util.Random;
import java.util.Scanner;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;

public class TSPGeneticAlgorithm {

    private final int[][] distanceMatrix;
    private final int numCities;
    private int populationSize = 100;          // Valor optimizado
    private int numGenerations = 1500;         // Valor irrelevante (lo controla el tiempo)
    private double mutationRate = 0.04;        // Tasa base (configurable por el usuario)
    private double currentMutationRate;        // Tasa usada durante la ejecución
    private final Random random = new Random();
    private long timeLimitMillis = 10000;      // 10 segundos
    private String matrixTypeDescription = "";
    private int minDistanceValue = 1;
    private int maxDistanceValue = 1000;
    private boolean includeMatrixInReport = false;
    private int generation = 0;
    private int lastImprovement = 0;

    // Operadores genéticos
    private final CrossoverOperator crossoverOperator;
    private final MutationOperator mutationOperator = new SwapMutation();

    public TSPGeneticAlgorithm(int[][] distanceMatrix) {
        validateMatrix(distanceMatrix);
        this.distanceMatrix = distanceMatrix;
        this.numCities = distanceMatrix.length;
        this.crossoverOperator = new OrderCrossover(this.distanceMatrix);
        this.currentMutationRate = this.mutationRate;
    }

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in).useLocale(Locale.US);
        TSPGeneticAlgorithm ga = null;
        int[][] distanceMatrix = null;
        String matrixTypeDescription = "";
        int minDistanceValue = 1;
        int maxDistanceValue = 1000;
        boolean includeMatrixInReport = false;
        int defaultNumNodes = 16;

        while (true) {
            System.out.println("\n--- Menú Principal ---");
            System.out.println("1. Generar matriz de distancias");
            System.out.println("2. Configurar parámetros del AG");
            System.out.println("3. Mostrar parámetros actuales");
            System.out.println("4. Ejecutar algoritmo genético");
            System.out.println("0. Salir");
            System.out.print("Seleccione una opción [0-4]: ");

            int choice;
            try {
                choice = scanner.nextInt();
                scanner.nextLine();
            } catch (InputMismatchException e) {
                System.out.println("Entrada inválida. Por favor, ingrese un número.");
                scanner.next();
                continue;
            }

            switch (choice) {
                case 1 -> {
                    Object[] matrixInfo = configureAndGenerateMatrix(scanner, minDistanceValue, maxDistanceValue, defaultNumNodes);
                    distanceMatrix = (int[][]) matrixInfo[0];
                    matrixTypeDescription = (String) matrixInfo[1];
                    minDistanceValue = (Integer) matrixInfo[2];
                    maxDistanceValue = (Integer) matrixInfo[3];
                    includeMatrixInReport = (Boolean) matrixInfo[4];

                    if (distanceMatrix != null) {
                        ga = new TSPGeneticAlgorithm(distanceMatrix);
                        ga.setMatrixGenerationInfo(matrixTypeDescription, minDistanceValue, maxDistanceValue);
                        ga.setIncludeMatrixInReport(includeMatrixInReport);
                    }
                }
                case 2 -> {
                    if (distanceMatrix != null) {
                        if (ga == null) {
                            ga = new TSPGeneticAlgorithm(distanceMatrix);
                        }
                        configureGAParameters(scanner, ga);
                    } else {
                        System.out.println("Por favor, genere la matriz de distancias primero.");
                    }
                }
                case 3 -> {
                    if (ga != null) {
                        System.out.println("\n--- MATRIZ DE DISTANCIAS ---");
                        printDistanceMatrix(ga.distanceMatrix, ga.maxDistanceValue);
                        System.out.println("\nNodos: " + ga.numCities);
                        System.out.println("Rango distancias: [" + ga.minDistanceValue + " - " + ga.maxDistanceValue + "]");
                        System.out.println("Tipo de matriz: " + ga.matrixTypeDescription);

                        System.out.println("\n--- PARÁMETROS (AG) ---");
                        ga.showCurrentParametersAG();
                    } else {
                        System.out.println("Por favor, genere la matriz de distancias primero.");
                    }
                }
                case 4 -> {
                    if (ga != null) {
                        long defaultTimeLimit = 10;
                        System.out.printf("Tiempo límite en segundos (0=ilimitado) [%d]: ", defaultTimeLimit);
                        String input = scanner.nextLine().trim();

                        try {
                            long timeLimitSeconds = input.isEmpty() ? defaultTimeLimit : Long.parseLong(input);
                            ga.timeLimitMillis = (timeLimitSeconds > 0) ? TimeUnit.SECONDS.toMillis(timeLimitSeconds) : -1;
                        } catch (NumberFormatException e) {
                            System.out.println("Entrada inválida. Usando 10 segundos por defecto.");
                            ga.timeLimitMillis = TimeUnit.SECONDS.toMillis(defaultTimeLimit);
                        }
                        runGeneticAlgorithm(ga);
                    } else {
                        System.out.println("Por favor, genere la matriz de distancias primero.");
                    }
                }
                case 0 -> {
                    System.out.println("Saliendo del programa.");
                    scanner.close();
                    return;
                }
                default ->
                    System.out.println("Opción no válida. Intente de nuevo.");
            }
        }
    }

    private static Object[] configureAndGenerateMatrix(Scanner scanner, int defaultMinDistance, int defaultMaxDistance, int defaultNumNodes) {
        System.out.println("\n--- Configuración de la Matriz ---");

        // Configurar número de nodos
        int numNodes = 0;
        while (numNodes <= 3) {
            System.out.printf("Número de nodos [%d]: ", defaultNumNodes);
            String numNodesStr = scanner.nextLine();

            if (numNodesStr.isEmpty()) {
                numNodes = defaultNumNodes;
                if (numNodes <= 3) {
                    System.out.println("El valor por defecto debe ser mayor que 3. Cambie el valor por defecto.");
                    continue;
                }
                break;
            }

            try {
                numNodes = Integer.parseInt(numNodesStr);
                if (numNodes <= 3) {
                    System.out.println("El número de nodos debe ser mayor que 3.");
                }
            } catch (NumberFormatException e) {
                System.out.println("Entrada inválida. Por favor, ingrese un número.");
            }
        }

        // Configurar tipo de matriz
        System.out.println("Tipo de matriz:");
        System.out.println("1. Aleatoria Simétrica");
        System.out.println("2. Aleatoria Asimétrica");
        System.out.print("Seleccione el tipo de matriz [1]: ");
        int matrixType = 0;
        while (matrixType != 1 && matrixType != 2) {
            String matrixTypeStr = scanner.nextLine();
            if (matrixTypeStr.isEmpty()) {
                matrixType = 1;
                break;
            }
            try {
                matrixType = Integer.parseInt(matrixTypeStr);
                if (matrixType != 1 && matrixType != 2) {
                    System.out.println("Tipo de matriz no válido. Elija 1 o 2.");
                }
            } catch (NumberFormatException e) {
                System.out.println("Entrada inválida. Por favor, ingrese 1 o 2.");
            }
        }

        // Configurar valor mínimo
        int minDistance = 0;
        while (true) {
            System.out.printf("Valor mínimo de la distancia [%d]: ", defaultMinDistance);
            String minDistanceStr = scanner.nextLine();
            if (minDistanceStr.isEmpty()) {
                minDistance = defaultMinDistance;
                break;
            }
            try {
                minDistance = Integer.parseInt(minDistanceStr);
                break;
            } catch (NumberFormatException e) {
                System.out.println("Entrada inválida. Por favor, ingrese un número.");
            }
        }

        // Configurar valor máximo (validación reforzada)
        int maxDistance = 0;
        while (true) {
            System.out.printf("Valor máximo de la distancia [%d]: ", defaultMaxDistance);
            String maxDistanceStr = scanner.nextLine();

            try {
                if (maxDistanceStr.isEmpty()) {
                    maxDistance = defaultMaxDistance;
                } else {
                    maxDistance = Integer.parseInt(maxDistanceStr);
                }

                // Validación crítica
                if (maxDistance < minDistance) {
                    System.out.println("ERROR: El máximo debe ser >= al mínimo (" + minDistance + ")");
                    System.out.println("Por favor, ingrese un valor válido.");
                    continue;
                }

                break; // Salir solo si el valor es válido

            } catch (NumberFormatException e) {
                System.out.println("Entrada inválida. Ingrese un número.");
            }
        }

        // Configurar inclusión en informe
        System.out.print("¿Incluir la matriz en el informe de ejecución? [S/n]: ");
        String includeMatrixOption = scanner.nextLine();
        boolean includeMatrixInReport = !includeMatrixOption.equalsIgnoreCase("n"); // Enter = Sí

        // Generar matriz
        int[][] distanceMatrix;
        String matrixTypeDescription;
        if (matrixType == 1) {
            distanceMatrix = generateRandomSymmetricDistanceMatrix(numNodes, minDistance, maxDistance);
            matrixTypeDescription = "Aleatoria Simétrica";
        } else {
            distanceMatrix = generateRandomAsymmetricDistanceMatrix(numNodes, minDistance, maxDistance);
            matrixTypeDescription = "Aleatoria Asimétrica";
        }

        System.out.println("Matriz de distancias generada:");
        printDistanceMatrix(distanceMatrix, maxDistance);

        return new Object[]{distanceMatrix, matrixTypeDescription, minDistance, maxDistance, includeMatrixInReport};
    }

    private static void configureGAParameters(Scanner scanner, TSPGeneticAlgorithm ga) {
        System.out.println("\n--- Configuración del Algoritmo Genético ---");
        System.out.print("¿Usar valores por defecto? [S/n]: ");
        String useDefaults = scanner.nextLine();
        if (useDefaults.equalsIgnoreCase("n")) { // Enter = Sí
            ga.populationSize = getIntInputWithDefault(scanner, "Tamaño de la población", ga.populationSize);
            ga.numGenerations = getIntInputWithDefault(scanner, "Número de generaciones (límite)", ga.numGenerations);
            ga.mutationRate = getDoubleInputWithDefault(scanner, "Tasa de mutación", ga.mutationRate);
        } else {
            System.out.println("Usando valores por defecto optimizados.");
        }
        System.out.println("\nParámetros actualizados:");
        ga.showCurrentParameters();
    }

    private static int getIntInputWithDefault(Scanner scanner, String message, int defaultValue) {
        while (true) {
            System.out.printf("%s [%d]: ", message, defaultValue);
            String inputStr = scanner.nextLine();
            if (inputStr.isEmpty()) {
                return defaultValue;
            }
            try {
                return Integer.parseInt(inputStr);
            } catch (NumberFormatException e) {
                System.out.println("Entrada inválida. Por favor, ingrese un número entero.");
            }
        }
    }

    private static double getDoubleInputWithDefault(Scanner scanner, String message, double defaultValue) {
        while (true) {
            System.out.printf("%s [%.3f]: ", message, defaultValue);
            String inputStr = scanner.nextLine();
            if (inputStr.isEmpty()) {
                return defaultValue;
            }
            try {
                return Double.parseDouble(inputStr);
            } catch (NumberFormatException e) {
                System.out.println("Entrada inválida. Por favor, ingrese un número decimal.");
            }
        }
    }

    private static String formatElapsedTime(double elapsedTimeMillis) {
        double seconds = elapsedTimeMillis / 1000.0;
        return String.format("%.3f segundos", seconds);
    }

    private static void runGeneticAlgorithm(TSPGeneticAlgorithm ga) {
        System.out.println("\n--- Ejecución del Algoritmo Genético ---");
        System.out.println("Iniciando búsqueda " + (ga.timeLimitMillis > 0
                ? "con límite de tiempo: " + (ga.timeLimitMillis / 1000) + " segundos"
                : "sin límite de tiempo") + "\n");

        long startTime = System.currentTimeMillis();
        Solution bestSolution = ga.run();
        long endTime = System.currentTimeMillis();
        double elapsedTimeMillis = endTime - startTime;

        System.out.println("\nBúsqueda finalizada.");
        System.out.println("Mejor ruta encontrada: " + bestSolution);
        System.out.println("Distancia total: " + bestSolution.getDistance());
        System.out.println("Tiempo de ejecución: " + formatElapsedTime(elapsedTimeMillis));

        System.out.print("¿Guardar informe de ejecución? [S/n]): ");
        Scanner scanner = new Scanner(System.in);
        String saveOption = scanner.nextLine();
        if (!saveOption.equalsIgnoreCase("n")) { // Enter = Sí
            String filename = generateReportFilename(ga);
            saveExecutionReportToFile(ga, bestSolution, elapsedTimeMillis, filename);
        }
    }

    private static String generateReportFilename(TSPGeneticAlgorithm ga) {
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyyMMddHHmm");
        String timestamp = LocalDateTime.now().format(dtf);
        String matrixType = ga.matrixTypeDescription.startsWith("Aleatoria Simétrica") ? "S" : "A";
        String mutationRateStr = String.format("%04d", (int) (ga.mutationRate * 10000));
        String timeLimitStr = (ga.timeLimitMillis > 0) ? String.valueOf(TimeUnit.MILLISECONDS.toSeconds(ga.timeLimitMillis)) : "0";
        String randomID = generateRandomString(8);

        return String.format("Report_%s_%d%s_P%d_G%d_M%s_T%s_%s.txt",
                timestamp,
                ga.numCities,
                matrixType,
                ga.populationSize,
                ga.numGenerations,
                mutationRateStr,
                timeLimitStr,
                randomID
        );
    }

    private static String generateRandomString(int length) {
        String allowedChars = "abcdefghijklmnopqrstuvwxyz1234567890";
        StringBuilder sb = new StringBuilder(length);
        Random random = new Random();
        for (int i = 0; i < length; i++) {
            sb.append(allowedChars.charAt(random.nextInt(allowedChars.length())));
        }
        return sb.toString();
    }

    private static void saveExecutionReportToFile(TSPGeneticAlgorithm ga, Solution solution, double elapsedTimeMillis, String filename) {
        try (FileWriter writer = new FileWriter(filename)) {
            writer.write("=== INFORME DE EJECUCIÓN TSP ALGORITMO GENÉTICO (AG) ===\n\n");
            writer.write("Fecha: " + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) + "\n\n");

            writer.write("=== PARÁMETROS AG ===\n\n");
            writer.write("Población: " + ga.populationSize + "\n");
            writer.write("Generaciones: " + ga.numGenerations + "\n");
            writer.write("Mutación: " + ga.mutationRate + "\n");
            writer.write("Tiempo límite: " + (ga.timeLimitMillis > 0 ? (ga.timeLimitMillis / 1000) + " s" : "Ilimitado") + "\n\n");

            writer.write("=== PARÁMETROS MATRIZ DE DISTANCIAS ===\n\n");
            writer.write("Tipo: " + ga.matrixTypeDescription + "\n");
            writer.write("Nodos: " + ga.numCities + "\n\n");
            if (ga.includeMatrixInReport) {
                writer.write("Matriz:\n");
                printDistanceMatrix(ga.distanceMatrix, ga.maxDistanceValue, writer);
            }
            writer.write("\n=== RESULTADOS ===\n\n");
            writer.write("Mejor ruta: " + Arrays.toString(solution.getPath()) + "\n");
            writer.write("Distancia: " + String.format("%.1f", solution.getDistance()) + "\n");
            writer.write("Tiempo ejecución: " + formatElapsedTime(elapsedTimeMillis) + "\n");

            System.out.println("Informe guardado como: " + filename);
        } catch (IOException e) {
            System.out.println("Error al guardar el informe: " + e.getMessage());
        }
    }

    public void showCurrentParameters() {
        System.out.println("\n--- PARÁMETROS (AG) ---");
        showCurrentParametersAG();
    }

    public void showCurrentParametersAG() {
        System.out.println("Tamaño de población: " + populationSize);
        System.out.println("Número de generaciones: " + numGenerations);
        System.out.println("Tasa de mutación: " + mutationRate);
    }

    public Solution run() {
        currentMutationRate = mutationRate;
        List<Solution> population = initializePopulation();
        Solution bestSolution = getFittest(population);
        long startTime = System.currentTimeMillis();
        generation = 0;
        lastImprovement = 0;

        while (true) {
            boolean timeExpired = (timeLimitMillis > 0) && (System.currentTimeMillis() - startTime >= timeLimitMillis);
            if (timeExpired || generation >= numGenerations) {
                break;
            }

            // Elitismo: conservar el 2% de los mejores
            int eliteSize = Math.max(1, (int) (populationSize * 0.02));
            List<Solution> newPopulation = new ArrayList<>(population.subList(0, eliteSize));

            while (newPopulation.size() < populationSize) {
                Solution parent1 = tournamentSelection(population);
                Solution parent2 = tournamentSelection(population);
                Solution offspring = crossoverOperator.crossover(parent1.getPath(), parent2.getPath(), random);
                mutate(offspring);
                newPopulation.add(offspring);
            }

            population = new ArrayList<>(newPopulation);
            Solution currentBest = getFittest(population);
            if (currentBest.getDistance() < bestSolution.getDistance()) {
                bestSolution = currentBest;
                lastImprovement = generation;
            }

            // Ajuste adaptativo de mutación
            if (generation - lastImprovement > 20) {
                mutationRate = Math.min(0.1, mutationRate * 1.2);
            }

            // Verificación periódica del tiempo
            if (generation % 5 == 0 && timeLimitMillis > 0) {
                if (System.currentTimeMillis() - startTime >= timeLimitMillis) {
                    break;
                }
            }
            generation++;
        }
        return bestSolution;
    }

    private List<Solution> initializePopulation() {
        List<Solution> population = new ArrayList<>();
        int[] cities = IntStream.range(0, numCities).toArray();
        for (int i = 0; i < populationSize; i++) {
            int[] shuffledPath = Arrays.copyOf(cities, cities.length);
            shuffleArray(shuffledPath);
            population.add(new Solution(shuffledPath, calculateDistance(shuffledPath)));
        }
        return population;
    }

    private void shuffleArray(int[] array) {
        for (int i = array.length - 1; i > 0; i--) {
            int index = random.nextInt(i + 1);
            int temp = array[index];
            array[index] = array[i];
            array[i] = temp;
        }
    }

    private double calculateDistance(int[] path) {
        double distance = 0;
        for (int i = 0; i < numCities - 1; i++) {
            distance += distanceMatrix[path[i]][path[i + 1]];
        }
        distance += distanceMatrix[path[numCities - 1]][path[0]];
        return distance;
    }

    private Solution tournamentSelection(List<Solution> population) {
        int tournamentSize = 3;  // Reducido para mayor velocidad
        List<Solution> tournament = new ArrayList<>();
        for (int i = 0; i < tournamentSize; i++) {
            tournament.add(population.get(random.nextInt(population.size())));
        }
        return getFittest(tournament);
    }

    private void mutate(Solution solution) {
        double dynamicMutationRate = currentMutationRate;
        if (generation - lastImprovement > 20) {
            currentMutationRate = Math.min(0.1, currentMutationRate * 1.2);
        }
        if (random.nextDouble() < dynamicMutationRate) {
            mutationOperator.mutate(solution.getPath(), random);
            solution.setDistance(calculateDistance(solution.getPath()));
        }
    }

    private Solution getFittest(List<Solution> population) {
        Solution fittest = population.get(0);
        for (Solution solution : population) {
            if (solution.getDistance() < fittest.getDistance()) {
                fittest = solution;
            }
        }
        return fittest;
    }

    interface CrossoverOperator {

        Solution crossover(int[] parent1, int[] parent2, Random random);
    }

    interface MutationOperator {

        void mutate(int[] path, Random random);
    }

    static class OrderCrossover implements CrossoverOperator {

        private final int[][] distanceMatrix;

        public OrderCrossover(int[][] distanceMatrix) {
            this.distanceMatrix = distanceMatrix;
        }

        @Override
        public Solution crossover(int[] parent1, int[] parent2, Random random) {
            int length = parent1.length;
            int[] childPath = new int[length];
            Arrays.fill(childPath, -1);

            int start = random.nextInt(length);
            int end = random.nextInt(length);

            if (start > end) {
                int temp = start;
                start = end;
                end = temp;
            }

            for (int i = start; i <= end; i++) {
                childPath[i] = parent1[i];
            }

            int currentPos = 0;
            for (int city : parent2) {
                if (!contains(childPath, city)) {
                    while (currentPos < length && childPath[currentPos] != -1) {
                        currentPos++;
                    }
                    if (currentPos < length) {
                        childPath[currentPos] = city;
                    }
                }
            }

            double distance = calculateDistance(childPath);
            return new Solution(childPath, distance);
        }

        private boolean contains(int[] array, int value) {
            for (int num : array) {
                if (num == value) {
                    return true;
                }
            }
            return false;
        }

        private double calculateDistance(int[] path) {
            double distance = 0;
            for (int i = 0; i < path.length - 1; i++) {
                distance += distanceMatrix[path[i]][path[i + 1]];
            }
            distance += distanceMatrix[path[path.length - 1]][path[0]];
            return distance;
        }
    }

    static class SwapMutation implements MutationOperator {

        @Override
        public void mutate(int[] path, Random random) {
            // 70% swap mutation, 30% inversión
            if (random.nextDouble() < 0.7) {
                // Swap mutation
                int index1 = random.nextInt(path.length);
                int index2 = random.nextInt(path.length);
                int temp = path[index1];
                path[index1] = path[index2];
                path[index2] = temp;
            } else {
                // Inversión de segmento
                int start = random.nextInt(path.length);
                int end = start + random.nextInt(path.length - start);
                while (start < end) {
                    int temp = path[start];
                    path[start] = path[end];
                    path[end] = temp;
                    start++;
                    end--;
                }
            }
        }
    }

    static class Solution {

        private final int[] path;
        private double distance;

        public Solution(int[] path, double distance) {
            this.path = Arrays.copyOf(path, path.length);
            this.distance = distance;
        }

        public int[] getPath() {
            return Arrays.copyOf(path, path.length);
        }

        public double getDistance() {
            return distance;
        }

        public void setDistance(double distance) {
            this.distance = distance;
        }

        @Override
        public String toString() {
            return Arrays.toString(path);
        }
    }

    public static int[][] generateRandomSymmetricDistanceMatrix(int numNodes, int minDistance, int maxDistance) {
        int[][] matrix = new int[numNodes][numNodes];
        Random rand = new Random();
        for (int i = 0; i < numNodes; i++) {
            for (int j = i + 1; j < numNodes; j++) {
                int value = rand.nextInt(maxDistance - minDistance + 1) + minDistance;
                matrix[i][j] = value;
                matrix[j][i] = value;
            }
        }
        return matrix;
    }

    public static int[][] generateRandomAsymmetricDistanceMatrix(int numNodes, int minDistance, int maxDistance) {
        int[][] matrix = new int[numNodes][numNodes];
        Random rand = new Random();
        for (int i = 0; i < numNodes; i++) {
            for (int j = 0; j < numNodes; j++) {
                if (i != j) {
                    matrix[i][j] = rand.nextInt(maxDistance - minDistance + 1) + minDistance;
                }
            }
        }
        return matrix;
    }

    public static void printDistanceMatrix(int[][] matrix, int maxDistanceValue) {
        printDistanceMatrix(matrix, maxDistanceValue, null);
    }

    private static void printDistanceMatrix(int[][] matrix, int maxDistanceValue, FileWriter writer) {
        int numDigits = String.valueOf(maxDistanceValue).length() + 2;
        String format = "%" + numDigits + "d";

        for (int[] row : matrix) {
            StringBuilder sb = new StringBuilder();
            for (int val : row) {
                sb.append(String.format(format, val));
            }
            String line = sb.toString();
            if (writer == null) {
                System.out.println(line);
            } else {
                try {
                    writer.write(line + "\n");
                } catch (IOException e) {
                    System.out.println("Error escribiendo en archivo: " + e.getMessage());
                }
            }
        }
    }

    private void validateMatrix(int[][] matrix) {
        if (matrix.length < 4) {
            throw new IllegalArgumentException("La matriz debe tener al menos 4 nodos.");
        }
        for (int i = 0; i < matrix.length; i++) {
            if (matrix[i][i] != 0) {
                matrix[i][i] = 0;
            }
        }
    }

    public void setMatrixGenerationInfo(String type, int min, int max) {
        this.matrixTypeDescription = type;
        this.minDistanceValue = min;
        this.maxDistanceValue = max;
    }

    public void setIncludeMatrixInReport(boolean include) {
        this.includeMatrixInReport = include;
    }
}


Resultado:

run:

--- Menú Principal ---
1. Generar matriz de distancias
2. Configurar parámetros del AG
3. Mostrar parámetros actuales
4. Ejecutar algoritmo genético
0. Salir
Seleccione una opción [0-4]: 1

--- Configuración de la Matriz ---
Número de nodos [16]: 8
Tipo de matriz:
1. Aleatoria Simétrica
2. Aleatoria Asimétrica
Seleccione el tipo de matriz [1]: 2
Valor mínimo de la distancia [1]:
Valor máximo de la distancia [1000]:
¿Incluir la matriz en el informe de ejecución? [S/n]: s
Matriz de distancias generada:
     0   707   455   183   113   990   675   872
   988     0   610   426   600     2   499   696
   850   724     0   449   556   847   678   729
   820   519   745     0    82   657   755   885
   436   665   918   666     0   707   269   794
   337   122   607   446   428     0   809   410
   501   446   697   542   626   768     0   726
   414   711   721   971   832   685   210     0

--- Menú Principal ---
1. Generar matriz de distancias
2. Configurar parámetros del AG
3. Mostrar parámetros actuales
4. Ejecutar algoritmo genético
0. Salir
Seleccione una opción [0-4]: 2

--- Configuración del Algoritmo Genético ---
¿Usar valores por defecto? [S/n]:
Usando valores por defecto optimizados.

Parámetros actualizados:

--- PARÁMETROS (AG) ---
Tamaño de población: 100
Número de generaciones: 1500
Tasa de mutación: 0.04

--- Menú Principal ---
1. Generar matriz de distancias
2. Configurar parámetros del AG
3. Mostrar parámetros actuales
4. Ejecutar algoritmo genético
0. Salir
Seleccione una opción [0-4]: 3

--- MATRIZ DE DISTANCIAS ---
     0   707   455   183   113   990   675   872
   988     0   610   426   600     2   499   696
   850   724     0   449   556   847   678   729
   820   519   745     0    82   657   755   885
   436   665   918   666     0   707   269   794
   337   122   607   446   428     0   809   410
   501   446   697   542   626   768     0   726
   414   711   721   971   832   685   210     0

Nodos: 8
Rango distancias: [1 - 1000]
Tipo de matriz: Aleatoria Asimétrica

--- PARÁMETROS (AG) ---
Tamaño de población: 100
Número de generaciones: 1500
Tasa de mutación: 0.04

--- Menú Principal ---
1. Generar matriz de distancias
2. Configurar parámetros del AG
3. Mostrar parámetros actuales
4. Ejecutar algoritmo genético
0. Salir
Seleccione una opción [0-4]: 4
Tiempo límite en segundos (0=ilimitado) [10]:

--- Ejecución del Algoritmo Genético ---
Iniciando búsqueda con límite de tiempo: 10 segundos


Búsqueda finalizada.
Mejor ruta encontrada: [2, 3, 4, 6, 1, 5, 7, 0]
Distancia total: 2527.0
Tiempo de ejecución: 0,130 segundos
¿Guardar informe de ejecución? [S/n]):
Informe guardado como: Report_202502012204_8A_P100_G1500_M1000_T10_gpobvzpy.txt

--- Menú Principal ---
1. Generar matriz de distancias
2. Configurar parámetros del AG
3. Mostrar parámetros actuales
4. Ejecutar algoritmo genético
0. Salir
Seleccione una opción [0-4]: 0
Saliendo del programa.
BUILD SUCCESSFUL (total time: 52 seconds)

Con la tecnología de Blogger.