-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Expand file tree
/
Copy pathTravelingSalesmanSolver.cs
More file actions
135 lines (119 loc) · 4.63 KB
/
TravelingSalesmanSolver.cs
File metadata and controls
135 lines (119 loc) · 4.63 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
namespace Algorithms.Problems.TravelingSalesman;
/// <summary>
/// Provides methods to solve the Traveling Salesman Problem (TSP) using brute-force and nearest neighbor heuristics.
/// The TSP is a classic optimization problem in which a salesman must visit each city exactly once and return to the starting city, minimizing the total travel distance.
/// </summary>
public static class TravelingSalesmanSolver
{
/// <summary>
/// Solves the TSP using brute-force search. This method checks all possible permutations of cities to find the shortest possible route.
/// WARNING: This approach is only feasible for small numbers of cities due to factorial time complexity.
/// </summary>
/// <param name="distanceMatrix">A square matrix where element [i, j] represents the distance from city i to city j.</param>
/// <returns>A tuple containing the minimal route (as an array of city indices) and the minimal total distance.</returns>
public static (int[] Route, double Distance) SolveBruteForce(double[,] distanceMatrix)
{
int n = distanceMatrix.GetLength(0);
if (n != distanceMatrix.GetLength(1))
{
throw new ArgumentException("Distance matrix must be square.");
}
if (n < 2)
{
throw new ArgumentException("At least two cities are required.");
}
var cities = Enumerable.Range(0, n).ToArray();
double minDistance = double.MaxValue;
int[]? bestRoute = null;
foreach (var perm in Permute(cities.Skip(1).ToArray()))
{
var route = new int[n + 1];
route[0] = 0;
for (int i = 0; i < perm.Length; i++)
{
route[i + 1] = perm[i];
}
// Ensure route ends at city 0
route[n] = 0;
double dist = 0;
for (int i = 0; i < n; i++)
{
dist += distanceMatrix[route[i], route[i + 1]];
}
if (dist < minDistance)
{
minDistance = dist;
bestRoute = (int[])route.Clone();
}
}
return (bestRoute ?? Array.Empty<int>(), minDistance);
}
/// <summary>
/// Solves the TSP using the nearest neighbor heuristic. This method builds a route by always visiting the nearest unvisited city next.
/// This approach is much faster but may not find the optimal solution.
/// </summary>
/// <param name="distanceMatrix">A square matrix where element [i, j] represents the distance from city i to city j.</param>
/// <param name="start">The starting city index.</param>
/// <returns>A tuple containing the route (as an array of city indices) and the total distance.</returns>
public static (int[] Route, double Distance) SolveNearestNeighbor(double[,] distanceMatrix, int start = 0)
{
int n = distanceMatrix.GetLength(0);
if (n != distanceMatrix.GetLength(1))
{
throw new ArgumentException("Distance matrix must be square.");
}
if (start < 0 || start >= n)
{
throw new ArgumentOutOfRangeException(nameof(start));
}
var visited = new bool[n];
List<int> route = [start];
visited[start] = true;
double totalDistance = 0;
int current = start;
for (int step = 1; step < n; step++)
{
double minDist = double.MaxValue;
int next = -1;
for (int j = 0; j < n; j++)
{
if (!visited[j] && distanceMatrix[current, j] < minDist)
{
minDist = distanceMatrix[current, j];
next = j;
}
}
if (next == -1)
{
throw new InvalidOperationException("No unvisited cities remain.");
}
route.Add(next);
visited[next] = true;
totalDistance += minDist;
current = next;
}
totalDistance += distanceMatrix[current, start];
route.Add(start);
return (route.ToArray(), totalDistance);
}
/// <summary>
/// Generates all permutations of the input array.
/// Used for brute-force TSP solution.
/// </summary>
private static IEnumerable<int[]> Permute(int[] arr)
{
if (arr.Length == 1)
{
yield return arr;
yield break;
}
for (int i = 0; i < arr.Length; i++)
{
var rest = arr.Where((_, idx) => idx != i).ToArray();
foreach (var perm in Permute(rest))
{
yield return [arr[i], ..perm];
}
}
}
}