TSP问题—蚁群算法(ACO)

TSP问题—蚁群算法(ACO)

蚁群算法(AG)是一种模拟蚂蚁觅食行为的模拟优化算法,它是由意大利学者Dorigo M等人于1991年首先提出,并首先使用在解决TSP(旅行商问题)上。

蚁群算法的基本思想:

蚁群算法

蚁群算法的基本原理:

  1. 蚂蚁在路径上释放信息素

  2. 如果遇到没有走过的路口,就随机选择一个路径行走。同时,释放与路径长度有关的信息素

  3. 信息素浓度与路径长度成反比。后来的蚂蚁再次碰到该路口时就选择信息素浓度较高的路径

  4. 由于走的蚂蚁多,故最优路径上的浓度越来越大

  5. 最终蚁群找到最优寻食路径

问题建模

  • TSP问题寻找经过所有城市的最短路径,即:路径越短越优,所以信息素浓度应当与路径的长度成反比

基本蚁群的两个过程

  1. 状态转移

  2. 信息素更新

状态转移

城市被选择的概率由距离和信息素浓度共同决定

概率选择公式

蚂蚁在选择要走的城市的时候,为了避免陷入局部最优解,故不能够只选择信息素浓度大和距离小的路径,在这里可以采用轮盘赌的方法进行选取。

   # 选择下一个城市
    def __choose_next_city(self):
        next_city = -1
        select_city_prob = [0.0 for each in range(city_num)]  # 存储选择每个城市的概率
        total_prob = 0.0

        # 获取去每个城市的概率
        for index in range(city_num):
            if self.open_table_city[index]:  # 如果index城市没有被探索过,就计算选择这个城市的概率;已经探索过的话不可以再重探索这个城市
                try:
                    # 计算概率,与信息浓度成正比,与距离成反比
                    select_city_prob[index] = pow(pheromone_graph[self.current_city][index], ALPHA) * pow((1.0 / distance_graph[self.current_city][index]), BETA)
                    total_prob += select_city_prob[index]
                except ZeroDivisionError as e:
                    print('Ant ID:{ID},current city:{current},target city{target}'.format(ID=self.ID,current=self.current_city,target=index))
                    sys.exit(1)

        # 采用轮盘赌方法选择下一个行走的城市
        if total_prob > 0.0:
            # 产生一个随机概率
            temp_prob = random.uniform(0.0, total_prob)
            for index in range(city_num):
                if self.open_table_city[index]:
                    # 轮次相减
                    temp_prob -= select_city_prob[index]
                    if temp_prob < 0.0:
                        next_city = index
                        break

        # 如果next_city=-1,则没有利用轮盘赌求出要去的城市
        # 通过随机生成的方法生成一个城市
        if next_city == -1:
            next_city = random.randint(0, city_num - 1)
            while ((self.open_table_city[next_city]) == False):  # 如果next_city已经被访问过,则需要重新生成
                next_city = random.randint(0, city_num - 1)

        return next_city
信息素更新

采用局部更新原则,即在所有蚂蚁完成一次转移之后执行信息素浓度的更新,其中 更新后的信息素浓度=没有挥发的信息素+新产生的信息素

image
# 更新信息素
    def __update_pheromone_graph(self):
        temp_pheromone = [[0.0 for i in range(city_num)] for j in range(city_num)]

        for ant in self.ants:
            for i in range(1, city_num):
                start, end = ant.path[i - 1], ant.path[i]
                # 留下的信息素浓度与蚂蚁所走路径总距离成反比
                temp_pheromone[start][end] += Q / ant.total_distance
                temp_pheromone[end][start] = temp_pheromone[start][end]
            #更新尾部到头部的信息浓度
            temp_pheromone[ant.path[city_num-1]][ant.path[0]] += Q/ant.total_distance
            temp_pheromone[ant.path[0]][ant.path[city_num-1]] = temp_pheromone[ant.path[city_num-1]][ant.path[0]]

        # 更新信息素,新的信息素等于信息素增量加上没有挥发掉的信息素
        for i in range(city_num):
            for j in range(city_num):
                pheromone_graph[i][j] = (1 - RHO) * pheromone_graph[i][j] + temp_pheromone[i][j]

效果:


