Better Routing

Lecture 7 - Management Science

Dr. Tobias Vlćek

Introduction

Client Briefing: Artisan Bakery

Master Baker’s Morning Dilemma:

“Every morning at 5:00, our delivery van leaves with fresh bread for 16 cafés across the city. Our driver currently takes much too long using his ‘intuition’ for the route. The fuel costs are killing us, and worse, some cafés get their bread late.”

The Delivery Challenge

Artisan Bakery’s daily logistics puzzle:

  • 16 Cafés: Each expecting fresh bread by 8:00
  • One Van: Unlimited capacity, must visit all locations
  • Time Windows: 3 cafés open early (6:30) and need priority
  • Current Problem: Driver uses “gut feeling” for routing

The Stakes: Poor routing costs plus reputation damage from late deliveries!

Quick Recap: Greedy Decisions

Last week we learned greedy algorithms for scheduling:

  • SPT: Process shortest jobs first
  • EDD: Process by earliest due date
  • Fast & Simple: Made quick decisions, no looking back

Question: Can we use the same greedy approach for routing?

Today: We’ll start greedy, then learn how to improve solutions with local search!

The Routing Problem

The Traveling Salesman Problem

Visit all locations exactly once, minimize total distance.

Map of 16 cafés scattered over an area of about 30 by 10 km, with the bakery in the centre and no route drawn. The task is to link all points in one round trip that is as short as possible.

Compute Everything?

How many unique tours exist? With depot, n!/2 unique tours.

If your computer checks 1 million routes per second:

  • 4 cafés: 4!/2 = 12 tours → 0.000012 seconds ✓
  • 8 cafés: 8!/2 = 20,160 tours → 0.02 seconds ✓
  • 12 cafés: 12!/2 = 239,500,800 tours → 4 minutes ~
  • 16 cafés: 16!/2 = 10,461,394,944,000 tours → 4 months! ✗
  • 20 cafés: 20!/2 → 38,573 years! ✗

The Reality: Exact approach would take longer than the universe has existed!

The “Cost” of Complexity

Why buying a faster computer won’t help:

  • P: Tasks that can be solved in polynomial time
    • Like sorting a spreadsheet or calculating payroll
    • These are safe, predictable, and easy to automate
  • NP: Easy to check, hard to solve
    • Analogy: easy to check if a specific password works
    • Very hard to guess a password by trying every combination!

TSP optimization where we find minimum cost tour → NP-Hard. This means no known algorithm can find the perfect solution quickly for large problems.

Graph Theory

What is a Graph?

A graph \(G = (V, E)\) consists of:

  • Vertices (V): The nodes or points (bakery + cafés)
  • Edges (E): The connections between vertices (roads)
  • Weight Function: \(w\) assigns costs to edges (distances)

For our bakery problem:

  • \(|V| = 17\) (1 bakery + 16 cafés)
  • \(|E| = \binom{17}{2} = 136\) possible connections
  • Each edge \((i,j)\) has weight \(w_{ij}\) = distance between \(i\) and \(j\)

Complete vs. Sparse Graphs

Different graph structures lead to different complexities:

  • Complete Graph: All vertices connected to each other
    • TSP: \((n-1)!/2\) unique tours
    • Real roads: Usually complete (drive between any two points)
  • Sparse Graph: Limited connections between vertices
    • Fewer edges = fewer possible routes
    • Examples: Public transit networks, restricted road access

Density dramatically affects both problem difficulty and solution approaches!

Hamiltonian Cycles and Tours

Mathematical foundation of the TSP:

  • Hamiltonian Path: Visits each vertex exactly once
  • Hamiltonian Cycle: Hamiltonian path that returns to start vertex
  • TSP Tour: Minimum weight Hamiltonian cycle

Mathematical Definition: A tour \(T = (v_1, v_2, ..., v_n, v_1)\) where:

  • Each \(v_i \in V\) appears exactly once (except start/end)
  • Total weight: \(w(T) = \sum_{i=1}^{n} w_{v_i, v_{i+1}}\) (where \(v_{n+1} = v_1\))

Goal: Find tour \(T^*\) such that \(w(T^*) = \min_{T \in \mathcal{H}} w(T)\)

Distance Functions and Metrics

The weight function can have different properties:

  1. Identity: \(d(i,i) = 0\) \(i \in V\)
  2. Symmetry: \(d(i,j) = d(j,i)\) \(i,j \in V\)
  3. Triangle Inequality: \(d(i,k) \leq d(i,j) + d(j,k) \quad \forall i,j,k \in V\)

Common Distance Functions

  • Euclidean: \(d(i,j) = \sqrt{(x_i - x_j)^2 + (y_i - y_j)^2}\)
  • Manhattan: \(d(i,j) = |x_i - x_j| + |y_i - y_j|\)
  • Real road distances: Often violate triangle inequality!

