Skip to content
Open
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
206 changes: 206 additions & 0 deletions graphs/travelling_salesman.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
import heapq

Check failure on line 1 in graphs/travelling_salesman.py

View workflow job for this annotation

GitHub Actions / ruff

Ruff (I001)

graphs/travelling_salesman.py:1:1: I001 Import block is un-sorted or un-formatted

def tsp(cost):
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As there is no test file in this pull request nor any test function or class in the file graphs/travelling_salesman.py, please provide doctest for the function tsp

Please provide return type hint for the function: tsp. If the function does not return a value, please provide the type hint as: def function() -> None:

Please provide type hint for the parameter: cost

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As there is no test file in this pull request nor any test function or class in the file graphs/travelling_salesman.py, please provide doctest for the function tsp

Please provide return type hint for the function: tsp. If the function does not return a value, please provide the type hint as: def function() -> None:

Please provide type hint for the parameter: cost

"""
https://www.geeksforgeeks.org/dsa/approximate-solution-for-travelling-salesman-problem-using-mst/

Problem definition:
Given a 2d matrix cost[][] of size n where cost[i][j] denotes the cost of moving from city i to city j.

Check failure on line 8 in graphs/travelling_salesman.py

View workflow job for this annotation

GitHub Actions / ruff

Ruff (E501)

graphs/travelling_salesman.py:8:89: E501 Line too long (107 > 88)
The task is to complete a tour from city 0 to all other towns such that we visit each city exactly once

Check failure on line 9 in graphs/travelling_salesman.py

View workflow job for this annotation

GitHub Actions / ruff

Ruff (E501)

graphs/travelling_salesman.py:9:89: E501 Line too long (107 > 88)
and then return to city 0 at minimum cost.

Both the Naive and Dynamic Programming solutions for this problem are infeasible.
In fact, there is no polynomial time solution available for this problem as it is a known NP-Hard problem.

Check failure on line 13 in graphs/travelling_salesman.py

View workflow job for this annotation

GitHub Actions / ruff

Ruff (E501)

graphs/travelling_salesman.py:13:89: E501 Line too long (110 > 88)

There are approximate algorithms to solve the problem though; for example, the Minimum Spanning Tree (MST) based

Check failure on line 15 in graphs/travelling_salesman.py

View workflow job for this annotation

GitHub Actions / ruff

Ruff (E501)

graphs/travelling_salesman.py:15:89: E501 Line too long (116 > 88)
approximation algorithm defined below which gives a solution that is at most twice the cost of the optimal solution.

Check failure on line 16 in graphs/travelling_salesman.py

View workflow job for this annotation

GitHub Actions / ruff

Ruff (E501)

graphs/travelling_salesman.py:16:89: E501 Line too long (120 > 88)

Assumptions:
1. The graph is complete.

2. The problem instance satisfies Triangle-Inequality.(The least distant path to reach a vertex j from i is always to reach j

Check failure on line 21 in graphs/travelling_salesman.py

View workflow job for this annotation

GitHub Actions / ruff

Ruff (W291)

graphs/travelling_salesman.py:21:130: W291 Trailing whitespace

Check failure on line 21 in graphs/travelling_salesman.py

View workflow job for this annotation

GitHub Actions / ruff

Ruff (E501)

graphs/travelling_salesman.py:21:89: E501 Line too long (130 > 88)
directly from i, rather than through some other vertex k)

Check failure on line 22 in graphs/travelling_salesman.py

View workflow job for this annotation

GitHub Actions / ruff

Ruff (W291)

graphs/travelling_salesman.py:22:62: W291 Trailing whitespace

3. The cost matrix is symmetric, i.e., cost[i][j] = cost[j][i]

Time complexity: O(n ^ 3), the time complexity of triangleInequality() function is O(n ^ 3) as we are using 3 nested loops.

Check failure on line 26 in graphs/travelling_salesman.py

View workflow job for this annotation

GitHub Actions / ruff

Ruff (E501)

graphs/travelling_salesman.py:26:89: E501 Line too long (127 > 88)
Space Complexity: O(n ^ 2), to store the adjacency list, and creating MST.

"""
# create the adjacency list
adj = createList(cost)

#check for triangle inequality violations
if triangleInequality(adj):
print("Triangle Inequality Violation")
return -1

# construct the travelling salesman tour
tspTour = approximateTSP(adj)
Comment thread
raisaaajose marked this conversation as resolved.
Outdated

# calculate the cost of the tour
tspCost = tourCost(tspTour)
Comment thread
raisaaajose marked this conversation as resolved.
Outdated

return tspCost

# function to implement approximate TSP
def approximateTSP(adj):
Comment thread
raisaaajose marked this conversation as resolved.
Outdated
n = len(adj)

# to store the cost of minimum spanning tree
mstCost = [0]
Comment thread
raisaaajose marked this conversation as resolved.
Outdated

# stores edges of minimum spanning tree
mstEdges = findMST(adj, mstCost)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable and function names should follow the snake_case naming convention. Please update the following name accordingly: mstEdges


# to mark the visited nodes
visited = [False] * n

# create adjacency list for mst
mstAdj = [[] for _ in range(n)]
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable and function names should follow the snake_case naming convention. Please update the following name accordingly: mstAdj