效果图
下面是python实现的源代码
'''
ALPHA:信息启发因子,信息浓度对于路径选择所占的比重

BETA: 期望启发式因子,BETA越大,蚁群就越容易选择局部较短路径,
      这时算法的收敛速度加快;但是随机性却不高,容易得到局部的最优解.

RHO:  信息挥发因子,RHO过小,在各路径下残留的信息素过多,导致无效的路径
      被持续搜索,影响到算法的收敛速率;RHO过大无效的路径虽然可以被排除搜索,
      但是有效地路径也会被放弃搜索,影响到最后的最优值搜索.

'''
import copy
import random
import threading
import tkinter
from functools import reduce
import sys

(ALPHA, BETA, RHO, Q) = (1.0, 2.0, 0.5, 100.0)
# 城市数目,蚁群数目
(city_num, ant_num) = (50, 50)

# 城市对应点的坐标
distance_x = [
    178, 272, 176, 171, 650, 499, 267, 703, 408, 437, 491, 74, 532, 416, 626,
    42, 271, 359, 163, 508, 229, 576, 147, 560, 35, 714, 757, 517, 64, 314,
    675, 690, 391, 628, 87, 240, 705, 699, 258, 428, 614, 36, 360, 482, 666,
    597, 209, 201, 492, 294]
distance_y = [
    170, 395, 198, 151, 242, 556, 57, 401, 305, 421, 267, 105, 525, 381, 244,
    330, 395, 169, 141, 380, 153, 442, 528, 329, 232, 48, 498, 265, 343, 120,
    165, 50, 433, 63, 491, 275, 348, 222, 288, 490, 213, 524, 244, 114, 104,
    552, 70, 425, 227, 331]

# 城市距离和信息素,采用二维数组的形式存储
# 初始化城市距离为0,信息素浓度为1
distance_graph = [[0.0 for col in range(city_num)] for raw in range(city_num)]
pheromone_graph = [[1.0 for col in range(city_num)] for raw in range(city_num)]


class Ant(object):
    # 初始化
    def __init__(self, ID):
        self.ID = ID
        self.__clean_data()  # 初始化出生点

    # 初始化数据
    def __clean_data(self):
        self.path = []  # 当前蚂蚁的行走路径
        self.total_distance = 0.0  # 当前蚂蚁行走的总长度
        self.move_count = 0  # 当前蚂蚁的行走次数
        self.current_city = -1  # 当前蚂蚁的所在城市
        self.open_table_city = [True for each in range(city_num)]  # 探索城市的状态,True表示未被探索果,False表示已经被探索过

        city_index = random.randint(0, city_num - 1)  # 随机初始生成点
        self.current_city = city_index
        self.path.append(city_index)
        self.open_table_city[city_index] = False
        self.move_count = 1

    # 选择下一个城市
    def __choose_next_city(self):
        next_city = -1
        select_city_prob = [0.0 for each in range(city_num)]  # 存储选择每个城市的概率
        total_prob = 0.0

        # 获取去每个城市的概率
        for index in range(city_num):
            if self.open_table_city[index]:  # 如果index城市没有被探索过,就计算选择这个城市的概率;已经探索过的话不可以再重探索这个城市
                try:
                    # 计算概率,与信息浓度成正比,与距离成反比
                    select_city_prob[index] = pow(pheromone_graph[self.current_city][index], ALPHA) * pow((1.0 / distance_graph[self.current_city][index]), BETA)
                    total_prob += select_city_prob[index]
                except ZeroDivisionError as e:
                    print('Ant ID:{ID},current city:{current},target city{target}'.format(ID=self.ID,current=self.current_city,target=index))
                    sys.exit(1)

        # 采用轮盘赌方法选择下一个行走的城市
        if total_prob > 0.0:
            # 产生一个随机概率
            temp_prob = random.uniform(0.0, total_prob)
            for index in range(city_num):
                if self.open_table_city[index]:
                    # 轮次相减
                    temp_prob -= select_city_prob[index]
                    if temp_prob < 0.0:
                        next_city = index
                        break

        # 如果next_city=-1,则没有利用轮盘赌求出要去的城市
        # 通过随机生成的方法生成一个城市
        if next_city == -1:
            next_city = random.randint(0, city_num - 1)
            while ((self.open_table_city[next_city]) == False):  # 如果next_city已经被访问过,则需要重新生成
                next_city = random.randint(0, city_num - 1)

        return next_city

    # 计算路径总距离
    def __cal_total_distance(self):
        temp_distance = 0.0

        for i in range(1, city_num):
            end = self.path[i]
            start = self.path[i - 1]
            temp_distance += distance_graph[start][end]

        # 回路
        start = city_num-1
        end = 0
        temp_distance += distance_graph[start][end]
        self.total_distance = temp_distance

    # 移动操作
    def __move(self, next_city):
        self.path.append(next_city)
        self.open_table_city[next_city] = False
        self.total_distance += distance_graph[self.current_city][next_city]
        self.current_city = next_city
        self.move_count += 1

    # 搜索路径
    def search_path(self):
        # 初始化数据
        self.__clean_data()

        # 搜索路径,遍历完所有的城市为止
        while self.move_count < city_num:
            next_city = self.__choose_next_city()
            # 移动
            self.__move(next_city)

        # 计算路径总长度
        self.__cal_total_distance()


