Вы находитесь на странице: 1из 4

Лабораторная работа

Наименование: задача коммивояжёра (TSP – Travelling salesman problem) метод ветвей и границ

Цель работы: изучить и решить задачу коммивояжёра

Ход работы:

1) Изучить определения, условия и методы решения задачи коммивояжёра


2) Алгоритм решения методом ветвей и границ
3) Пример решения задачи из методички
4) Вывод

Теория:

Имеется n населенных пунктов (n>0) с заданными между ними расстояниями aij ( i, j = 1, …, n). Если прямого
сообщения между пунктами i и j не существует, то полагаем aij = бесконечности. Также считаем aij =
бесконечности, i = 1, …, n. Так как на некоторых дорогах допускается только одностороннее движение, то,
вообще говоря, aij != aji.

Требуется найти только маршрут, начинающийся в данном населенном пункте, проходящий ч/з все
населенные пункты по одному разу и заканчивающийся в исходном пункте (такие циклы называются
гамильтоновыми), чтобы его длина была минимальной. Это задача коммивояжёра.

Задача коммивояжёра может быть решена с помощью простого перебора всех возможных маршрутов. Но
при больших n число маршрутов огромно. С целью сокращения перебора используют метод ветвей и границ.

Описание алгоритма

1. Вычисляем наименьший элемент в каждой строке (константа приведения для строки)


2. Переходим к новой матрице затрат, вычитая из каждой строки ее константу приведения
3. Вычисляем наименьший элемент в каждом столбце (константа приведения для столбца)
4. Переходим к новой матрице затрат, вычитая из каждого столбца его константу приведения.
Как результат имеем матрицу затрат, в которой в каждой строчке и в каждом столбце имеется хотя
бы один нулевой элемент.
5. Вычисляем границу на данном этапе как сумму констант приведения для столбцов и строк (данная
граница будет являться стоимостью, меньше которой невозможно построить искомый маршрут)

Получившийся код написан на python

import math
from typing import List
maxsize = float('inf')

class TSPP:
def __init__(self, matrix: List[List[int]]) -> None:
self.matrix = matrix
self.n = len(matrix) # размерность матрицы
self.visited = [False] * self.n # массив посещённости точек
self.path_length = maxsize # длина результирующего пути
self.path = [-1] * (self.n + 1) # результирующий путь
self._curr_path = [-1] * (self.n+1) # текущий путь
self._curr_bound = 0. # текущая оценка
self._curr_weight = 0. # текущая сумма
self._curr_level = 1 # текущий уровень ветвления

# Рассчитываем оценку для начального узла.


for i in range(self.n):
self._curr_bound += (self.first_min(i) + self.second_min(i))

self._curr_bound = math.ceil(self._curr_bound / 2)

# Начинаем с 0-го элемента.


self.visited[0] = True
self._curr_path[0] = 0

# Запускаем алгоритм.
self.TSPRecursive()

def first_min(self, i):


"""
Находит минимальную дугу с вершиной i.
"""
min = maxsize
for k in range(self.n):
if self.matrix[i][k] < min and i != k:
min = self.matrix[i][k]

return min

def second_min(self, i):


"""
Находит две минимальных дуги с вершиной i и возвращает вторую.
"""
first, second = maxsize, maxsize
for j in range(self.n):
if i == j:
continue
if self.matrix[i][j] <= first:
second = first
first = self.matrix[i][j]

elif(self.matrix[i][j] <= second and self.matrix[i][j] != first):


second = self.matrix[i][j]

return second

def TSPRecursive(self, curr_weight=0, level=1):


"""
Решение задачи коммивояжера через метод ветвей и границ.
"""
# Случай, когда мы прошли через все узлы.
if level == self.n:

# Проверяем наличие дуги из последнего узла в первый.


if self.matrix[self._curr_path[level - 1]][self._curr_path[0]]:

# Записываем всю длину пути и сравниваем её


curr_res = curr_weight + self.matrix[self._curr_path[level - 1]]
[self._curr_path[0]]
if curr_res < self.path_length:
self.path = self._curr_path.copy()
self.path[self.n] = self.path[0]
self.path_length = curr_res
return

# Для всех остальных случаев решаем задачу оптимизации


# методом ветвей и границ.
for i in range(self.n):
# Проверяем не является ли текущий узел
# посещённым или находящимся на диагонали.
if self.matrix[self._curr_path[level-1]][i] and not self.visited[i]:
old_curr_bound = self._curr_bound
curr_weight += self.matrix[self._curr_path[level - 1]][i]

# Вычисляем оценку, но для 1 уровня отдельно.


if level == 1:
bound = (self.first_min(self._curr_path[level - 1]) +
self.first_min(i)) / 2
self._curr_bound -= bound
else:
bound = (self.second_min(self._curr_path[level - 1]) +
self.first_min(i)) / 2
self._curr_bound -= bound

# Проверяем текущую длину пути (self._curr_bound + curr_weight)


# если она меньше предыдущей, то углубляемся.
if self._curr_bound + curr_weight < self.path_length:
self._curr_path[level] = i
self.visited[i] = True

self.TSPRecursive(curr_weight, level + 1)

# Если же нет, то откатываем изменения в значениях.


curr_weight -= self.matrix[self._curr_path[level - 1]][i]
self._curr_bound = old_curr_bound

# Так же откатываем посещение узла.


self.visited = [False] * len(self.visited)
for j in range(level):
if self._curr_path[j] != -1:
self.visited[self._curr_path[j]] = True

def TSP(matrix):
tsp = TSPP(matrix)

print("Len:", tsp.path_length)
print("Trace:", '->'.join(str(tsp.path[i]+1) for i in range(tsp.n + 1)))

ex43 = [
[0, 7, 2, 9, 7],
[5, 0, 3, 9, 1],
[4, 8, 0, 5, 3],
[5, 6, 4, 0, 7],
[7, 6, 3, 7, 0]]

TSP(ex43)
# Длина пути: 21
# Путь: 1->2->5->3->4->1

no38 = [
[0, 3, 6, 1, 4],
[9, 0, 9, 3, 8],
[7, 1, 0, 7, 5],
[1, 4, 6, 0, 1],
[2, 5, 1, 8, 0]]

# TSP(no38)
# Длина пути: 13
# Путь: 1->4->5->3->2->1

Решение задач:

Пример 43.
Расстояния между населенными пунктами заданы с помощью матрицы

Ответ в методичке совпал с выходными данными программы.

Задача 38.

Для матрицы

Вывод: В ходе лабораторной работе мы изучили и написали программу решения задачи коммивояжёра
методом ветвей и границ.