From ca8067abe7715355a34410778a809685aa70e06a Mon Sep 17 00:00:00 2001 From: gilip Date: Fri, 28 Aug 2020 00:43:06 +0300 Subject: [PATCH] Heuristic solver for tsp --- Algorithm_tests/graphtheory_tests/test_NN.py | 69 ++++++++++ .../NearestNeighborTSP.py | 126 ++++++++++++++++++ .../nearest-neighbor-tsp/graph.txt | 9 ++ 3 files changed, 204 insertions(+) create mode 100644 Algorithm_tests/graphtheory_tests/test_NN.py create mode 100644 Algorithms/graphtheory/nearest-neighbor-tsp/NearestNeighborTSP.py create mode 100644 Algorithms/graphtheory/nearest-neighbor-tsp/graph.txt diff --git a/Algorithm_tests/graphtheory_tests/test_NN.py b/Algorithm_tests/graphtheory_tests/test_NN.py new file mode 100644 index 0000000..ad8322f --- /dev/null +++ b/Algorithm_tests/graphtheory_tests/test_NN.py @@ -0,0 +1,69 @@ +import unittest +import sys + +# Import from different folder +sys.path.append("Algorithms/graphtheory/nearest-neighbor-tsp/") + +import NearestNeighborTSP + + +class TestNN(unittest.TestCase): + def setUp(self): + self.G1 = [[0,3,-1],[3,0,1],[-1,1,0]] + self.correct_path1 = [0,1,2,0] + + # No possible solution for this one so its a dead end + 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]] + self.correct_path2 = [0,1,3,4] + + # No possible solution for this one so its a dead end + 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]] + self.correct_path3 = [0, 1, 3] + + # Multiple possible solutions + self.G4 = [[0,1,1,1],[1,0,1,1],[1,1,0,1],[1,1,1,0]] + self.correct_path4 = [0, 1, 2, 3, 0] + + + # adjacency matrix of a graph for testing + 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]] + # correct rank of each node's neighbors + correctNeighbors = [[1,4,2],[0,2,3],[1,0,3,4],[4,1,2],[3,0,2]] + + + def test_0_rankNeighbors(self): + for i in range(0,4): + self.assertEqual(NearestNeighborTSP.rankNeighbors(i, self.adjMatrix), self.correctNeighbors[i], "Check if order is different.") + + + def test_1_nnTSP(self): + path=NearestNeighborTSP.nnTSP(self.adjMatrix) + # Test if path is null + self.assertIsNotNone(path,"Output is empty") + # Test if path is not complete + self.assertEqual(len(path),len(self.adjMatrix)+1,"Path in incomplete") + + + def test_linear_graph(self): + #print(NearestNeighbor.nnTSP(self.G2)) + path = NearestNeighborTSP.nnTSP(self.G1) + self.assertEqual(path,self.correct_path1) + + + def test_simple_graph(self): + path = NearestNeighborTSP.nnTSP(self.G2) + self.assertEqual(path,self.correct_path2) + + + def test_disconnected_graph(self): + path = NearestNeighborTSP.nnTSP(self.G3) + self.assertEqual(path, self.correct_path3) + + + def test_complete_graph(self): + path = NearestNeighborTSP.nnTSP(self.G4) + self.assertEqual(path, self.correct_path4) + +if __name__ == '__main__': + print("Running Nearest Neighbor TSP solver tests:") + unittest.main() \ No newline at end of file diff --git a/Algorithms/graphtheory/nearest-neighbor-tsp/NearestNeighborTSP.py b/Algorithms/graphtheory/nearest-neighbor-tsp/NearestNeighborTSP.py new file mode 100644 index 0000000..fc6b658 --- /dev/null +++ b/Algorithms/graphtheory/nearest-neighbor-tsp/NearestNeighborTSP.py @@ -0,0 +1,126 @@ +""" +Author: Philip Andreadis +e-mail: philip_andreadis@hotmail.com + + This script implements a simple heuristic solver for the Traveling Salesman Problem. + It is not guaranteed that an optimal solution will be found. + + Format of input text file must be as follows: + 1st line - number of nodes + each next line is an edge and its respective weight + + The program is executed via command line with the graph in the txt format as input. + +""" + +import time +import sys + + + +""" +This function reads the text file and returns a 2d list which represents +the adjacency matrix of the given graph +""" +def parseGraph(path): + # Read number of vertices and create adjacency matrix + f = open(path, "r") + n = int(f.readline()) + adjMatrix = [[-1 for i in range(n)] for j in range(n)] + #Fill adjacency matrix with the correct edges + for line in f: + edge = line.split(" ") + edge = list(map(int, edge)) + adjMatrix[edge[0]][edge[1]] = edge[2] + adjMatrix[edge[1]][edge[0]] = edge[2] + for i in range(len(adjMatrix)): + adjMatrix[i][i] = 0 + return adjMatrix + + + +""" +Returns all the neighboring nodes of a node sorted based on the distance between them. +""" +def rankNeighbors(node,adj): + sortednn = {} + nList = [] + for i in range(len(adj[node])): + if adj[node][i]>0: + sortednn[i] = adj[node][i] + sortednn = {k: v for k, v in sorted(sortednn.items(), key=lambda item: item[1])} + nList = list(sortednn.keys()) + return nList + + +""" +Function implementing the logic of nearest neighbor TSP. +Generate two lists a and b, placing the starting node in list a and the rest in list b. +While b is not empty append to a the closest neighboring node of the last node in a and remove it from b. +Repeat until a full path has been added to a and b is empty. +Returns list a representing the shortest path of the graph. +""" +def nnTSP(adj): + nodes = list(range(0, len(adj))) + #print(nodes) + weight = 0 + global length + # Starting node is 0 + a = [] + a.append(nodes[0]) + b = nodes[1:] + while b: + # Take last placed node in a + last = a[-1] + # Find its nearest neighbor + sortedNeighbors = rankNeighbors(last,adj) + # If node being checked has no valid neighbors and the path is not complete a dead end is reached + if (not sortedNeighbors) and len(a)