]> Skullheadx's Git Forge - The-Traveling-Salesman-Problem.git/commitdiff
greedy heuristic clean up + explanation
authorSkullheadx <94652084+Skullheadx@users.noreply.github.com>
Thu, 29 Dec 2022 04:20:21 +0000 (23:20 -0500)
committerSkullheadx <94652084+Skullheadx@users.noreply.github.com>
Thu, 29 Dec 2022 04:20:21 +0000 (23:20 -0500)
README.md
display.py
graph.py
greedy.py
main.py

index a5be25d06cd5e845f5f38c30d9f5a56a5f3dc0c5..07822bdd67362e1a13cdf355777a224bbd8e7b58 100644 (file)
--- a/README.md
+++ b/README.md
@@ -82,3 +82,5 @@ Using these new approximation ratios, we can now determine how effective differe
 
 --------------------------------------------------------------------------------------------------------------
 **Method 3: Greedy Heuristic**
+
+If we use an approach similar to how we created the Minimum Spanning Tree, we can apply similar logic to create a good heuristic. In each iteration, we determine the shortest edge distance to add to the route, however we must follow the rules that each town is connected to exactly 2 other towns and by adding an edge, we do not create a cycle of length smaller than n.  
\ No newline at end of file
index c9aa16a2de41538784472f75d7e998f70e4f8b08..80aefc71a483d6fc027b34b8c4e30627319acc7d 100644 (file)
@@ -1,5 +1,6 @@
 import pygame
 import math
+from graph import linker
 
 pygame.init()
 
@@ -75,7 +76,7 @@ class Display:
     pygame.display.set_caption("Traveling Salesman Problem")
     font = pygame.font.SysFont("arial", 20)
 
-    def __init__(self, path: str, route: list, mst=None, one_tree=None, removed_vertex=None) -> None:
+    def __init__(self, path: str, route: list, mst=None, one_tree=None, removed_vertex=None, mode="direct") -> None:
         with open(path, "r") as f:
             contents = f.read().split("\n")
             if contents[-1] == "":
@@ -86,14 +87,17 @@ class Display:
         self.screen = pygame.display.set_mode((self.WIDTH, self.HEIGHT))
 
         self.route = route
-        # self.salesman = Salesman(self.route)
+        if mode == "points":
+            self.route = linker(self.route)
+
+        self.salesman = Salesman(self.route)
 
         self.mst = mst
         self.one_tree = one_tree
         self.removed_vertex = removed_vertex
 
-    def update(self, delta: float) -> None:
-        self.salesman.update(delta)
+    def update(self, delta: float) -> None:
+        self.salesman.update(delta)
 
     def show(self) -> None:
         is_running = True
@@ -105,31 +109,31 @@ class Display:
                 if event.type == pygame.QUIT:
                     is_running = False
 
-            self.salesman.update(delta)
+            self.salesman.update(delta)
 
             self.screen.fill(WHITE)
             if self.one_tree is not None:
-                pygame.draw.circle(self.screen,BLUE,self.removed_vertex,15)
+                pygame.draw.circle(self.screen, BLUE, self.removed_vertex, 15)
                 for line in self.one_tree:  # One Tree
                     start, end = line
                     pygame.draw.line(self.screen, GREEN, start, end, 12)
             if self.mst is not None:
-                for i,line in enumerate(self.mst):  # Minimum Spanning Tree
+                for i, line in enumerate(self.mst):  # Minimum Spanning Tree
                     start, end = line
-                    pygame.draw.line(self.screen,ORANGE, start, end, 4)
-                    text = self.font.render(str(i), True, (0,0,0))
-                    self.screen.blit(text, text.get_rect(center=((start[0]+end[0])/2,(end[1]+start[1])/2)))
+                    pygame.draw.line(self.screen, ORANGE, start, end, 4)
+                    text = self.font.render(str(i), True, (0,0,0))
+                    self.screen.blit(text, text.get_rect(center=((start[0]+end[0])/2,(end[1]+start[1])/2)))
 
             if len(self.route) > 1:
                 pygame.draw.lines(self.screen, BLUE, True, self.route, 3)  # Route
 
-            for i,node in enumerate(self.nodes):
+            for i, node in enumerate(self.nodes):
                 node.draw(self.screen)
-                text = self.font.render(str((node.position)), True, (0, 0, 0))
-                self.screen.blit(text, text.get_rect(center=node.position))
-            self.salesman.draw(self.screen)
+                text = self.font.render(str((node.position)), True, (0, 0, 0))
+                self.screen.blit(text, text.get_rect(center=node.position))
+            self.salesman.draw(self.screen)
 
             pygame.display.update()
             delta = clock.tick(60) / 1000  # Seconds
-            # pygame.image.save(self.screen, "NN_Heuristic+OneTree.png")
+            # pygame.image.save(self.screen, "Greedy_Heuristic+OneTree.png")
             # quit()
index 55670b386bd98b4a70a68df814ce7fb58687dcb5..141fb6457aa51082d7cd7ab59a3135ecde5fa7ea 100644 (file)
--- a/graph.py
+++ b/graph.py
@@ -69,9 +69,9 @@ def calculate_route(route: list, mode="direct") -> float:
     elif mode == "points":
         d = 0.0
         for i in route:
-            start,end = i
+            start, end = i
 
-            d += distance(start,end)
+            d += distance(start, end)
         return d
 
 