for e in mstEdges:
mstAdj[e[0]].append([e[1], e[2]])
mstAdj[e[1]].append([e[0], e[2]])

# to store the eulerian tour
tour = []
eulerianCircuit(mstAdj, 0, tour, visited, -1)

# add the starting node to the tour
tour.append(0)

# to store the final tour path
tourPath = []
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable and function names should follow the snake_case naming convention. Please update the following name accordingly: tourPath


for i in range(len(tour) - 1):
u = tour[i]
v = tour[i + 1]
weight = 0

# find the weight of the edge u -> v
for neighbor in adj[u]:
if neighbor[0] == v:
weight = neighbor[1]
break

# add the edge to the tour path
tourPath.append([u, v, weight])

return tourPath

def tourCost(tour):
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As there is no test file in this pull request nor any test function or class in the file graphs/travelling_salesman.py, please provide doctest for the function tourCost

Please provide return type hint for the function: tourCost. If the function does not return a value, please provide the type hint as: def function() -> None:

Variable and function names should follow the snake_case naming convention. Please update the following name accordingly: tourCost

Please provide type hint for the parameter: tour

cost = 0
for edge in tour:
cost += edge[2]
return cost


def eulerianCircuit(adj, u, tour, visited, parent):
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As there is no test file in this pull request nor any test function or class in the file graphs/travelling_salesman.py, please provide doctest for the function eulerianCircuit

Please provide return type hint for the function: eulerianCircuit. If the function does not return a value, please provide the type hint as: def function() -> None:

Variable and function names should follow the snake_case naming convention. Please update the following name accordingly: eulerianCircuit

Please provide type hint for the parameter: adj

Please provide descriptive name for the parameter: u

Please provide type hint for the parameter: u

Please provide type hint for the parameter: tour

Please provide type hint for the parameter: visited

Please provide type hint for the parameter: parent

visited[u] = True
tour.append(u)

for neighbor in adj[u]:
v = neighbor[0]
if v == parent:
continue

if visited[v] == False:
eulerianCircuit(adj, v, tour, visited, u)

# function to find the minimum spanning tree
def findMST(adj, mstCost):
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As there is no test file in this pull request nor any test function or class in the file graphs/travelling_salesman.py, please provide doctest for the function findMST

Please provide return type hint for the function: findMST. If the function does not return a value, please provide the type hint as: def function() -> None:

Variable and function names should follow the snake_case naming convention. Please update the following name accordingly: findMST

Please provide type hint for the parameter: adj

Please provide type hint for the parameter: mstCost

Variable and function names should follow the snake_case naming convention. Please update the following name accordingly: mstCost

n = len(adj)

# to marks the visited nodes
visited = [False] * n

# stores edges of minimum spanning tree
mstEdges = []
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable and function names should follow the snake_case naming convention. Please update the following name accordingly: mstEdges


pq = []
heapq.heappush(pq, [0, 0, -1])

while pq:
current = heapq.heappop(pq)

u = current[1]
weight = current[0]
parent = current[2]

if visited[u]:
continue

mstCost[0] += weight
visited[u] = True

if parent != -1:
mstEdges.append([u, parent, weight])

for neighbor in adj[u]:
v = neighbor[0]
if v == parent:
continue
w = neighbor[1]

if not visited[v]:
heapq.heappush(pq, [w, v, u])
return mstEdges



# function to calculate if the
# triangle inequality is violated
def triangleInequality(adj):
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As there is no test file in this pull request nor any test function or class in the file graphs/travelling_salesman.py, please provide doctest for the function triangleInequality

Please provide return type hint for the function: triangleInequality. If the function does not return a value, please provide the type hint as: def function() -> None:

Variable and function names should follow the snake_case naming convention. Please update the following name accordingly: triangleInequality

Please provide type hint for the parameter: adj

n = len(adj)

# Sort each adjacency list based
# on the weight of the edges
for i in range(n):
adj[i].sort(key=lambda a: a[1])
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please provide descriptive name for the parameter: a

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please provide descriptive name for the parameter: a


# check triangle inequality for each
# triplet of nodes (u, v, w)
for u in range(n):
for x in adj[u]:
v = x[0]
costUV = x[1]
Comment thread
raisaaajose marked this conversation as resolved.
Outdated
for y in adj[v]:
w = y[0]
costVW = y[1]
Comment thread
raisaaajose marked this conversation as resolved.
Outdated
for z in adj[u]:
if z[0] == w:
costUW = z[1]
Comment thread
raisaaajose marked this conversation as resolved.
Outdated
if (costUV + costVW < costUW) and (u < w):
return True
# no violations found
return False

# function to create the adjacency list
def createList(cost):
Comment thread
raisaaajose marked this conversation as resolved.
Outdated
n = len(cost)

# to store the adjacency list
adj = [[] for _ in range(n)]

for u in range(n):
for v in range(n):
# if there is no edge between u and v
if cost[u][v] == 0:
continue
# add the edge to the adjacency list
adj[u].append([v, cost[u][v]])

return adj



if __name__ == "__main__":
#test
cost = [
[0, 1000, 5000],
[5000, 0, 1000],
[1000, 5000, 0]
]

print(tsp(cost))