Back to Blog
Algorithms Java Optimization December 1, 2024

Genetic Algorithms: Survival of the Bittest

How nature solves complex problems and how we translate that into code. A practical introduction with Java.

Over billions of years, nature has developed a remarkably effective optimization process: evolution. Genetic algorithms (GA) transfer this principle into computer science and solve problems where classical algorithms fail.

The Basic Principle

Imagine you are searching for the best solution to a problem, but the solution space is enormous. Trying all possibilities would take years. Genetic algorithms take a different approach: they simulate evolution.

1
Population
Random initial solutions
2
Fitness
Evaluation of each solution
3
Selection
The best survive
4
Crossover
Combining parents
5
Mutation
Random variation
6
Repeat
Until goal is reached

A Concrete Example

Our goal: Find a binary string where all bits are set to 1. Sounds trivial? It is. But this simple example demonstrates all the concepts that work the same way for more complex problems.

The Individual: An array of 5 bits, randomly initialized with 0 or 1.

1
0
1
0
1
Fitness: 3

The Fitness: Number of ones. Maximum is 5, then we have the perfect solution.

The Implementation in Java

The Individual

Each individual represents a possible solution:

public class Individual {
    private int[] genes = new int[5];
    private int fitness = 0;

    // Random initialization
    public Individual() {
        Random rn = new Random();
        for (int i = 0; i < genes.length; i++) {
            genes[i] = rn.nextInt(2); // 0 or 1
        }
    }

    // Fitness = number of ones
    public void calcFitness() {
        fitness = 0;
        for (int gene : genes) {
            if (gene == 1) fitness++;
        }
    }
}

The Population

A population manages multiple individuals:

public class Population {
    private Individual[] individuals = new Individual[10];
    private int fittest = 0; // Best fitness of the population

    public Individual getFittest() {
        int maxFit = Integer.MIN_VALUE;
        Individual fittestIndividual = null;

        for (Individual ind : individuals) {
            if (ind.getFitness() > maxFit) {
                maxFit = ind.getFitness();
                fittestIndividual = ind;
            }
        }
        return fittestIndividual;
    }

    public Individual getSecondFittest() {
        // Similar, but second best
        ...
    }
}

Selection

The two best individuals are selected as parents. There are various selection methods like tournament or roulette. Here we use elitist selection: simple and effective.

public void selection() {
    fittest = population.getFittest();
    secondFittest = population.getSecondFittest();
}

Crossover

The core mechanism: At a random point, the genes of both parents are swapped. The genes up to the crossover point are exchanged between the parents.

Parent 1:
1
0
1
1
0
Parent 2:
0
1
0
0
1
Crossover point: Position 2
Child 1:
0
1
1
1
0
public void crossover() {
    Random rn = new Random();
    int crossOverPoint = rn.nextInt(fittest.genes.length);

    for (int i = 0; i < crossOverPoint; i++) {
        int temp = fittest.genes[i];
        fittest.genes[i] = secondFittest.genes[i];
        secondFittest.genes[i] = temp;
    }
}

Mutation

Mutation prevents the algorithm from getting stuck in local optima. A random bit is flipped.

public void mutation() {
    Random rn = new Random();

    // 10% probability of mutation
    if (rn.nextDouble() < 0.1) {
        int mutationPoint = rn.nextInt(fittest.genes.length);

        // Flip bit: 0 → 1 or 1 → 0
        fittest.genes[mutationPoint] = 1 - fittest.genes[mutationPoint];
    }
}

The Algorithm

Putting it all together: Iterate until the perfect solution is found.

public static void main(String[] args) {
    GeneticAlgorithm ga = new GeneticAlgorithm();

    // 1. Initialize population
    ga.population.initializePopulation(10);
    ga.population.calculateFitness();

    int generation = 0;

    // 2. Evolution loop
    while (ga.population.fittest < 5) {
        generation++;

        // Selection
        ga.selection();

        // Crossover
        ga.crossover();

        // Mutation
        ga.mutation();

        // Replace weakest individual
        ga.addFittestOffspring();

        // Recalculate
        ga.population.calculateFitness();

        System.out.println("Generation: " + generation +
            " Fitness: " + ga.population.fittest);
    }

    System.out.println("Solution found in generation " + generation);
}

Typical Output

Generation: 1  Fitness: 3
Generation: 2  Fitness: 3
Generation: 3  Fitness: 4
Generation: 4  Fitness: 4
Generation: 5  Fitness: 5
Solution found in generation 5
Genes: [1, 1, 1, 1, 1]

With only 5 generations and 10 individuals, the algorithm finds the perfect solution. For more complex problems it takes longer, but the principle remains identical.

Real-World Use Cases

This simple example can be applied to complex problems:

Traveling Salesman Problem

Genes = order of cities. Fitness = total distance (minimize). Crossover = combining route segments.

Neural Network Training

Genes = network weights. Fitness = prediction accuracy. Evolution finds optimal weights without backpropagation.

Feature Selection

Genes = which features to use (0/1). Fitness = model performance. Automatically finds the most important features.

Scheduling

Genes = assignment of tasks to time slots. Fitness = deadline compliance, resource utilization.

Key Learnings

"Genetic algorithms do not guarantee an optimal solution, but they often find a very good one in acceptable time."

1. Encoding is crucial

How you represent the problem as genes determines success. Binary is simple, but permutations, floats, or complex structures are often more suitable.

2. Balance between exploration and exploitation

Too much mutation = chaotic search. Too little = premature convergence. The right mutation rate is problem-specific.

3. The fitness function is key

A poor fitness function leads to poor solutions. Invest time in a precise evaluation function.

4. Maintain diversity

When all individuals become similar, evolution stagnates. Techniques like crowding or niching help.

Conclusion

Genetic algorithms are a powerful tool for optimization problems where the solution space is too large for exhaustive search. The implementation is surprisingly simple: population, fitness, selection, crossover, mutation. The art lies in the right encoding and the appropriate fitness function.

The beauty of it: You don't need to understand what the optimal solution looks like. You only need to be able to evaluate how good a solution is. Evolution takes care of the rest.


Technologies used:

Java Evolutionary Computation

Interested in optimization algorithms or AI-powered solutions? Get in touch, I look forward to the conversation.