[Rod Stephens Books]
Index Books Python Examples About Rod Contact
[Mastodon] [Bluesky]
[Build Your Own Ray Tracer With Python]

[Beginning Database Design Solutions, Second Edition]

[Beginning Software Engineering, Second Edition]

[Essential Algorithms, Second Edition]

[The Modern C# Challenge]

[WPF 3d, Three-Dimensional Graphics with WPF and C#]

[The C# Helper Top 100]

[Interview Puzzles Dissected]

[Book: Essential Algorithms] This example uses an algorithm to find a number's prime factors. For other interesting algorithms, see my book Essential Algorithms, Second Edition. The book describes many fascinating algorithms in pseudocode with source code available for download in Python and C#.

Title: Improve branch and bound to solve the traveling salesman problem in Python

[A solution to the traveling salesman problem found by improved branch and bound] The post Use branch and bound to solve the traveling salesman problem in Python explains how to use branch and bound to solve the traveling salesman problem (TSP). The idea is to keep track of the length of the best tour found so far. When you add a new point to the test tour, you calculate the minimum length the tour could possibly have. If that minimum is greater than the best length found so far, you can ignore the current partial test tour and all of the tours that start with it.

To estimate the shortest possible length for the test tour, the program's best_possible_length function adds up the lengths of the shortest distances to the points that are not yet in the tour.

That works but we can eliminate more searching if we can get a more accurate estimate for the test tour's shortest possible length. We can do that if we think a bit more about how points are added to the test tour.

First, we only add new points at the end of the test tour, so a point A that is still unused will be reached either (1) from another unused point B or (2) from the last point in the current test tour. The previous version of best_possible_length looks at the shortest distance to point A. In the first change, we only consider (1) distances from other unused points to point A and (2) the distance from the last point in the current tour to point A.

We can make a second change if we remember that a complete TSP tour must visit every point and then return to the starting point. The previous version of branch and bound adds up the shortest distances to the new points but doesn't add a distance for the return to the starting point. We don't know exactly how far it will be because we don't know which point will be last in the tour, but we can use the smallest distance from any unused point to the starting point.

Code

The following code shows a revised best_possible_length function that makes these two changes.

def best_possible_length2(num_points, current_tour, distances, is_used): '''Return the best possible tour length starting from the current tour.''' # If this is a complete tour, return 0 so generate_b_and_b2_tours does it. if len(current_tour) == num_points: return 0 # This will be the shortest distance form # any unused point back to the start. best_return_distance = math.inf # The index of the last point in the current tour. last_point = current_tour[-1] # Loop through the points. total_length = 0 for i in range(num_points): # If point i is unused... if not is_used[i]: # ...find smallest distance into point i. length_to_i = math.inf for j in range(num_points): # Don't count i --> i (which has distance 0). if j == i: continue # Only count it if j is also unused or # j is the last point in the current tour. if j != last_point and is_used[j]: continue # See if this is a shorter distance. if distances[j][i] < length_to_i: length_to_i = distances[j][i] total_length += length_to_i # See if this point gives a shorter return. if distances[i][0] < best_return_distance: best_return_distance = distances[i][0] return total_length + best_return_distance

Summary

You can improve branch and bound's performance by using more accurate bounds. This example does that by considering a subset of the links leading to unused points and by including a distance from the tour's possible last point to its starting point.

So how much difference does it make? The following table shows the number of function calls and the times used by exhaustive search, the original version of branch and bound, and the new version.

# Points Exhaustive
Calls
B & B
Calls
B & B2
Calls
Exhaustive
Time (sec)
B & B
Time (sec)
B & B2
Time (sec)
8 13,700 1,155 187 0.14 0.01 0.01
9 109,601 3,298 392 1.21 0.02 0.02
10 986,410 12,379 16,594 1,269 0.05 0.01
11 9,864,101 108,224 9,161 159.32 0.73 0.10
12 108,505,112 104,687 5,465 2,103.36 0.87 0.09
13 --- 606,335 13,285 --- 5.51 0.25
14 --- 666,382 31,001 --- 10.36 0.83
15 --- 5,828,115 260,118 --- 116.29 6.89
16 --- --- 254,747 --- --- 13.12
17 --- --- 336,387 --- --- 23.55
18 --- --- 387,315 --- --- 32.55
19 --- --- 1,512,113 --- --- 178.65
20 --- --- 494,913 --- --- 124.18

The improved version of branch and bound is always faster than the first version, which in turn is always faster than exhaustive search. The exact required by both versions of branch and bound vary depending on the points and their arrangement so they aren't consistent. You can see a particularly obvious example in the tests that used 19 and 20 points.

Download the example and see the two previous posts to experiment and to see additional details.

© 2024 Rocky Mountain Computer Consulting, Inc. All rights reserved.