Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 39f4da8

Browse files
Merge pull request #35 from philipandreadis/philip-andreadis
Heuristic solver for tsp
2 parents 5109cca + ca8067a commit 39f4da8

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+
e-mail: philip_andreadis@hotmail.com
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)

AltStyle によって変換されたページ (->オリジナル) /