Skip to content

Commit ca8067a

Browse files
Heuristic solver for tsp
1 parent 5109cca commit ca8067a

File tree

3 files changed

+204
-0
lines changed

3 files changed

+204
-0
lines changed
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import unittest
2+
import sys
3+
4+
# Import from different folder
5+
sys.path.append("Algorithms/graphtheory/nearest-neighbor-tsp/")
6+
7+
import NearestNeighborTSP
8+
9+
10+
class TestNN(unittest.TestCase):
11+
def setUp(self):
12+
self.G1 = [[0,3,-1],[3,0,1],[-1,1,0]]
13+
self.correct_path1 = [0,1,2,0]
14+
15+
# No possible solution for this one so its a dead end
16+
self.G2 = [[0, 2, -1,-1,-1], [2, 0,5,1,-1], [-1, 5, 0, -1, -1],[-1, 1, -1, 0, 3], [-1, -1, -1, 3, 0]]
17+
self.correct_path2 = [0,1,3,4]
18+
19+
# No possible solution for this one so its a dead end
20+
self.G3 = [[0, 2, -1,-1,-1], [2, 0,5,1,-1], [-1, 5, 0, -1, -1],[-1, 1, -1, 0, -1], [-1, -1, -1, -1, 0]]
21+
self.correct_path3 = [0, 1, 3]
22+
23+
# Multiple possible solutions
24+
self.G4 = [[0,1,1,1],[1,0,1,1],[1,1,0,1],[1,1,1,0]]
25+
self.correct_path4 = [0, 1, 2, 3, 0]
26+
27+
28+
# adjacency matrix of a graph for testing
29+
adjMatrix = [[0,2,5,-1,3],[2,0,2,4,-1],[5,2,0,5,5],[-1,4,5,0,2],[3,-1,5,2,0]]
30+
# correct rank of each node's neighbors
31+
correctNeighbors = [[1,4,2],[0,2,3],[1,0,3,4],[4,1,2],[3,0,2]]
32+
33+
34+
def test_0_rankNeighbors(self):
35+
for i in range(0,4):
36+
self.assertEqual(NearestNeighborTSP.rankNeighbors(i, self.adjMatrix), self.correctNeighbors[i], "Check if order is different.")
37+
38+
39+
def test_1_nnTSP(self):
40+
path=NearestNeighborTSP.nnTSP(self.adjMatrix)
41+
# Test if path is null
42+
self.assertIsNotNone(path,"Output is empty")
43+
# Test if path is not complete
44+
self.assertEqual(len(path),len(self.adjMatrix)+1,"Path in incomplete")
45+
46+
47+
def test_linear_graph(self):
48+
#print(NearestNeighbor.nnTSP(self.G2))
49+
path = NearestNeighborTSP.nnTSP(self.G1)
50+
self.assertEqual(path,self.correct_path1)
51+
52+
53+
def test_simple_graph(self):
54+
path = NearestNeighborTSP.nnTSP(self.G2)
55+
self.assertEqual(path,self.correct_path2)
56+
57+
58+
def test_disconnected_graph(self):
59+
path = NearestNeighborTSP.nnTSP(self.G3)
60+
self.assertEqual(path, self.correct_path3)
61+
62+
63+
def test_complete_graph(self):
64+
path = NearestNeighborTSP.nnTSP(self.G4)
65+
self.assertEqual(path, self.correct_path4)
66+
67+
if __name__ == '__main__':
68+
print("Running Nearest Neighbor TSP solver tests:")
69+
unittest.main()
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
"""
2+
Author: Philip Andreadis
3+
4+
5+
This script implements a simple heuristic solver for the Traveling Salesman Problem.
6+
It is not guaranteed that an optimal solution will be found.
7+
8+
Format of input text file must be as follows:
9+
1st line - number of nodes
10+
each next line is an edge and its respective weight
11+
12+
The program is executed via command line with the graph in the txt format as input.
13+
14+
"""
15+
16+
import time
17+
import sys
18+
19+
20+
21+
"""
22+
This function reads the text file and returns a 2d list which represents
23+
the adjacency matrix of the given graph
24+
"""
25+
def parseGraph(path):
26+
# Read number of vertices and create adjacency matrix
27+
f = open(path, "r")
28+
n = int(f.readline())
29+
adjMatrix = [[-1 for i in range(n)] for j in range(n)]
30+
#Fill adjacency matrix with the correct edges
31+
for line in f:
32+
edge = line.split(" ")
33+
edge = list(map(int, edge))
34+
adjMatrix[edge[0]][edge[1]] = edge[2]
35+
adjMatrix[edge[1]][edge[0]] = edge[2]
36+
for i in range(len(adjMatrix)):
37+
adjMatrix[i][i] = 0
38+
return adjMatrix
39+
40+
41+
42+
"""
43+
Returns all the neighboring nodes of a node sorted based on the distance between them.
44+
"""
45+
def rankNeighbors(node,adj):
46+
sortednn = {}
47+
nList = []
48+
for i in range(len(adj[node])):
49+
if adj[node][i]>0:
50+
sortednn[i] = adj[node][i]
51+
sortednn = {k: v for k, v in sorted(sortednn.items(), key=lambda item: item[1])}
52+
nList = list(sortednn.keys())
53+
return nList
54+
55+
56+
"""
57+
Function implementing the logic of nearest neighbor TSP.
58+
Generate two lists a and b, placing the starting node in list a and the rest in list b.
59+
While b is not empty append to a the closest neighboring node of the last node in a and remove it from b.
60+
Repeat until a full path has been added to a and b is empty.
61+
Returns list a representing the shortest path of the graph.
62+
"""
63+
def nnTSP(adj):
64+
nodes = list(range(0, len(adj)))
65+
#print(nodes)
66+
weight = 0
67+
global length
68+
# Starting node is 0
69+
a = []
70+
a.append(nodes[0])
71+
b = nodes[1:]
72+
while b:
73+
# Take last placed node in a
74+
last = a[-1]
75+
# Find its nearest neighbor
76+
sortedNeighbors = rankNeighbors(last,adj)
77+
# If node being checked has no valid neighbors and the path is not complete a dead end is reached
78+
if (not sortedNeighbors) and len(a)<len(nodes):
79+
print("Dead end!")
80+
return a
81+
flag = True
82+
# Find the neighbor that has not been visited
83+
for n in sortedNeighbors:
84+
if n not in a:
85+
nextNode = n
86+
flag = False
87+
break
88+
# If all neighbors of node have been already visited a dead end has been reached
89+
if flag:
90+
print("Dead end!")
91+
return a
92+
a.append(nextNode)
93+
b.remove(nextNode)
94+
# Add the weight of the edge to the total length of the path
95+
weight = weight + adj[last][nextNode]
96+
# Finishing node must be same as the starting node
97+
weight = weight + adj[a[0]][a[-1]]
98+
length = weight
99+
a.append(a[0])
100+
return a
101+
102+
103+
if __name__ == "__main__":
104+
# Import graph text file
105+
filename = sys.argv[1]
106+
print("~Nearest Neighbor~")
107+
start = time.time()
108+
graph = parseGraph(filename)
109+
n = len(graph)
110+
length = 0
111+
112+
# Print adjacency matrix
113+
print("Adjacency matrix of input graph:")
114+
for i in range(len(graph)):
115+
print(graph[i])
116+
117+
start = time.time()
118+
119+
path = nnTSP(graph)
120+
121+
finish = time.time()-start
122+
123+
# Output
124+
print("Path:",path)
125+
print("Length",length)
126+
print("Time:",finish)
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
5
2+
0 1 2
3+
0 2 5
4+
0 4 3
5+
1 2 2
6+
1 3 4
7+
2 3 5
8+
2 4 5
9+
3 4 2

0 commit comments

Comments
 (0)