Greedy Construction

A Bad Start: Random Route

What happens if we pick cafés randomly?

The bakery B and eight cafés C1 to C8 connected in random order: the route zigzags back and forth across the map and crosses itself several times, which makes it much longer than necessary.

Random selection creates routes with many crossings and inefficiencies.

Nearest Neighbor: The Problem

Given these 8 cafés, which should we visit first?

The bakery B and the eight cafés C1 to C8 as labelled points, without a route. Small numbers give each café's straight-line distance from the bakery: C3 and C2, just below and to the right of the bakery, are the closest; C7 on the far right is the farthest.

Question: Using nearest neighbor, which café would you visit first?

Nearest Neighbor: The Algorithm

Build a route by always visiting the closest unvisited location.

  1. Start at the bakery
  2. Find the nearest unvisited café
  3. Go there
  4. Repeat until all visited
  5. Return to bakery

Intuition: Like picking low-hanging fruit - grab what’s easiest (nearest) first!

Nearest Neighbor: The Solution

Let’s see how nearest neighbor builds the route step by step:

Eight small panels show nearest neighbour adding one café per step, always the closest unvisited one, in the order 3, 2, 6, 4, 8, 1, 5, 7. The tour ends at café 7 on the far right, so the last step draws a long return leg back to the bakery that cuts across the route.

Nearest neighbor starts with short hops, but ends far out at café 7 and needs a long return leg!

Farthest Insertion: The Problem

Same 8 cafés - but now we’ll use a different strategy:

The same map again: the bakery B and the cafés C1 to C8 as labelled points, with no route and no distances shown. C7 on the far right is the café farthest from the bakery.

The farthest point is our start!

Farthest Insertion: The Algorithm

Build a route by starting with distant points and filling in gaps:

  1. Start at the bakery
  2. Find the farthest café from bakery - add it to tour
  3. Create initial tour: Bakery → Farthest → Bakery
  4. Repeat: Find the café farthest from current tour
  5. Insert it at the position that minimizes tour increase
  6. Continue until all cafés are in the tour

Intuition: Build the “skeleton” of the route first with distant points, then fill in the gaps.

Farthest Insertion: Step-by-Step

Eight small panels show farthest insertion step by step: it starts with a there-and-back trip to café 7, the farthest from the bakery, then inserts cafés 4, 6, 5, 2, 8, 3 and 1 one at a time where they lengthen the tour least. The outer shape comes first and the nearer cafés fill it in, ending in one loop without a long return leg.

Notice how farthest insertion builds a more balanced tour by establishing the outer structure first, then filling in the gaps!

Comparison

Let’s compare all three construction methods:

  • Random: Usually worst - no strategy at all!
  • Nearest Neighbor: Fast and decent, but can create long returns
  • Farthest: Often best initial solution, builds good “skeleton”

The better your starting point, the better your final result after local search!

This is also true for all other problems we are solving! A good initial heuristic to create a solution will help us later.

The Problem with Greedy

Often obvious inefficiencies in the resulting routes

  • Crossing paths: Route crosses over itself
  • Long return: Far from bakery at the end
  • Myopic decisions: Can’t see the “big picture”

Can we improve our greedy solutions?

Solution Space: An Intuitive View

Think of the solution space as a landscape:

  • Each point: A different route through the cafés
  • Height: The total distance of that route (lower is better)
  • Neighbors: Routes that differ by small change
  • Local optimum: Best route among nearby alternatives
  • Global optimum: The absolute best route overall

Search Strategy

How can we search this space?

  1. Start somewhere (greedy construction)
  2. Look around at neighboring solutions
  3. Move to better neighbors
  4. Stop when no neighbor is better

Local search transforms “quick and dirty” solutions into “pretty good” ones!

Local Improvements

The 2-Opt Algorithm

Systematically improve routes by removing crossing paths.

The Idea: Take two edges and swap them (to uncross the route)

Two panels with the same four points. Before: edges A-B and C-D cross each other in an X. After the swap: edges A-C and B-D no longer cross, and their combined length drops from about 11.3 to 8.

Example: Step-by-Step

Let’s see exactly how 2-opt fixes a crossing in a real route:

Three panels on one small route. 1: the tour B, C1, C3, C5, C2, C4, B crosses itself twice; edges C1-C3 and C2-C4 are marked for removal. 2: these two edges are replaced by C1-C2 and C3-C4, which reverses the segment in between. 3: the new tour B, C1, C2, C5, C3, C4, B is shorter and has only one crossing left.

The Key Insight: When you reverse a segment between two crossing edges, you automatically eliminate the crossing and create a shorter route!