class TSP(object):
    # 初始化
    def __init__(self, root, width=800, height=600, n=city_num):

        # 创建画布
        self.root = root
        self.width = width
        self.height = height

        # 城市数目初始化为city_num
        self.city_num = city_num

        self.canvas = tkinter.Canvas(root,
                                     width=self.width,
                                     height=self.height,
                                     bg='#EBEBEB',
                                     xscrollincrement=1,
                                     yscrollincrement=1)

        self.canvas.pack(expand=tkinter.YES, fill=tkinter.BOTH)
        self.title("TSP蚁群算法(n:初始化,e:开始搜索,s:停止搜索,q:退出程序)")
        self.__r = 5
        self.__lock = threading.RLock()  # 线程锁
        self.bindEvents()
        self.new()

        # 计算城市之间的距离
        for i in range(city_num):
            for j in range(city_num):
                temp_distance = pow(distance_x[i] - distance_x[j], 2) + pow(distance_y[i] - distance_y[j], 2)
                temp_distance = pow(temp_distance, 0.5)
                distance_graph[i][j] = float(int(temp_distance + 0.5))

        self.ants = [Ant(ID) for ID in range(ant_num)]  # 初始蚁群
        self.best = Ant(-1)  # 初始最优解
        self.best.total_distance = 1 << 31  # 初始最大距离
        self.iter = 1  # 初始化迭代次数

    # 更改标题
    def title(self, s):
        self.root.title(s)

    # 初始化
    def new(self, evt=None):

        # 停止线程
        self.__lock.acquire()
        self.__running = False
        self.__lock.release()

        self.clear()  # 清除信息
        self.nodes = []  # 节点坐标
        self.nodes2 = []  # 节点对象

        # 初始化城市节点
        for i in range(len(distance_x)):
            # 在画布上画出初始坐标
            x = distance_x[i]
            y = distance_y[i]
            self.nodes.append((x, y))
            # 生成节点椭圆,半径为self.__r
            node = self.canvas.create_oval(
                x - self.__r,
                y - self.__r,
                x + self.__r,
                y + self.__r,
                fill="#ff0000",  # 填充白色
                outline="#000000",  # 轮框白色
                tags="node")
            self.nodes2.append(node)

            # 显示坐标
            self.canvas.create_text(x,
                                    y - 10,
                                    text='(' + str(x) + ',' + str(y) + ')',
                                    fill='black')

        # 初始化城市之间的信息素
        for i in range(city_num):
            for j in range(city_num):
                pheromone_graph[i][j] = 1.0
        self.best = Ant(-1)  # 初始最优解
        self.best.total_distance = 1 << 31  # 初始最大距离
        self.iter = 1  # 初始化迭代次数

    # 将节点按照order顺序连线
    def line(self, order):
        # 删除原线
        self.canvas.delete("line")

        def line2(i1, i2):
            p1, p2 = self.nodes[i1], self.nodes[i2]
            self.canvas.create_line(p1, p2, fill="#000000", tags="line")
            return i2

        # order[-1]初始点
        reduce(line2, order, order[-1])


    # 清除画布
    def clear(self):
        for item in self.canvas.find_all():
            self.canvas.delete(item)

    # 退出程序
    def quit(self, evt=None):
        self.__lock.acquire()
        self.__running = False
        self.__lock.release()
        self.root.destroy()
        print("\n程序已经退出...")
        sys.exit()

    # 停止搜索
    def stop(self, evt=None):
        self.__lock.acquire()
        self.__running = False
        self.__lock.release()

    # 开始搜索
    def search_path(self, evt=None):
        # 开启线程
        self.__lock.acquire()
        self.__running = True
        self.__lock.release()

        while self.__running:
            # 遍历每一只蚂蚁
            for ant in self.ants:
                # 搜索每一条路径
                ant.search_path()
                # 判断是否是最优解
                if ant.total_distance < self.best.total_distance:
                    self.best =  copy.deepcopy(ant)
            # 更新信息素
            self.__update_pheromone_graph()
            print("迭代次数:", self.iter, "最佳路径总距离", int(self.best.total_distance))
            # 连线
            self.line(self.best.path)
            # 设置标题
            self.title("TSP蚁群算法(n:初始化,e:开始搜索,s:停止搜索,q:退出程序),迭代次数:%d,总距离%d" % (self.iter,int(self.best.total_distance)))
            # 更新画布
            self.canvas.update()
            self.iter += 1

        # 更新信息素

    def __update_pheromone_graph(self):
        temp_pheromone = [[0.0 for i in range(city_num)] for j in range(city_num)]

        for ant in self.ants:
            for i in range(1, city_num):
                start, end = ant.path[i - 1], ant.path[i]
                # 留下的信息素浓度与蚂蚁所走路径总距离成反比
                temp_pheromone[start][end] += Q / ant.total_distance
                temp_pheromone[end][start] = temp_pheromone[start][end]
            #更新尾部到头部的信息浓度
            temp_pheromone[ant.path[city_num-1]][ant.path[0]] += Q/ant.total_distance
            temp_pheromone[ant.path[0]][ant.path[city_num-1]] = temp_pheromone[ant.path[city_num-1]][ant.path[0]]

        # 更新信息素,新的信息素等于信息素增量加上没有挥发掉的信息素
        for i in range(city_num):
            for j in range(city_num):
                pheromone_graph[i][j] = (1 - RHO) * pheromone_graph[i][j] + temp_pheromone[i][j]

        # 按键响应

    def bindEvents(self):

        self.root.bind("q", self.quit)  # 退出程序
        self.root.bind("e", self.search_path)  # 开始搜索
        self.root.bind("s", self.stop)  # 停止搜索
        self.root.bind("n", self.new)  # 初始化

        # 主循环

    def mainloop(self):
        self.root.mainloop()


