How to choose the shortest route? (Vehicle routing problem)

This question is about a small instance of the classical vehicle routing problem (VRP).

Someone needs to start from home and complete the three tasks of going to the post office to send a letter, going to the bookstore to buy books, and going to the supermarket to buy food, and finally return home. He can walk through some nodes repeatedly. How should he choose the route to make the path the shortest?

Graph[{Home \[UndirectedEdge] School,
Home \[UndirectedEdge] Supermarket,
Home \[UndirectedEdge] PostOffice,
PostOffice \[UndirectedEdge] Home,
PostOffice \[UndirectedEdge] Bookstore,
PostOffice \[UndirectedEdge] Supermarket,
Bookstore \[UndirectedEdge] PostOffice,
Bookstore \[UndirectedEdge] Supermarket,
Supermarket \[UndirectedEdge] Bookstore,
Supermarket \[UndirectedEdge] PostOffice,
Supermarket \[UndirectedEdge] Home,
Supermarket \[UndirectedEdge] School,
School \[UndirectedEdge] Supermarket,
School \[UndirectedEdge] Home},
EdgeWeight -> {410, 510, 218, 218, 75, 329, 75, 440, 440, 329, 510,
125, 125, 410}, VertexLabels -> "Name",
VertexCoordinates -> {Home -> {0, 0}, School -> {1, 0},
PostOffice -> {0.2, 1}, Supermarket -> {1.2, 0.8},
Bookstore -> {0.4, 1.7}}]


If possible, I hope the respondents can provide as many methods as possible to solve this problem, such as neural network algorithm, genetic algorithm, or built-in function solution, etc.

• FindShortestTour[g] ? You'll need to delete the duplicate edges and corresponding weights to get it to work, for example you have both Home \[UndirectedEdge] School and School \[UndirectedEdge] Home but you only need one edge. Commented Jul 25, 2020 at 1:22
• A quick shortcut for the above is this FindShortestTour[SimpleGraph[g]] which will prune the extra edges and find the shortest tour. {2536, {Home, School, Supermarket, Bookstore, PostOffice, Home}} If you don't care about the school in the question, just remove it from the graph FindShortestTour[VertexDelete[SimpleGraph[g], School]] gives {2486, {Home, PostOffice, Bookstore, Supermarket, Home}} This is okay, because even though the School provides access on the home-supermarket route, it's cost is higher (410+125) than the direct route (510) Commented Jul 25, 2020 at 1:31
• ^that said , I assumed visiting each vertex only once, but a much shorter route exists if you can visit a vertex more than once: home->po->bookstore->po->supermarket->home (218 + 75 + 75 + 329 + 510) 1207. There are no easy built in methods to find paths that visit specific vertices at least once that I'm aware of, though you could possibly find a postman tour of the dual graph... Commented Jul 25, 2020 at 1:44
• @flinty Thank you very much. I allowed that someone to walk through some nodes repeatedly, but I didn't know how to do it. Commented Jul 25, 2020 at 1:51
• There's some discussion here and here I think you could brute force it with random greedy searches - starting from home, find the shortest path to a random shuffle of (bookstore,post-office,supermarket), store all the nodes visited and walk to that node. Then from that node, find the shortest path to the first remaining node and move there, storing visited nodes as you go, and recurse until no more nodes to visit left and find the shortest path home. Commented Jul 25, 2020 at 2:21

In this code below I'm doing a repeated greedy search. This gives a much better route than FindShortestTour which does not allow re-visited nodes.

We start by randomly permuting our list of targets. We then get the first remaining target and find the shortest path from our current node to the target, recording the path as we go. Any other targets we happen to encounter on the way there are removed from the list of targets. We repeat this procedure until no more targets are left, then we walk back home.

The whole thing is repeated starting from scratch for every different permutation of the target list. This should ensure we aren't biasing the greedy search to visit the targets in a particular order.

One thing to note, I've adjusted the graph so that it's simple:

g = Graph[{Home \[UndirectedEdge] PostOffice,
Home \[UndirectedEdge] Supermarket, Home \[UndirectedEdge] School,
PostOffice \[UndirectedEdge] Supermarket,
PostOffice \[UndirectedEdge] Bookstore,
Bookstore \[UndirectedEdge] Supermarket,
Supermarket \[UndirectedEdge] School},
EdgeWeight -> {218, 510, 410, 329, 75, 440, 125},
VertexLabels -> "Name", EdgeLabels -> "EdgeWeight",
VertexCoordinates -> {Home -> {0, 0}, School -> {1, 0},
PostOffice -> {0.2, 1}, Supermarket -> {1.2, 0.8},
Bookstore -> {0.4, 1.7}}, PlotTheme -> "Scientific"]