How 2-Opt Works

improved = True
while improved:
    improved = False
    best_distance = calculate_route_distance(route, distances)
    for i in range(len(route) - 1):
        for j in range(i + 2, len(route)):
            new_route = route[:i+1] + route[i+1:j+1][::-1] + route[j+1:]
            new_distance = calculate_route_distance(new_route, distances)
            if new_distance < best_distance:
                route = new_route
                best_distance = new_distance
                improved = True
                break
        if improved:
            break

The [::-1] reverses the segment, eliminating crossings!

2-Opt Applied

Let’s see how this changes the route!

Left: the nearest neighbour tour, whose long return leg from café 7 back to the bakery cuts across the rest of the route. Right: after 2-opt the same eight cafés form a single loop without crossings, and the tour is shorter.

Notice how 2-opt removed the crossing paths from our nearest neighbor solution, creating a more efficient route!

Common 2-Opt Bugs

Debug these scenarios you’ll encounter:

Bug 1: Infinite Loop

# WRONG: Forgot to update route
if new_distance < current_distance:
    improved = True  # But route never changes!

Bug 2: Missing Return to Start

# WRONG: Only measures cafe-to-cafe
total = sum(distances between stops)

Bug 3: Invalid Segment Reversal

# WRONG: Off-by-one error
new_route = route[:i] + route[i:j][::-1] + route[j:]

Fix: route[:i+1] + route[i+1:j+1][::-1] + route[j+1:]

What About 1-Opt?

Left: a square tour A, B, C, D and back to A, with edge B-C marked for removal. Right: without that edge the tour is an open path with a gap between B and C. The only way to close it is to put the same edge back, so removing a single edge cannot change a tour.

Where 1-opt DOES work: In problems like knapsack (swap 1 item), assignment (reassign 1 person), or facility location (relocate 1 facility).

Or-Opt: Moving Sequences

Moves sequence of 1-3 consecutive cities to different position.

Three panels on a tour of the bakery and seven cafés that lie on a ring. 1: the tour B, C1, C2, C3, C4, C5, C6, C7, B crosses itself three times because it visits C2 and C3 at the top too early; these two consecutive stops are marked as the sequence to move. 2: an arrow shows the pair being taken out and inserted between C6 and C7 instead. 3: the new route B, C1, C4, C5, C6, C2, C3, C7, B is a ring without crossings, in which the pair keeps its order and three new edges reconnect the tour.

Moving a sequence can satisfy time constraints without breaking tour structure!

Beyond: k-Opt Neighborhoods

The k-opt family of improvements:

2-opt

  • Removes 2 edges
  • 1 way to reconnect
  • n² combinations
  • Fast, good results

3-opt

  • Removes 3 edges
  • 7 ways to reconnect
  • n³ combinations
  • Better but slower

Or-opt

  • Moves 1-3 nodes
  • Good for time windows
  • Specialized variant

Start with 2-opt (fast), use 3-opt if you have time! As k increases, solutions improve but computation time grows exponentially.

Local Optima

Convergence and Local Optima

When does local search stop? Why might it get stuck?

Convergence:

  • Algorithm stops when no neighboring solution is better

The Local Optimum Problem:

  • Algorithm can only “see” neighboring solutions
  • Might miss better solutions that require multiple changes
  • Like being stuck on a small hill when there’s a mountain nearby

Local search for improvement but does not guarantee global optimality.

The Local Optimum Trap

Imagine you’re a hiker dropped in foggy mountains at night…

  • Your Mission: Find the highest peak (global optimum)
  • Your Tool: An altimeter (objective function)
  • Your Vision: Only the ground at your feet (local neighborhood)
  • The Greedy Strategy: Always step uphill

Question: What happens when you reach the top of a small hill?

You’re stuck! Every step is downhill, but you might be on a tiny hill while a much larger moutain is nearby. This is the local optimum trap!

Visualizing Local Optima

A curve of total distance over the solution space with two valleys separated by a high ridge. The left valley is the local minimum, labelled 'Stuck here!'; the right valley is only slightly deeper and is the global minimum, labelled 'Want here!'. A search that only moves downhill cannot leave the left valley.

Here, the local minimum is already quite good, but we likely won’t reach the global optimum from here.

The Reality

Real problems often have thousands of local optima!

A jagged curve of total distance over the solution space with many peaks and valleys. Four marked local minima sit at very different heights, all well above the single deep global minimum near the right end. Shaded bands grade solutions from excellent at the bottom to poor at the top.

The probability of finding the global optimum with simple local search is nearly zero!

Escaping Local Optima

Depending on the problem: Multi-Start Strategy!

  • Most local minima are much worse than the global optimum
  • The global minimum is sometimes isolated and hard to reach
  • Starting point dramatically affects final solution quality
  • Thus, start with different random solutions
  • Use different initial heuristics