if __name__ == '__main__':
    TSP(tkinter.Tk()).mainloop()

参考文献:蚁群算法原理及其实现(python)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,427评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,551评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,747评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,939评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,955评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,737评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,448评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,352评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,834评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,992评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,133评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,815评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,477评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,022评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,147评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,398评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,077评论 2 355

推荐阅读更多精彩内容

  • 蚂蚁几乎没有视力,但他们却能够在黑暗的世界中找到食物,而且能够找到一条从洞穴到食物的最短路径。它们是如何做到的呢?...
    大闲人柴毛毛阅读 5,232评论 2 9
  • 姓名:彭帅 学号:17021210850 【嵌牛导读】:蚁群算法(ACO)是受自然界中蚂蚁搜索食物行为的启发,是一...
    重露成涓滴阅读 38,598评论 0 8
  • 前言 最近初学蚁群算法,用eil51.tsp数据集做训练。从一开始的44x,经过各种优化后,终于基本稳定在最优解4...
    yanzf阅读 1,920评论 0 3
  • 一、录制命令 1.1、获取支持的设备 要录制屏幕,首先要知道当前笔记本支持的输入设备。 通过 ffmpeg -de...
    未见哥哥阅读 17,683评论 1 8
  • 渐行,行在起雾的黄昏 矗立极目,却愈发模糊, 雾带来了水气,凝结, 濛濛的水汽,贴在脸上, 渐渐的变凉,明白了凉意...
    半路书生阅读 104评论 0 0