PSO解決TSP問題(粒子群演算法解決旅行商問題)--python實現
歡迎私戳關注這位大神!
有任何問題歡迎私戳我->給我寫信
首先來看一下什麼是TSP:
The travelling salesman problem (TSP) asks the following question: "Given a list of cities and the distances between each pair of cities, what is the shortest possible route that visits each city and returns to the origin city?" It is an NP-hard problem in
通俗來講就是,給定n個城市,n個城市之間兩兩連通,求一條能從一個城市A出發,訪問完所有n個城市,並最終回到該城市A的路線,使得該路線所耗費的成本最小,在本例中即路程最小(也可以自定義兩兩城市之間的耗費權重,實現更復雜的分析)。
下面對該問題舉個例項,進行實現。
- 假設有9個城市,以0~8標記;
- 城市間均兩兩通行;
- 城市位置分別是(30, 5), (40, 10), (40, 20), (29, 25), (19, 25), (9, 19), (9, 9), (20, 5),(25,15);
路徑表達為城市序號的組合,如012345678等。
解決演算法的核心是路徑序號的交換,將序號的順序作為該粒子的速度。
可以看到城市分佈是這樣子的。
然後直接上程式碼吧,
首先匯入所用的python包
from operator import attrgetter
import random, sys, time, copy
其次定義Graphic類和路徑生成類,在graphic類中可以方便的對城市及各城市之間權重進行表示
# class that represents a graph class Graph: def __init__(self, amount_vertices): self.edges = {} # dictionary of edges self.vertices = set() # set of vertices self.amount_vertices = amount_vertices # amount of vertices # adds a edge linking "src" in "dest" with a "cost" def addEdge(self, src, dest, cost = 0): # checks if the edge already exists if not self.existsEdge(src, dest): self.edges[(src, dest)] = cost self.vertices.add(src) self.vertices.add(dest) # checks if exists a edge linking "src" in "dest" def existsEdge(self, src, dest): return (True if (src, dest) in self.edges else False) # shows all the links of the graph def showGraph(self): print('Showing the graph:\n') for edge in self.edges: print('%d linked in %d with cost %d' % (edge[0], edge[1], self.edges[edge])) # returns total cost of the path def getCostPath(self, path): total_cost = 0 for i in range(self.amount_vertices - 1): total_cost += self.edges[(path[i], path[i+1])] # add cost of the last edge total_cost += self.edges[(path[self.amount_vertices - 1], path[0])] return total_cost # gets random unique paths - returns a list of lists of paths def getRandomPaths(self, max_size): random_paths, list_vertices = [], list(self.vertices) initial_vertice = random.choice(list_vertices) if initial_vertice not in list_vertices: print('Error: initial vertice %d not exists!' % initial_vertice) sys.exit(1) list_vertices.remove(initial_vertice) list_vertices.insert(0, initial_vertice) for i in range(max_size): list_temp = list_vertices[1:] random.shuffle(list_temp) list_temp.insert(0, initial_vertice) if list_temp not in random_paths: random_paths.append(list_temp) return random_paths
# class that represents a complete graph
class CompleteGraph(Graph):
# generates a complete graph
def generates(self):
for i in range(self.amount_vertices):
for j in range(self.amount_vertices):
if i != j:
weight = random.randint(1, 10)
self.addEdge(i, j, weight)
然後定義粒子類
# class that represents a particle
class Particle:
def __init__(self, solution, cost):
# current solution
self.solution = solution
# best solution (fitness) it has achieved so far
self.pbest = solution
# set costs
self.cost_current_solution = cost
self.cost_pbest_solution = cost
# velocity of a particle is a sequence of 4-tuple
# (1, 2, 1, 'beta') means SO(1,2), prabability 1 and compares with "beta"
self.velocity = []
# set pbest
def setPBest(self, new_pbest):
self.pbest = new_pbest
# returns the pbest
def getPBest(self):
return self.pbest
# set the new velocity (sequence of swap operators)
def setVelocity(self, new_velocity):
self.velocity = new_velocity
# returns the velocity (sequence of swap operators)
def getVelocity(self):
return self.velocity
# set solution
def setCurrentSolution(self, solution):
self.solution = solution
# gets solution
def getCurrentSolution(self):
return self.solution
# set cost pbest solution
def setCostPBest(self, cost):
self.cost_pbest_solution = cost
# gets cost pbest solution
def getCostPBest(self):
return self.cost_pbest_solution
# set cost current solution
def setCostCurrentSolution(self, cost):
self.cost_current_solution = cost
# gets cost current solution
def getCostCurrentSolution(self):
return self.cost_current_solution
# removes all elements of the list velocity
def clearVelocity(self):
del self.velocity[:]
最後定義一個PSO演算法類
# PSO algorithm
class PSO:
def __init__(self, graph, iterations, size_population, beta=1, alfa=1):
self.graph = graph # the graph
self.iterations = iterations # max of iterations
self.size_population = size_population # size population
self.particles = [] # list of particles
self.beta = beta # the probability that all swap operators in swap sequence (gbest - x(t-1))
self.alfa = alfa # the probability that all swap operators in swap sequence (pbest - x(t-1))
# initialized with a group of random particles (solutions)
solutions = self.graph.getRandomPaths(self.size_population)
# checks if exists any solution
if not solutions:
print('Initial population empty! Try run the algorithm again...')
sys.exit(1)
# creates the particles and initialization of swap sequences in all the particles
for solution in solutions:
# creates a new particle
particle = Particle(solution=solution, cost=graph.getCostPath(solution))
# add the particle
self.particles.append(particle)
# updates "size_population"
self.size_population = len(self.particles)
# set gbest (best particle of the population)
def setGBest(self, new_gbest):
self.gbest = new_gbest
# returns gbest (best particle of the population)
def getGBest(self):
return self.gbest
# shows the info of the particles
def showsParticles(self):
print('Showing particles...\n')
for particle in self.particles:
print('pbest: %s\t|\tcost pbest: %d\t|\tcurrent solution: %s\t|\tcost current solution: %d' \
% (str(particle.getPBest()), particle.getCostPBest(), str(particle.getCurrentSolution()),
particle.getCostCurrentSolution()))
print('')
def run(self):
# for each time step (iteration)
for t in range(self.iterations):
# updates gbest (best particle of the population)
self.gbest = min(self.particles, key=attrgetter('cost_pbest_solution'))
# for each particle in the swarm
for particle in self.particles:
particle.clearVelocity() # cleans the speed of the particle
temp_velocity = []
solution_gbest = copy.copy(self.gbest.getPBest()) # gets solution of the gbest
solution_pbest = particle.getPBest()[:] # copy of the pbest solution
solution_particle = particle.getCurrentSolution()[:] # gets copy of the current solution of the particle
# generates all swap operators to calculate (pbest - x(t-1))
for i in range(self.graph.amount_vertices):
if solution_particle[i] != solution_pbest[i]:
# generates swap operator
swap_operator = (i, solution_pbest.index(solution_particle[i]), self.alfa)
# append swap operator in the list of velocity
temp_velocity.append(swap_operator)
# makes the swap
aux = solution_pbest[swap_operator[0]]
solution_pbest[swap_operator[0]] = solution_pbest[swap_operator[1]]
solution_pbest[swap_operator[1]] = aux
# generates all swap operators to calculate (gbest - x(t-1))
for i in range(self.graph.amount_vertices):
if solution_particle[i] != solution_gbest[i]:
# generates swap operator
swap_operator = (i, solution_gbest.index(solution_particle[i]), self.beta)
# append swap operator in the list of velocity
temp_velocity.append(swap_operator)
# makes the swap
aux = solution_gbest[swap_operator[0]]
solution_gbest[swap_operator[0]] = solution_gbest[swap_operator[1]]
solution_gbest[swap_operator[1]] = aux
# updates velocity
particle.setVelocity(temp_velocity)
# generates new solution for particle
for swap_operator in temp_velocity:
if random.random() <= swap_operator[2]:
# makes the swap
aux = solution_particle[swap_operator[0]]
solution_particle[swap_operator[0]] = solution_particle[swap_operator[1]]
solution_particle[swap_operator[1]] = aux
# updates the current solution
particle.setCurrentSolution(solution_particle)
# gets cost of the current solution
cost_current_solution = self.graph.getCostPath(solution_particle)
# updates the cost of the current solution
particle.setCostCurrentSolution(cost_current_solution)
# checks if current solution is pbest solution
if cost_current_solution < particle.getCostPBest():
particle.setPBest(solution_particle)
particle.setCostPBest(cost_current_solution)
接下來就是解決實際問題的部分啦。
首先輸入城市的列表,迴圈加入各城市間路線的權重。
if __name__ == "__main__":
#輸入城市座標陣列
cities = [(30,5),(40,10),(40,20),(29,25),(19,25),(9,19),(9,9),(20,5),(25,15)]
# 初始化圖
graph = Graph(amount_vertices=len(cities))
#計算距離作為邊的權重
for i in range(0,len(cities)):
for j in range(0,len(cities)):
if i != j:
distance = pow((cities[i][0]-cities[j][0])**2+(cities[i][1]-cities[j][1])**2,0.5)
graph.addEdge(i, j, distance)
#生成PSO,此處分別設定迭代次數,粒子群數量,β值,α值
pso = PSO(graph, iterations=1000, size_population=5000, beta=1, alfa=0.9)
# 執行PSO
pso.run()
#展示粒子
pso.showsParticles()
# 展示全域性最佳粒子
print('gbest: %s | cost: %d\n' % (pso.getGBest().getPBest(), pso.getGBest().getCostPBest()))
結果如下:
可以看到最佳路徑為 0, 8, 7, 6, 5, 4, 3, 2, 1 所需的路程耗費為: 98
路線如下,箭頭由淺到深進行遍歷,解決各TSP問題都是愛你的形狀!!!
完美解決問題!!!
更多GIS演算法分享,請持續關注DomicZhong的部落格!