Skip to content

Add Traveling Salesman Problem Algorithms And Tests #12820

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 20 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
10b1a2a
Add Traveling Salesman Problem algorithms and tests
MapleBauhinia Jul 5, 2025
e39c3ce
Add TSP Problem
MapleBauhinia Jul 5, 2025
c32a022
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jul 5, 2025
85f1401
Fix: TSP rename lambda parameter and add type hints
MapleBauhinia Jul 5, 2025
345d58f
add-tsp-problem
MapleBauhinia Jul 5, 2025
c7be8c0
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jul 5, 2025
a766f38
Fix: format and pass all tests
MapleBauhinia Jul 5, 2025
ed8b6e3
add-tsp-problem
MapleBauhinia Jul 5, 2025
13df43e
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jul 5, 2025
7bd83fd
Standardize output to int
MapleBauhinia Jul 5, 2025
81fcb2f
Merge branch 'add-tsp-problem' of https://github.com/MapleBauhinia/Py…
MapleBauhinia Jul 5, 2025
80cc148
Fix: Build PR
MapleBauhinia Jul 5, 2025
cef217a
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jul 5, 2025
f61f7cf
Fix: format
MapleBauhinia Jul 5, 2025
3042b37
Merge branch 'add-tsp-problem' of https://github.com/MapleBauhinia/Py…
MapleBauhinia Jul 5, 2025
9cc0448
Fix: tsp-greedy
MapleBauhinia Jul 5, 2025
9c9a3e4
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jul 5, 2025
4c59775
Fix: ruff check
MapleBauhinia Jul 5, 2025
d913580
Merge branch 'add-tsp-problem' of https://github.com/MapleBauhinia/Py…
MapleBauhinia Jul 5, 2025
86be481
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jul 5, 2025
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
Prev Previous commit
Next Next commit
[pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
  • Loading branch information
pre-commit-ci[bot] committed Jul 5, 2025
commit c32a022a18274df74217b4fa4b52857faf98d643
12 changes: 10 additions & 2 deletions graphs/tests/test_traveling_salesman_problem.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import pytest
from graphs.traveling_salesman_problem import tsp_brute_force, tsp_dp, tsp_greedy


def sample_graph_1() -> list[list[int]]:
return [
[0, 29, 20],
[29, 0, 15],
[20, 15, 0],
]


def sample_graph_2() -> list[list[int]]:
return [
[0, 10, 15, 20],
Expand All @@ -16,31 +18,37 @@ def sample_graph_2() -> list[list[int]]:
[20, 25, 30, 0],
]


def test_brute_force():
graph = sample_graph_1()
assert tsp_brute_force(graph) == 64


def test_dp():
graph = sample_graph_1()
assert tsp_dp(graph) == 64


def test_greedy():
graph = sample_graph_1()
# The greedy algorithm does not guarantee an optimal solution;
# The greedy algorithm does not guarantee an optimal solution;
# it is necessary to verify that its output is an integer greater than 0.
# An approximate solution cannot be represented by '==' and can only ensure that the result is reasonable.
result = tsp_greedy(graph)
assert isinstance(result, int)
assert result >= 64


def test_dp_larger_graph():
graph = sample_graph_2()
assert tsp_dp(graph) == 80
assert tsp_dp(graph) == 80


def test_brute_force_larger_graph():
graph = sample_graph_2()
assert tsp_brute_force(graph) == 80


def test_greedy_larger_graph():
graph = sample_graph_2()
result = tsp_greedy(graph)
Expand Down
46 changes: 27 additions & 19 deletions graphs/traveling_salesman_problem.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from itertools import permutations


def tsp_brute_force(graph: list[list[int]]) -> int:
"""
Solves TSP using brute-force permutations.
Expand All @@ -16,22 +17,23 @@ def tsp_brute_force(graph: list[list[int]]) -> int:
"""
n = len(graph)
# Apart from other cities aside from City 0, City 0 serves as the starting point.
nodes = list(range(1, n))
min_path = float('inf')
nodes = list(range(1, n))
min_path = float("inf")

# Enumerate all the permutations from city 1 to city n-1.
for perm in permutations(nodes):
# Construct a complete path:
# Construct a complete path:
# starting from point 0, visit in the order of arrangement, and then return to point 0.
path = [0] + list(perm) + [0]

# Calculate the total distance of the path.
# Calculate the total distance of the path.
# Update the shortest path.
total_cost = sum(graph[path[i]][path[i + 1]] for i in range(n))
min_path = min(min_path, total_cost)

return min_path


def tsp_dp(graph: list[list[int]]) -> int:
"""
Solves the Traveling Salesman Problem using Held-Karp dynamic programming.
Expand All @@ -50,9 +52,9 @@ def tsp_dp(graph: list[list[int]]) -> int:
# Create a dynamic programming table of size (2^n) x n.
# Noting: 1 << n = 2^n
# dp[mask][i] represents the shortest path starting from city 0, passing through the cities in the mask, and ultimately ending at city i.
dp = [[float('inf')] * n for _ in range(1 << n)]
dp = [[float("inf")] * n for _ in range(1 << n)]
# Initial state: only city 0 is visited, and the path length is 0.
dp[1][0] = 0
dp[1][0] = 0

for mask in range(1 << n):
# The mask indicates which cities have been visited.
Expand All @@ -70,10 +72,11 @@ def tsp_dp(graph: list[list[int]]) -> int:
# State Transition: From city u to city v, updating the shortest path.
next_mask = mask | (1 << v)
dp[next_mask][v] = min(dp[next_mask][v], dp[mask][u] + graph[u][v])

# After completing visits to all cities, return to city 0 and obtain the minimum value.
return min(dp[(1 << n) - 1][i] + graph[i][0] for i in range(1, n))


def tsp_greedy(graph: list[list[int]]) -> int:
"""
Solves TSP approximately using the nearest neighbor heuristic.
Expand Down Expand Up @@ -101,9 +104,13 @@ def tsp_greedy(graph: list[list[int]]) -> int:
for _ in range(n - 1):
# Find the nearest city to the current location that has not been visited.
next_city = min(
((city, cost) for city, cost in enumerate(graph[current]) if not visited[city] and city != current),
(
(city, cost)
for city, cost in enumerate(graph[current])
if not visited[city] and city != current
),
key=lambda x: x[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: x

Copy link
Author

Choose a reason for hiding this comment

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

OK! Now I have solved this problem. I replaced the x with 'cost'.

default=(None, float('inf'))
default=(None, float("inf")),
)[0]

# If no such city exists, break the loop.
Expand Down Expand Up @@ -135,24 +142,25 @@ def test_tsp_example():

result = tsp_brute_force(graph)
if result != 80:
raise Exception('tsp_brute_force Incorrect result')
raise Exception("tsp_brute_force Incorrect result")
else:
print('Test passed')
print("Test passed")

result = tsp_dp(graph)
if result != 80:
raise Exception('tsp_dp Incorrect result')
raise Exception("tsp_dp Incorrect result")
else:
print("Test passed")

result = tsp_greedy(graph)
if result != 80:
if result < 0:
raise Exception('tsp_greedy Incorrect result')
raise Exception("tsp_greedy Incorrect result")
else:
print("tsp_greedy gets an approximate result.")
else:
print('Test passed')
print("Test passed")


if __name__ == '__main__':
test_tsp_example()
if __name__ == "__main__":
test_tsp_example()
Loading
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy