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

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.