Skip to content
Open
Changes from all 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
205 changes: 205 additions & 0 deletions graphs/travelling_salesman.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
import heapq


def tsp(cost):

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

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 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)
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 10 in graphs/travelling_salesman.py

View workflow job for this annotation

GitHub Actions / ruff

Ruff (E501)

graphs/travelling_salesman.py:10: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 14 in graphs/travelling_salesman.py

View workflow job for this annotation

GitHub Actions / ruff

Ruff (E501)

graphs/travelling_salesman.py:14: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 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 (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 17 in graphs/travelling_salesman.py

View workflow job for this annotation

GitHub Actions / ruff

Ruff (E501)

graphs/travelling_salesman.py:17: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 22 in graphs/travelling_salesman.py

View workflow job for this annotation

GitHub Actions / ruff

Ruff (E501)

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

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 27 in graphs/travelling_salesman.py

View workflow job for this annotation

GitHub Actions / ruff

Ruff (E501)

graphs/travelling_salesman.py:27: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 = create_list(cost)

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

# construct the travelling salesman tour
tsp_tour = approximate_tsp(adj)

# calculate the cost of the tour
tsp_cost = tour_cost(tsp_tour)

return tsp_cost


# function to implement approximate TSP
def approximate_tsp(adj):

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 approximate_tsp

Please provide return type hint for the function: approximate_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: adj

n = len(adj)

# to store the cost of minimum spanning tree
mst_cost = [0]

# stores edges of minimum spanning tree
mst_edges = find_mst(adj, mst_cost)

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

# create adjacency list for mst
mst_adj = [[] for _ in range(n)]
mst_edges = find_mst(adj, mst_cost)
for e in mst_edges:
mst_adj[e[0]].append([e[1], e[2]])
mst_adj[e[1]].append([e[0], e[2]])

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

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

# to store the final tour path
tour_path = []

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
tour_path.append([u, v, weight])

return tour_path


def tour_cost(tour):

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 tour_cost

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

Please provide type hint for the parameter: tour

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


def eulerian_circuit(adj, u, tour, visited, parent):

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 eulerian_circuit

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

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:

Check failure on line 111 in graphs/travelling_salesman.py

View workflow job for this annotation

GitHub Actions / ruff

Ruff (E712)

graphs/travelling_salesman.py:111:12: E712 Avoid equality comparisons to `False`; use `not visited[v]:` for false checks
eulerian_circuit(adj, v, tour, visited, u)


# function to find the minimum spanning tree
def find_mst(adj, mst_cost):

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 find_mst

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

Please provide type hint for the parameter: adj

Please provide type hint for the parameter: mst_cost

n = len(adj)

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

# stores edges of minimum spanning tree
mst_edges = []

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

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

if parent != -1:
mst_edges.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 mst_edges


# function to calculate if the
# triangle inequality is violated
def triangle_inequality(adj):

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 triangle_inequality

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

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])

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

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]
cost_UV = x[1]

Check failure on line 170 in graphs/travelling_salesman.py

View workflow job for this annotation

GitHub Actions / ruff

Ruff (N806)

graphs/travelling_salesman.py:170:13: N806 Variable `cost_UV` in function should be lowercase
for y in adj[v]:
w = y[0]
cost_VW = y[1]

Check failure on line 173 in graphs/travelling_salesman.py

View workflow job for this annotation

GitHub Actions / ruff

Ruff (N806)

graphs/travelling_salesman.py:173:17: N806 Variable `cost_VW` in function should be lowercase
for z in adj[u]:
if z[0] == w:
cost_UW = z[1]

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: cost_UW

if (cost_UV + cost_VW < cost_UW) and (u < w):
return True
# no violations found
return False


# function to create the adjacency list
def create_list(cost):

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 create_list

Please provide return type hint for the function: create_list. 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

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))