route[g_, targetvtxs_, currentvtx_, paths_] :=
Module[{sp = Rest[FindShortestPath[g, currentvtx, First[targetvtxs]]], newtgtvtxs},
newtgtvtxs = Complement[targetvtxs, sp];
If[newtgtvtxs != {},
sp = Join[sp, route[g, newtgtvtxs, Last[sp], Join[paths, sp]]]];
Return[sp];
]

pathToEdges[path_] := UndirectedEdge @@@ Partition[path, 2, 1]

edgeLength[g_, edge_] := AnnotationValue[{g, edge}, EdgeWeight]

generateRoute[g_, targets_] :=
Module[{rt = Prepend[route[g, targets, Home, {}], Home]},
(* go back home if required *)
If[Last[rt] =!= Home,
rt = Join[rt, Rest[FindShortestPath[g, Last[rt], Home]]]
];
(* return the cost of the route and the route *)
Return[{Total[edgeLength[g, #] & /@ pathToEdges[rt]], rt}]
]

(* find the routes *)
DeleteDuplicates[
generateRoute[g, #] & /@ Permutations[{PostOffice, Bookstore, Supermarket}]
]
(* results:
{{1207, {Home, PostOffice, Bookstore, PostOffice, Supermarket, Home}},
{1207, {Home, Supermarket, PostOffice, Bookstore, PostOffice, Home}}}
*)


Notice that there are two short routes of identical length that it found.

In a larger more complex graph, the problem becomes more intractable. Therefore it would make sense to only try a relatively small number of the target permutations, perhaps using RandomSample, to find a good result.

As I mentioned in the comments, this problem seems to be related to the sparse Travelling Salesman Problem with revisits and while it's not the most common version of the problem which normally has a dense complete graph, it's definitely the most interesting to me.

• Thx, for large-scale problems, we can refer to the code in this repository(from the topic of Huawei 2016 challenge). Commented Jul 26, 2020 at 0:17
• The problem remains quite solvable for large graphs as long as the number of stores to visit is low. This is because the shortest path problem can be solved efficiently.
– A.G.
Commented Jul 27, 2020 at 8:50
• ^ however, this is a greedy search. Imagine a large graph which contains a 'hub' vertex that can reach all the stores efficiently but the hub itself is quite difficult to reach. It may be more efficient overall to take the long route to the hub, visit all stores from this hub, then return home, than it is to follow shortest routes that avoid the hub node. But then it's also possible the greedy approach is efficient - it's hard to tell. Commented Jul 27, 2020 at 11:50

With such a low number of places ($$n=3$$) to visit the number of orderings ($$n!=6$$) is low enough for an exhaustive search. (given distances that are the same in both directions, the number is actually $$n!/2=3$$).

Of course the possible orderings of the stores can be generated by Mathematica:

Permutations[{Bookstore, PostOffice, Supermarket}]


$$\longrightarrow$$

{{Bookstore, PostOffice, Supermarket}, {Bookstore, Supermarket,
PostOffice}, {PostOffice, Bookstore, Supermarket}, {PostOffice,
Supermarket, Bookstore}, {Supermarket, Bookstore,
PostOffice}, {Supermarket, PostOffice, Bookstore}}


In the present case one can remove routes that are reverse of one another because distances are the same going from A to B or B to A:

DeleteDuplicates[Permutations[{Bookstore, PostOffice, Supermarket}],
#1 == Reverse[#2] &]


$$\longrightarrow$$

{{Bookstore, PostOffice, Supermarket}, {Bookstore, Supermarket,
PostOffice}, {PostOffice, Bookstore, Supermarket}}


Given an ordering of places to visit, just use shortest paths from one to the next. I will assume the guy returns home:

length[a_, b_, c_] :=
GraphDistance[g, Home, a] + GraphDistance[g, a, b] +
GraphDistance[g, b, c]    + GraphDistance[g, c, Home];
length1 = length[Bookstore, PostOffice, Supermarket]
length2 = length[Bookstore, Supermarket, PostOffice]
length3 = length[PostOffice, Bookstore, Supermarket]


output is 1207, 1244, 1207. Just pick any order with lowest total, say the first. The route is:

FindShortestPath[g, Home, Bookstore]
FindShortestPath[g, Bookstore, PostOffice]
FindShortestPath[g, PostOffice, Supermarket]
FindShortestPath[g, Supermarket, Home]


With output

{Home, PostOffice, Bookstore}
{Bookstore, PostOffice}
{PostOffice, Supermarket}
{Supermarket, Home}


So one optimal tour is:

Home, PostOffice, Bookstore, PostOffice, Supermarket, Home.

This approach will work fine as long as $$n!/2$$ is not too high, but the graph itself can be quite large as finding shortest paths is usually quite efficient computationally.

There are several problems if to attempt to solve this question with the ordinary methods like FindShortestTour.

Mathematica traditionally annoys first time users with the travelling salesman alike problems. Many authors have therefore published in their introductory books handwritten and so specially adopted and adaptable routine to solve this for their handsome readers. But this is a hard problem for professional measures.

So in literature, it is unusual to use the Mathematica built-in graph data structure and even the edge list and the edge list are seldom. The reason is evident. It is like in this question possible to deviate some the so intrinsic euclidian measure of the real world. That is already indicted in the copied picture the problem is not treatable with a metric like Manhattan distance and else.

The situation looks like this

The problem might be solved in manners like that in house-of-santa-claus. That problem has not weights and the solution is so general in the node, knots it can not be transfered to this question directly.

listpoints = {{0, 0}, {177.9189, 125.972}, {410, 0}, {503.140,
83.366}}


Since this is mathematics there is a trick!

I suggest the code from Comparing Algorithms For The Traveling Salesman Problem. There is the need for the exact position for the last node, knot.

And there comes somehow the flaw of the design of the question into range. Only the 75 weight is in need to be satisfied. The 440 can be shortened to fit.

So another path is divide into loops and solve for the loops.

Sort[410, 510, 218, 218, 75, 329, 75, 440, 440, 329, 510, 125, 125, 410]

{75,125,218,329,440,510}

Anneal the weights and discard 440 and 510. Travel along the other egdes covers the graph. This is the main drawback of all Mathematica built-ins for such questions. They cover the graph and use all edges and all knots. That is not needed in general for traveling salesman problems.

Home -> Postoffice -> Bookshop -> Postoffice -> Supermarket -> Home is the minimal length in weight path. No other covers the reach of all nodes/knots.

The last step needs extra effort because the edge Supermarket -> Home can not be considered for the annealing with Mathematica built-ins

gred = Graph[{1 \[UndirectedEdge] 2, 1 \[UndirectedEdge] 3,
3 \[UndirectedEdge] 2, 3 \[UndirectedEdge] 4,
4 \[UndirectedEdge] 2}, EdgeWeight -> {218, 510, 329, 440, 75},
VertexLabels -> "Name", EdgeLabels -> "EdgeWeight",
VertexCoordinates -> {1 -> {0, 0}, 2 -> {0.2, 1}, 3 -> {1.2, 0.8},
4 -> {0.4, 1.7}}]

FindPostmanTour[gred] // First


{1 [UndirectedEdge] 3, 3 [UndirectedEdge] 4, 4 [UndirectedEdge] 2, 2 [UndirectedEdge] 3, 3 [UndirectedEdge] 2, 2 [UndirectedEdge] 1}

Seems Mathematica uses the loop partioning first and than adds up.

gred = Graph[{1 \[UndirectedEdge] 2, 3 \[UndirectedEdge] 2,
4 \[UndirectedEdge] 2}, EdgeWeight -> {218, 329, 75},
VertexLabels -> "Name", EdgeLabels -> "EdgeWeight",
VertexCoordinates -> {1 -> {0, 0}, 2 -> {0.2, 1}, 3 -> {1.2, 0.8},
4 -> {0.4, 1.7}}]


FindPostmanTour[gred] // First

{1 \[UndirectedEdge] 2, 2 \[UndirectedEdge] 4, 4 \[UndirectedEdge] 2,
2 \[UndirectedEdge] 3, 3 \[UndirectedEdge] 2, 2 \[UndirectedEdge] 1}


Replace the two edges then:

{1 \[UndirectedEdge] 2, 2 \[UndirectedEdge] 4, 4 \[UndirectedEdge] 2,
2 \[UndirectedEdge] 3, 3 \[UndirectedEdge] 1}


Chance has to replace the undirected egdes in this one case into a directed one from 3 to 1 first and than use Mathematica built-ins or solutions from other authors.

There is a difference between tsp with little nodes/knots and larger ones. They are usually treated different. For small numbers of knots it is cheaper to think first and than use Mathematica built-ins than other way round.

The detour, longer path than in real-world present makes this problem special. The question of whether all edges and nodes shall be used is intrinsic to Mathematical built-ins. It saves a lot of programming to anneal first. Most tsp problems allow ordering and annealing. That is the comfortable perspective of such kind of problems.

• Can you gives in the 2nd paragraph in German ? Please do it in the comments. Thanks Commented Jul 29, 2020 at 11:03