-
Notifications
You must be signed in to change notification settings - Fork 0
/
algorithms.py
182 lines (139 loc) · 6.1 KB
/
algorithms.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# -*- coding: utf-8 -*-
# Autor: Miłosz Sawicki
# Licencja: GNU GPL
# 26.06.20
# Moduł zawierający implementacje algorytmów rozwiązujących problem komiwojażera.
import numpy as np
from itertools import permutations, combinations
import graphs
def brute_force(G):
"""Rozwiązuje TSP metodą sprawdzenia wszystkich możliwości.
G jest pełnym grafem nieskierowanym w postaci macierzy sąsiedztwa.
"""
# Zmienna pathWeight jest sumą wszystkich wag krawędzi ścieżki.
pathWeight = 0
# Znajdowane są wszystkie permutacje n wierzchołków.
paths = list(permutations(range(0, G.n)))
paths = [list(n) for n in paths]
# Ścieżki łączone są w cykle.
for n in paths:
n.append(n[0])
# Lista bestPath jest ścieżką o najmniejszym koszcie wszystkich krawędzi.
for r in paths:
temp_weight = G.get_path_weight(r)
if temp_weight < pathWeight or pathWeight == 0:
pathWeight = temp_weight
bestPath = r
return bestPath, pathWeight
def NN_ALG(G, current):
"""Algorytm najbliższego sąsiada.
G jest pełnym grafem nieskierowanym w postaci macierzy sąsiedztwa.
Zmienna current oznacza wierzchołek początkowy.
"""
# Lista bestPath zawiera ścieżkę będącą rozwiązaniem;
bestPath = [0 for n in range(G.n)]
# Lista visited zawiera odwiedzone już wierzchołki;
visited = [current]
# Znajduje najkrótszą spośród krawędzi
# łączących aktualny wierzchołek
# z jeszcze nieodwiedzonymi wierzchołkami.
for i in range(G.n):
# Krotka minimum := (w, j) zawiera indeks jeszcze nieodwiedzonego
# wierzchołka j oraz wagę krawędzi w, łączącą j z ostatnio dodanym
# wierzchołkiem rozwiązania.
minimum = (0, 0)
for j in range(G.n):
if (j not in visited and
(0 < G[current][j] < minimum[0] or minimum == (0, 0))):
minimum = (G[current][j], j)
current = minimum[1]
bestPath[i] = visited[-1]
visited.append(current)
# Ścieżka łączona jest w cykl
bestPath.append(bestPath[0])
return bestPath, G.get_path_weight(bestPath)
def RNN_ALG(G):
"""Powtarzalny algorytm najbliższego sąsiada (RNN).
G jest pełnym grafem nieskierowanym w postaci macierzy sąsiedztwa.
"""
# Lista bestPath zawiera ścieżkę o najmniejszej wadze z już sprawdzonych ścieżek.
bestPath = None
bestPathWeight = 0
for i in range(G.n):
# Wywoływany jest algorytm najbliższego sąsiada dla każdego n.
temp_path, temp_weight = NN_ALG(G, i)
# Porównywana jest obecna najlepsza ścieżka z nowym rozwiązaniem.
if temp_weight < bestPathWeight or bestPathWeight == 0:
bestPathWeight = temp_weight
bestPath = temp_path
return bestPath, bestPathWeight
def CI_ALG(G):
"""Algorytm najmniejszej krawędzi (ang. cheapest insertion algorithm)
G jest pełnym grafem nieskierowanym w postaci macierzy sąsiedztwa.
"""
# queue - lista krawędzi wraz z ich wagami;
# Krawędzie nie powtarzaja się tj. nie wystepuje krawedz (a,b,w) i (b,a,w).
queue = []
for n in range(0, G.n-1):
minimum = [n, G[n][n], G[n][n+1]] # [wiersz, kolumna, waga]
for j, k in enumerate(G[n][n+1:], n):
minimum[1] = j+1
minimum[2] = k
queue.append(graphs.Edge(*minimum))
# Sortowanie kolejki rosnaco według wag.
queue.sort(key = lambda x: x.w)
# Tworzony jest graf określony przez liste sąsiedctwa zawierający rozwiązanie.
solution = graphs.GraphAdjacencyList(G.n)
edge_count = 0
for i in range(len(queue)):
edge = queue.pop(0)
# Dodawanie krawędzi w ostatnej iteracji algorytmu.
if edge_count == G.n-1 and solution.is_not_third(edge.u.key, edge.v.key):
solution.add_edge(edge.u.key, edge.v.key)
# Dodawanie krawędzi, gdy nie powstanie wierzchołek,
# z którego wychodzą trzy krawędzie oraz nie powstanie cykl.
elif (solution.is_not_third(edge.u.key, edge.v.key) and
solution.has_cycle(edge.u.key, edge.v.key) is False):
solution.add_edge(edge.u.key, edge.v.key)
edge_count += 1
# Metoda get_path zwraca ścieżkę w grafie określonym przez listę sąsiedztwa.
bestPath = solution.get_path()
return bestPath, G.get_path_weight(bestPath)
def held_karp(G):
"""Algorytm Helda-Karpa
G jest pełnym grafem nieskierowanym w postaci macierzy sąsiedztwa.
"""
# D jest słownikiem w postaci D[S, p] : (y, parent), gdzie
# S - zbiór wierzchołków przez które przechodzi ścieżka bez wierzchołka 0;
# p - ostatni wierzchołek przez który przechodzi ścieżka;
# y - suma wszystkich wag krawędzi na ścieżce;
# parent - indeks przedostatniego wierzchołka na ścieżce.
D = {}
# Do rozwiązania dodawane są przypadki trywialne, gdzie zbiór S jest jednoelementowy.
for j in range(1, G.n):
D[((j,),j)] = (G.get_edge_weight(0, j), 0)
# Zmienna subset_size oznacza ilość elementów w zbiorze S.
for subset_size in range(2, G.n):
for subset in combinations(range(1,G.n), subset_size):
for p in subset:
S_bez_p = [s for s in subset if s != p]
path_weights = []
for n in S_bez_p:
if (tuple(S_bez_p), n) in D.keys():
path_weights.append((D[(tuple(S_bez_p), n)][0] + G[n][p], n))
D[tuple(subset),p] = (min(path_weights)[0], min(path_weights)[1])
# Obliczanie kosztu przejścia całej ścieżki.
cost = []
S = tuple(range(1, G.n))
for n in S:
cost.append((D[(S,n)][0] + G[n][0], n))
opt = (min(cost)[0], min(cost)[1])
# Otwarzanie trasy korzystając z zmiennej parent.
path = [0, opt[1]]
parent = path[1]
for i in reversed(sorted( D.items(), key = lambda x: len(x))):
if i[0][0] == S and i[0][1] == parent:
S = tuple([j for j in i[0][0] if j != i[0][1]])
parent = i[1][1]
path.append(parent)
return path, opt[0]