优化旅行推销员算法(时间旅行者算法)

4

我尝试优化我编写的一个简单的Python算法,该算法大致解决了旅行商问题:

import math
import random
import matplotlib.pyplot as plt
import datetime


#Distance between two point
def distance(point1, point2):
    return math.sqrt((point2[0]-point1[0])**2+(point2[1]-point1[1])**2)

#TSP TimeTraveler Algorithm
def TSP_TimeTraveler(Set_Points):
    print("Solving TSP")

    #For calculating execution time
    time_start = datetime.datetime.now()

    #Copy the set points
    points = Set_Points.copy()
    route = []

    #Take 3 points at random
    route.append(points.pop(random.randint(0,len(points)-1)))
    route.insert(0,points.pop(random.randint(0,len(points)-1)))
    route.insert(1,points.pop(random.randint(0,len(points)-1)))

    #Calulating the initial route length
    Length = distance(route[0],route[1]) + distance(route[1],route[-1]) + distance(route[-1],route[0])

    #Time Traveler Algorithm
    while len(points)>0 :
        print("Points left : ", len(points),'              ', end="\r")

        #Take a random point from the Set
        point = points.pop(random.randint(0,len(points)-1))

        ###############################################################################################################
        #### Finding the closest route segment by calculation all lengths posibilities and finding the minimum one ####
        ###############################################################################################################
        Set_Lengths = []
        for i in range(1,len(route)):
            #Set of Lengths when the point is on each route segment except the last one
            L = Length - distance(route[i-1],route[i]) + distance(route[i-1],point) + distance(point, route[i])
            Set_Lengths.append((i,L))

        #Adding the last length when the point is on the last segement
        L = Length - distance(route[-1],route[0]) + distance(route[-1],point) + distance(point, route[0])
        Set_Lengths.append((0,L))
        ###############################################################################################################
        ###############################################################################################################

        #Sorting the set of lengths
        Set_Lengths.sort(key=lambda k: k[1])

        #Inserting the point on the minimum length segment
        route.insert(Set_Lengths[0][0], point)

        #Updating the new route length
        Length = Set_Lengths[0][1]

    #Connecting the start point with the finish point
    route.append(route[0])

    #For calculating execution time
    time_end = datetime.datetime.now()
    delta = (time_end-time_start).total_seconds()

    print("Points left : ", len(points),' Done              ',)
    print("Execution time : ", delta, "secs")

    return route

#######################
#Testing the Algorithm#
#######################

#Size of the set
size = 2520

#Generating a set of random 2D points
points = []
for i in range(size):
    points.append([random.uniform(0, 100),random.uniform(0, 100)])

#Solve TSP
route = TSP_TimeTraveler(points)

#Plot the solution
plt.scatter(*zip(*points),s=5)
plt.plot(*zip(*route))
plt.axis('scaled')
plt.show()

该算法分为三个简单步骤:

1/ 首先,我从点集中随机选择3个点,并将它们连接成初始路径。

2/ 然后,每一步,我从剩余的点集中随机选择一个点,并尝试找到最接近已有路径的线段并将其连接。

3/ 我重复执行第2步,直到剩余点集为空为止。

这是算法解决120个点集的gif图:TimeTravelerAlgorithm.gif

我将其命名为“时间旅行者”,因为它的操作类似于贪心推销员算法。但不同的是,贪心推销员在现在前往最近的新城市,而贪心推销员则会回到过去,前往他已经访问过的最近城市,然后继续他的正常路线。

时间旅行者从3个城市开始路线,每一步都会在过去添加一个新城市,直到他访问完所有城市并返回家乡。