No Free Lunch Theorem: There’s no universal “best” algorithm for all problems. What works great for routing might fail for scheduling. Always match your tool to your problem!

How Good is Good Enough?

Industry usage for delivery optimization

Method Industry Use
Human intuition Still common!
Start + 2-opt Common practice
Advanced Meta Sometimes practice
Exact (if possible) Mostly research

A 10% improvement = millions in savings for large logistics companies. Even a 2-opt implementation could literally save a lot of money if not used yet!

Time Constraints

Time Windows

Remember our bakery? Some cafés open earlier than others!

Artisan Bakery’s Morning Schedule:

  • Bakery opens: 5:00 (van departs)
  • Early Birds (3 cafés): Must receive by 6:30
    • Café Europa, Sunrise Bistro, Morning Glory
  • Standard (13 cafés): Must receive by 8:00

Question: Can we just find the shortest route?

The shortest route might deliver to early cafés last. Feasibility first, optimization second!

Time Windows: Practical Approach

Each location has a delivery time window:

Key Concepts:

  • Earliest time: When café opens
  • Latest time: Delivery deadline
  • Service time: Time to unload

Arrival Time = Previous departure + Travel time

  • Feasible route: All deadlines met
  • Infeasible route: At least one deadline missed (even if shortest!)

Time Windows: NN Modification

Modify greedy construction to prioritize early deadlines:

unvisited = set(range(len(locations))); route = []; current_time = start_time
while unvisited:
    # Find feasible neighbors (can reach before deadline)
    feasible = [i for i in unvisited
                if current_time + travel_time(current, i)
                    <= time_windows[i]['latest']]
    if not feasible:
        return None  # No feasible route exists!
    # Among feasible, choose most urgent
    next_stop = min(feasible, key=lambda i: (time_windows[i]['latest']))
    # Update state
    route.append(next_stop)
    unvisited.remove(next_stop)
    current_time += travel_time(current, next_stop) + service_time

2-Opt with Time Windows

Problem: 2-opt can break time feasibility!

Arrival times per stop with dashed deadline marks. Original route: Early1 at 5.5 h and Early2 at 6.2 h arrive before their 6.5 h deadline, both late cafés before 8.0 h. After the 2-opt swap Early2 is reached only at 7.2 h, after its deadline, and is marked with a red cross.

Early2 now arrives at 7:12 AM. Missed its 6:30 deadline by 42 minutes!

The Solution

Only accept swaps that maintain feasibility!

improved = True
while improved:
    improved = False
    for i in range(len(route) - 1):
        for j in range(i + 2, len(route)):
            new_route = route[:i+1] + route[i+1:j+1][::-1] + route[j+1:]
            # Check feasibility FIRST
            if not is_feasible(new_route, time_windows, start_time):
                continue  # Skip infeasible swaps
            # Among feasible swaps, take if shorter
            if calculate_distance(new_route) < calculate_distance(route):
                route = new_route
                improved = True
                break
        if improved:
            break

Feasibility is a hard constraint, distance is the objective.

Briefing

Choosing Your Algorithm

Different situations call for different approaches:

Situation Best Approach Why
Solution now Nearest Neighbor Lightning fast
Have seconds NN + 2-opt Good balance
Have minutes Multi-start + 2-opt Explore more options
Time windows NN (early) + Or-opt Preserves feasibility
Benchmark 3-opt or meta Best solutions

Competition? Choose whatever you are comfortable with.

Implementation Pitfalls to Avoid

Common bugs that cost you time.

Forgetting return to bakery:

# WRONG
total = sum(distances between consecutive stops)
# RIGHT
total = sum(distances) + distance(last_stop, bakery)

Index confusion in 2-opt:

# The 2-opt swap reverses route[i+1:j+1], not route[i:j]

Modifying while iterating:

new_route = current_route.copy()  # Don't modify original

Escaping Local Optima

When local search gets stuck, we need clever escapes:

Advanced Techniques Coming:

  • Simulated Annealing: Sometimes accept worse moves
  • Genetic Algorithms: Combine good routes to make better ones
  • Tabu Search: Remember where you’ve been to avoid circles

Today’s local search foundation makes advanced methods possible!

Summary

Key Takeaways:

  • TSP is computationally hard (factorial growth)
  • Local search is a universal framework (4 pillars)
  • Greedy construction gives fast initial solutions
  • 2-opt improves solutions iteratively
  • Multi-start helps escape local optima
  • Real constraints (time windows) add complexity
  • Two-phase approach: Build then improve!

Break!

Take 20 minutes, then we start the practice notebook

Next up: You’ll become Bean Counter’s route planner

Then: The Bakery competition