@@ -86,10 +86,11 @@ def find_shortest_route(routes: list) -> list:
     return shortest_route
 
 
-def print_info(route: list, time: float, method_name: str, one_tree: float, one_tree_time: float, r=0, mode="direct") -> None:
+def print_info(route: list, time: float, method_name: str, one_tree: float, one_tree_time: float, r=0,
+               mode="direct") -> None:
     d = calculate_route(route, mode)
     if mode == "direct":
-        num_nodes =  (len(route) - 1)
+        num_nodes = (len(route) - 1)
     elif mode == "points":
         num_nodes = len(route)
     print(
@@ -146,7 +147,7 @@ def find_lower_bound(graph: list):
     rm_vertex = None
 
     for removed_vertex_index in range(len(graph)):
-        one_tree_distance,one_tree= find_one_tree(graph,removed_vertex_index)
+        one_tree_distance, one_tree = find_one_tree(graph, removed_vertex_index)
 
         if lower_bound is None or one_tree_distance > lower_bound:
             lower_bound = one_tree_distance
@@ -178,3 +179,39 @@ def find_MST(graph: list):
                 q.put((distance(town, end), end, town))
 
     return mst_distance, mst
+
+
+def linker(points):
+    p = points[:]
+    direct = [p[0][0]]
+    head = p[0][0]
+    current = p[0]
+
+    graph = dict()
+
+    for pair in p:
+        start, end = pair
+        if start in graph:
+            graph[start].append(end)
+        else:
+            graph[start] = [end]
+        if end in graph:
+            graph[end].append(start)
+        else:
+            graph[end] = [start]
+    seen = set()
+    seen.add(head)
+    while True:
+        start, end = current
+        direct.append(end)
+
+        a, b = graph[end]
+        if a == start:
+            current = (end, b)
+        else:
+            current = (end, a)
+
+        if end == head:
+            break
+
+    return direct
index 419d9a11848722976b45b6f46805031cbf2af6e8..499f9037e3fd323574e5440d879e80c14ab128ac 100644 (file)
--- a/greedy.py
+++ b/greedy.py
@@ -1,18 +1,48 @@
 from graph import distance
 from queue import PriorityQueue
+from copy import deepcopy
+
 
 def greedy(graph: list):
-    seen = set()
     route = []
 
     q = PriorityQueue()
 
-    for town1 in graph:
-        for town2 in graph:
+    for i, town1 in enumerate(graph):
+        for town2 in graph[i:]:
             if town1 != town2:
                 q.put((distance(town1, town2), town1, town2))
 
+    def detect_cycle(start, end, target, gr, seen):
+        if start == target:
+            gr = deepcopy(gr)
+            gr[start].append(end)
+            gr[end].append(start)
+
+        if end == target or start in seen:
+            return True
+
+        seen.add(start)
+
+        for x in gr[end]:
+            if x != start:
+                t = detect_cycle(end, x, target, gr, seen)
+                if t:
+                    return t
+        return False
+
+    g = {town: [] for town in graph}
+
     while not q.empty() and len(route) < len(graph):
-        current = q.get()
+        d, start, end = q.get()
+
+        if len(g[start]) >= 2 or len(g[end]) >= 2:
+            continue
+        if len(route) < len(graph)-1 and detect_cycle(start, end, start, g, set()):
+            continue
+
+        route.append((start, end))
+        g[start].append(end)
+        g[end].append(start)
 
-        if 
+    return route
diff --git a/main.py b/main.py
index 06f72e18e3a6fea6cb65f8ebf6e0d6f8b1b3260b..14e8b55d8dedee92bc92b8ef8d125193562a9458 100644 (file)
--- a/main.py
+++ b/main.py
@@ -22,7 +22,7 @@ def main():
                     print("The file does not exist")
 
     if CREATE_NEW_GRAPHS:
-        graph, filename = create(GRAPH_PATH, 640, 640, 15)
+        graph, filename = create(GRAPH_PATH, 640, 640, 100)
     else:
         filename = "graph1.txt"
         graph = read(GRAPH_PATH, filename)
@@ -30,8 +30,8 @@ def main():
     route_time_start = perf_counter()
     # route = brute_force(graph)  # 10 nodes in 85.042 seconds. Optimal = 2,262.29
     # route = nearest_neighbor(graph)  # 100 nodes in 0.5762094999663532 seconds. Distance = 6,270.568142156188
-    route = greedy(graph)  # 100 nodes in 0.5762094999663532 seconds. Distance = 6,270.568142156188
-    print(f"{route=}")
+    route = greedy(graph)  # 100 nodes in 0.1383088999427855 seconds. Distance = 5,523.211501332208 OTLB: 4,
+    # 344.881943246125 Approx. 27.119944188995277%
     route_time_end = perf_counter()
 
     # MST_distance, MST = find_MST(graph)
@@ -44,8 +44,8 @@ def main():
     print_info(route, route_time_end - route_time_start, "Greedy Heuristic", lower_bound,
                one_tree_time_end - one_tree_time_start, r=3000, mode="points")
 
-    display = Display(os.path.join(GRAPH_PATH, filename), [], mst=route, one_tree=None,
-                      removed_vertex=removed_vertex)
+    display = Display(os.path.join(GRAPH_PATH, filename), route, mst=None, one_tree=one_tree,
+                      removed_vertex=removed_vertex, mode="points")
     display.show()