该算法可以快速地为小规模点集提供合理的解决方案。以下是在一台2.6GHz双核Intel Core i5处理器的Macbook上每组点的执行时间:
  • 大约0.03秒内完成120个点
  • 大约0.23秒内完成360个点
  • 大约10秒内完成2520个点
  • 大约3分钟内完成10000个点
  • 大约5小时内完成100000个点(解决方案地图
由于在某些情况下它会给出次优解,因此该算法远未被优化。并且它完全使用纯Python编写。也许使用NumPy或一些高级库甚至GPU可以加速程序。
我希望您能对其进行审查和帮助优化。我尝试解决可能极大的点集的近似解,而不产生交叉路径。
PS:我的算法和代码是开放的。互联网上的人们,请随意在任何项目或任何研究中使用它。

1
你可以比较距离的平方,避免计算平方根()。 - Severin Pappadeux
1
有一个类似的算法,但我不记得它叫什么。性能优化是使用一种方法,允许您在数据中确定最近的点,其时间复杂度为O(log h)而不是O(h),其中h是当前解决方案中的点数。可能是KD树或其他东西。还要实现2或3-opt以消除交叉。 - Nuclearman
1
如果您还不知道它们,我建议您对“标准”TSP基准测试数据案例运行算法,并查看它们的表现。http://www.math.uwaterloo.ca/tsp/data/index.html(这里是TSPLIB格式定义的PDF http://comopt.ifi.uni-heidelberg.de/software/TSPLIB95/tsp95.pdf) - Nuclearman
1
嗯,可以试试http://www.math.uwaterloo.ca/tsp/world/zitour.html。VLSI更难优化,因为它们在某些情况下可能是等距的(它们基本上是电路板),所以如果您不处理AB与BC距离相等的情况,那么在该点集上可能会遇到问题。建议在其他基准实例上进行测试。看看哪里出了最大的问题。我能看出算法出了什么问题,但16%对于近似值来说并不差。如果您将2-opt提升到3-opt,可能会有所帮助。我会实施并查看是否有所帮助。 - Nuclearman
1
另外,您可能需要考虑添加“删除”城市逻辑来处理“错误添加”。例如:添加X个城市(假设X=2),然后删除X个最昂贵的城市(即AB BC距离最长的城市)。我认为这可能有助于解决我看到的问题。例如,您可以添加2个城市,然后删除最昂贵的一个(总体而言)或添加5个城市然后删除3个。无论如何,始终添加比删除更多的城市。 - Nuclearman
显示剩余9条评论
2个回答

1
感谢评论。我使用对象、集合和链表重新实现了算法。我还从距离函数中删除了平方根。现在代码看起来更加清晰:
import math
import random
import datetime
import matplotlib.pyplot as plt

#Distance between two point
def distance(point1, point2):
    return (point2[0]-point1[0])**2 + (point2[1]-point1[1])**2

#Distance between two point
class Node:
    def __init__(self, dataval=None):
        self.dataval = dataval
        self.nextval = None

class TSP_TimeTraveler():
    def __init__(self, dataval=None):
        self.count = 0
        self.position = None
        self.length = 0

    def get_position():
        return self.position

    def next_city():
        self.position = self.position.nextval
        return self.position

    #adding a city to the current route with Time Traveler Algorithm :
    def add_city(self, point):
        node = Node(point)
        if self.count <=0 :
            self.position = node
        elif self.count == 1 :
            node.nextval = self.position
            self.position.nextval = node
            self.length = 2*distance(self.position.dataval,node.dataval)
        else : 

            #Creating the traveler
            traveler = self.position

            c = traveler.dataval #current position
            n = traveler.nextval.dataval #next position

            #Calculating the length of adding the city to the path
            Min_L = self.length-distance(c,n)+distance(c,node.dataval)+distance(node.dataval,n)
            Min_Node = traveler

            traveler = traveler.nextval

            while traveler != self.position :
                c = traveler.dataval #current position
                n = traveler.nextval.dataval #next position

                #Calculating the length of adding the city to the path
                L = self.length-distance(c,n)+distance(c,node.dataval)+distance(node.dataval,n)

                #Searching the path to the of city with minimum length
                if L < Min_L :
                    Min_L = L
                    Min_Node = traveler

                traveler = traveler.nextval


            #Adding the city to the minimum path
            node.nextval = Min_Node.nextval
            Min_Node.nextval = node
            self.length = Min_L

        #Incrementing the number of city in the route
        self.count = self.count + 1

    #Get the list of the route
    def getRoute(self):
        result = []

        traveler = self.position
        result.append(traveler.dataval)

        traveler = traveler.nextval

        while traveler != self.position :
            result.append(traveler.dataval)
            traveler = traveler.nextval

        result.append(traveler.dataval)

        return result

    def Solve(self, Set_points):
        print("Solving TSP")

        #For calculating execution time
        time_start = datetime.datetime.now()

        #Copy the set points list
        points = Set_points.copy()

        #Transform the list into set
        points = set(tuple(i) for i in points)

        #Add 
        while len(points)>0 :
            print("Points left : ", len(points),'              ', end="\r")
            point = points.pop()
            self.add_city(point)

        result = self.getRoute()

        #For calculating execution time
        time_end = datetime.datetime.now()
        delta = (time_end-time_start).total_seconds()

        print("Points left : ", len(points),' Done              ',)
        print("Execution time : ", delta, "secs")

        return result

#######################
#Testing the Algorithm#
#######################

#Size of the set
size = 120

#Generating a set of random 2D points
points = []
for i in range(size):
    points.append((random.uniform(0, 100),random.uniform(0, 100)))

#Solve TSP
TSP = TSP_TimeTraveler()

route = TSP.Solve(points)

#Plot the solution
plt.scatter(*zip(*points),s=5)
plt.plot(*zip(*route))
plt.axis('scaled')
plt.show()

使用PyPy代替普通的Python,它可以运行得更快:

  • 大约0.03秒内完成120个
  • 大约0.05秒内完成360个
  • 大约0.22秒内完成2520个
  • 大约2秒内完成10,000个
  • 大约7分钟内完成100,000个

之前需要5小时才能解决的100,000个问题,现在只需要7分钟就可以解决。

接下来,我将尝试使用双向链表和KD树实现2-opt算法,以便在没有交叉的情况下解决大型数据集。


0

我通过在每次插入时添加双向链表和2-opt来改进算法:

import math
import random
import datetime
import matplotlib.pyplot as plt

#Distance between two point
def distance(point1, point2):
    return (point2[0]-point1[0])**2 + (point2[1]-point1[1])**2

#Intersection between two segments
def intersects(p1, q1, p2, q2):
    def on_segment(p, q, r):
        if r[0] <= max(p[0], q[0]) and r[0] >= min(p[0], q[0]) and r[1] <= max(p[1], q[1]) and r[1] >= min(p[1], q[1]):
            return True
        return False

    def orientation(p, q, r):
        val = ((q[1] - p[1]) * (r[0] - q[0])) - ((q[0] - p[0]) * (r[1] - q[1]))
        if val == 0 : return 0
        return 1 if val > 0 else -1

    o1 = orientation(p1, q1, p2)
    o2 = orientation(p1, q1, q2)
    o3 = orientation(p2, q2, p1)
    o4 = orientation(p2, q2, q1)

    if o1 != o2 and o3 != o4:
        return True

    if o1 == 0 and on_segment(p1, q1, p2) : return True
    if o2 == 0 and on_segment(p1, q1, q2) : return True
    if o3 == 0 and on_segment(p2, q2, p1) : return True
    if o4 == 0 and on_segment(p2, q2, q1) : return True

    return False

#Distance Double Linked Node
class Node:
    def __init__(self, dataval=None):
        self.dataval = dataval
        self.prevval = None
        self.nextval = None

class TSP_TimeTraveler():
    def __init__(self):
        self.count = 0
        self.position = None
        self.length = 0
        self.traveler = None
        self.travelert_past = None
        self.is_2opt = True

    def get_position():
        return self.position

    def traveler_init(self):
        self.traveler = self.position
        self.travelert_past = self.position.prevval
        return self.traveler

    def traveler_next(self):
        if self.traveler.nextval != self.travelert_past:
            self.travelert_past = self.traveler
            self.traveler = self.traveler.nextval
            return self.traveler, False
        else :
            self.travelert_past = self.traveler
            self.traveler = self.traveler.prevval
            return self.traveler, True 

    #adding a city to the current route with Time Traveler Algorithm :
    def add_city(self, point):
        node = Node(point)
        if self.count <=0 :
            self.position = node
        elif self.count == 1 :
            node.nextval = self.position
            node.prevval = node
            self.position.nextval = node
            self.position.prevval = self.position
            self.length = 2*distance(self.position.dataval,node.dataval)
        elif self.count == 2 :
            node.nextval = self.position.nextval
            node.prevval = self.position
            self.position.nextval.prevval = node
            self.position.nextval = node
            self.length = 2*distance(self.position.dataval,node.dataval)
        else : 

            #Creating the traveler
            traveler = self.traveler_init()

            c = traveler #current position
            prev = False #inverse link

            n, prev = self.traveler_next()

            #Calculating the length of adding the city to the path
            Min_prev = prev
            Min_L = self.length-distance(c.dataval,n.dataval)+distance(c.dataval,node.dataval)+distance(node.dataval,n.dataval)
            Min_Node = c

            traveler = n

            while traveler != self.position :
                c = n #current position

                n, prev = self.traveler_next()

                #Calculating the length of adding the city to the path
                L = self.length-distance(c.dataval,n.dataval)+distance(c.dataval,node.dataval)+distance(node.dataval,n.dataval)

                #Searching the path to the of city with minimum length
                if L < Min_L :
                    Min_prev = prev 
                    Min_L = L
                    Min_Node = c
                traveler = n    

            if Min_prev : 
                Min_Next_Node = Min_Node.prevval
            else :
                Min_Next_Node = Min_Node.nextval

            node.nextval = Min_Next_Node
            node.prevval = Min_Node

            if Min_prev :
                Min_Node.prevval = node
            else :
                Min_Node.nextval = node

            if Min_Next_Node.nextval == Min_Node:
                Min_Next_Node.nextval = node
            else :
                Min_Next_Node.prevval = node
            
            self.length = Min_L
            
            #2-OP
            if self.is_2opt == True :
                self._2opt(Min_Node, node, Min_Next_Node)

        #Incrementing the number of city in the route
        self.count = self.count + 1

    #apply the 2opt to a-b-c
    def _2opt(self, a, b, c):
        traveler = self.traveler_init()

        c1 = a
        c2 = b

        n1 = b
        n2 = c

        c = traveler #current position
        t_prev = False
        n, t_prev = self.traveler_next()

        traveler = n

        while traveler != self.position :

            cross = False

            if (c.dataval != c1.dataval and c.dataval != c2.dataval and n.dataval != c1.dataval and n.dataval != c2.dataval) and intersects(c.dataval, n.dataval, c1.dataval, c2.dataval):
                
                self._2optswap(c,n,c1,c2)
                cross = True
                a = n
                n = c1
                c2 = a
                    
            if (c.dataval != n1.dataval and c.dataval != n2.dataval and n.dataval != n1.dataval and n.dataval != n2.dataval) and intersects(c.dataval, n.dataval, n1.dataval, n2.dataval):
                
                self._2optswap(c,n,n1,n2)
                cross = True
                a = n
                n = n1
                n2 = a

            if cross:
                return

            c = n #current position
            n, t_prev = self.traveler_next()
            traveler = n            


    #swap between the crossed segment a-b and c-d
    def _2optswap(self, a, b, c, d):

        if a.nextval == b :
            a.nextval = c
        else :
            a.prevval = c

        if b.prevval == a :
            b.prevval = d
        else :
            b.nextval = d

        if c.nextval == d :
            c.nextval = a
        else :
            c.prevval = a

        if d.prevval == c :
            d.prevval = b
        else :
            d.nextval = b

        self.length = self.length - distance(a.dataval,b.dataval) - distance(c.dataval,d.dataval) + distance(a.dataval,c.dataval) + distance(b.dataval,d.dataval)


    #Get the list of the route
    def getRoute(self):
        result = []

        traveler  = self.traveler_init()
        result.append(traveler.dataval)

        traveler, prev  = self.traveler_next()

        while traveler != self.position :
            result.append(traveler.dataval)
            traveler, prev = self.traveler_next()

        result.append(traveler.dataval)

        return result

    def Solve(self, Set_points, with_2opt = True):
        print("Solving TSP")

        #For calculating execution time
        time_start = datetime.datetime.now()

        #Copy the set points list
        points = Set_points.copy()

        #Transform the list into set
        points = set(tuple(i) for i in points)

        #Add 
        while len(points)>0 :
            print("Points left : ", len(points),'              ', end="\r")
            point = points.pop()
            self.add_city(point)

        result = self.getRoute()

        #For calculating execution time
        time_end = datetime.datetime.now()
        delta = (time_end-time_start).total_seconds()

        L=0
        for i in range(len(result)-1):
            L = L + math.sqrt((result[i-1][0]-result[i][0])**2 + (result[i-1][1]-result[i][1])**2)

        print("Points left : ", len(points),' Done              ',)
        print("Execution time : ", delta, "secs")
        print("Average time per point : ", 1000*delta/len(Set_points), "msecs")
        print("Length : ", L)

        return result

#######################
#Testing the Algorithm#
#######################

#Size of the set
size = 1000

#Generating a set of random 2D points
points = []
for i in range(size):
    points.append((random.uniform(0, 100),random.uniform(0, 100)))

#Solve TSP
TSP = TSP_TimeTraveler()
route = TSP.Solve(points, with_2opt = True)

plt.scatter(*zip(*route), s=5)
plt.plot(*zip(*route))
plt.axis('scaled')
plt.show()

现在这个解决方案可以快速地得出结果,而且没有交叉路径。

使用 PyPy 可以在 30 分钟内解决 100,000 个点,而且没有交叉路径。

现在我正在实现 KD 树来解决大型数据集。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接