-
-
Notifications
You must be signed in to change notification settings - Fork 45.4k
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
Fixes #12128 #12147
Open
SunayBhoyar
wants to merge
5
commits into
TheAlgorithms:master
Choose a base branch
from
SunayBhoyar:master
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+205
−7
Open
Fixes #12128 #12147
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
81ef261
add: TSP in graph
SunayBhoyar d6b11f7
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] 5fe84b5
Added travelling saleman problem
SunayBhoyar 0bd8b9c
fix: TSP in graph
SunayBhoyar 3c8e4d2
fix: TSP in graph
SunayBhoyar File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,193 @@ | ||
#!/usr/bin/env python3 | ||
import itertools | ||
import math | ||
|
||
|
||
class InvalidGraphError(ValueError): | ||
"""Custom error for invalid graph inputs.""" | ||
|
||
|
||
def euclidean_distance(point1: list[float], point2: list[float]) -> float: | ||
""" | ||
Calculate the Euclidean distance between two points in 2D space. | ||
|
||
:param point1: Coordinates of the first point [x, y] | ||
:param point2: Coordinates of the second point [x, y] | ||
:return: The Euclidean distance between the two points | ||
|
||
>>> euclidean_distance([0, 0], [3, 4]) | ||
5.0 | ||
>>> euclidean_distance([1, 1], [1, 1]) | ||
0.0 | ||
>>> euclidean_distance([1, 1], ['a', 1]) | ||
Traceback (most recent call last): | ||
... | ||
ValueError: Invalid input: Points must be numerical coordinates | ||
""" | ||
try: | ||
return math.sqrt((point2[0] - point1[0]) ** 2 + (point2[1] - point1[1]) ** 2) | ||
except TypeError: | ||
raise ValueError("Invalid input: Points must be numerical coordinates") | ||
|
||
|
||
def validate_graph(graph_points: dict[str, list[float]]) -> None: | ||
""" | ||
Validate the input graph to ensure it has valid nodes and coordinates. | ||
|
||
:param graph_points: A dictionary where the keys are node names, | ||
and values are 2D coordinates as [x, y] | ||
:raises InvalidGraphError: If the graph points are not valid | ||
""" | ||
if not isinstance(graph_points, dict): | ||
raise InvalidGraphError( | ||
"Graph must be a dictionary with node names and coordinates" | ||
) | ||
|
||
for node, coordinates in graph_points.items(): | ||
if ( | ||
not isinstance(node, str) | ||
or not isinstance(coordinates, list) | ||
or len(coordinates) != 2 | ||
): | ||
raise InvalidGraphError("Each node must have a valid 2D coordinate [x, y]") | ||
|
||
|
||
def travelling_salesman_brute_force( | ||
graph_points: dict[str, list[float]], | ||
) -> tuple[list[str], float]: | ||
""" | ||
Solve the Travelling Salesman Problem using brute force. | ||
|
||
:param graph_points: A dictionary of nodes and their coordinates {node: [x, y]} | ||
:return: The shortest path and its total distance | ||
|
||
>>> graph = {"A": [10, 20], "B": [30, 21], "C": [15, 35]} | ||
>>> travelling_salesman_brute_force(graph) | ||
(['A', 'C', 'B', 'A'], 56.35465722402587) | ||
|
||
>>> travelling_salesman_brute_force({}) | ||
Traceback (most recent call last): | ||
... | ||
travlelling_salesman_problem.InvalidGraphError: Graph must have at least two nodes | ||
""" | ||
validate_graph(graph_points) | ||
|
||
nodes = list(graph_points.keys()) | ||
if len(nodes) < 2: | ||
raise InvalidGraphError("Graph must have at least two nodes") | ||
|
||
min_path = [] | ||
min_distance = float("inf") | ||
|
||
start_node = nodes[0] | ||
other_nodes = nodes[1:] | ||
|
||
for perm in itertools.permutations(other_nodes): | ||
path = [start_node, *list(perm), start_node] | ||
total_distance = 0.0 | ||
for i in range(len(path) - 1): | ||
total_distance += euclidean_distance( | ||
graph_points[path[i]], graph_points[path[i + 1]] | ||
) | ||
|
||
if total_distance < min_distance: | ||
min_distance = total_distance | ||
min_path = path | ||
|
||
return min_path, min_distance | ||
|
||
|
||
def travelling_salesman_dynamic_programming( | ||
graph_points: dict[str, list[float]], | ||
) -> tuple[list[str], float]: | ||
""" | ||
Solve the Travelling Salesman Problem using dynamic programming. | ||
|
||
:param graph_points: A dictionary of nodes and their coordinates {node: [x, y]} | ||
:return: The shortest path and its total distance | ||
|
||
>>> graph = {"A": [10, 20], "B": [30, 21], "C": [15, 35]} | ||
>>> travelling_salesman_dynamic_programming(graph) | ||
(['A', 'C', 'B', 'A'], 56.35465722402587) | ||
>>> travelling_salesman_dynamic_programming({}) | ||
Traceback (most recent call last): | ||
... | ||
travlelling_salesman_problem.InvalidGraphError: Graph must have at least two nodes | ||
""" | ||
validate_graph(graph_points) | ||
|
||
n = len(graph_points) | ||
if n < 2: | ||
raise InvalidGraphError("Graph must have at least two nodes") | ||
|
||
nodes = list(graph_points.keys()) | ||
|
||
# Initialize distance matrix with float values | ||
dist = [[0.0] * n for _ in range(n)] | ||
for i in range(n): | ||
for j in range(n): | ||
dist[i][j] = euclidean_distance( | ||
graph_points[nodes[i]], graph_points[nodes[j]] | ||
) | ||
|
||
dp = [[float("inf")] * n for _ in range(1 << n)] | ||
dp[1][0] = 0 | ||
|
||
for mask in range(1 << n): | ||
for u in range(n): | ||
if mask & (1 << u): | ||
for v in range(n): | ||
if mask & (1 << v) == 0: | ||
next_mask = mask | (1 << v) | ||
dp[next_mask][v] = min( | ||
dp[next_mask][v], dp[mask][u] + dist[u][v] | ||
) | ||
|
||
final_mask = (1 << n) - 1 | ||
min_cost = float("inf") | ||
end_node = -1 | ||
|
||
for u in range(1, n): | ||
if min_cost > dp[final_mask][u] + dist[u][0]: | ||
min_cost = dp[final_mask][u] + dist[u][0] | ||
end_node = u | ||
|
||
path = [] | ||
mask = final_mask | ||
while end_node != 0: | ||
path.append(nodes[end_node]) | ||
for u in range(n): | ||
if ( | ||
mask & (1 << u) | ||
and dp[mask][end_node] | ||
== dp[mask ^ (1 << end_node)][u] + dist[u][end_node] | ||
): | ||
mask ^= 1 << end_node | ||
end_node = u | ||
break | ||
|
||
path.append(nodes[0]) | ||
path.reverse() | ||
path.append(nodes[0]) | ||
|
||
return path, min_cost | ||
|
||
|
||
if __name__ == "__main__": | ||
demo_graph = { | ||
"A": [10.0, 20.0], | ||
"B": [30.0, 21.0], | ||
"C": [15.0, 35.0], | ||
"D": [40.0, 10.0], | ||
"E": [25.0, 5.0], | ||
"F": [5.0, 15.0], | ||
"G": [50.0, 25.0], | ||
} | ||
|
||
# Example usage for brute force | ||
brute_force_result = travelling_salesman_brute_force(demo_graph) | ||
print(f"Brute force result: {brute_force_result}") | ||
|
||
# Example usage for dynamic programming | ||
dp_result = travelling_salesman_dynamic_programming(demo_graph) | ||
print(f"Dynamic programming result: {dp_result}") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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/travlelling_salesman_problem.py
, please provide doctest for the functionvalidate_graph