自适应大邻域算法(ALNS)求解TSP问题,golang实现

完整代码:https://github.com/huyiqiu/ALNS

1. TSP问题

旅行商问题(Traveling Salesman Problem, TSP)是管理科学和运筹学中一个著名的组合优化问题。这个问题问了这样一个问题:给定一组城市和每对城市之间的距离,访问每个城市一次并返回起始城市的最短路径是什么。

2. 大邻域搜索算法

大邻域搜索算法(Large Neighborhood Search , LNS)是一种元启发式优化算法,LNS算法从一个初始可行解开始,生成一个大邻域的可行解集,这些解与当前解决方案的结构相似,但潜在的差异很大。然后,该算法评估邻域内每个解的质量,并选择最优解作为新的当前解。重复这个过程,直到没有进一步的改进或满足停止标准。LNS与传统邻域搜索算法的主要区别在于邻域的大小。在LNS中,邻域比传统邻域搜索算法中使用的邻域要大得多,来搜索更广泛的解决方案,并可能找到更好的解决方案。较大的邻域搜索有助于跳出局部最优,尽可能接近全局最优解。容易陷入局部最优也是传统邻域搜索算法的主要缺点。

LNS的具体步骤可以根据要解决的问题和算法的具体实现而有所不同。然而,大邻域搜索算法所涉及的步骤概要如下:
步骤1:初始化可行解,初始解通常使用随机生成,也可以使用其他优化算法生成。
步骤2:生成大邻域,生成大邻域的方法通常为“破坏和修复”,破坏步骤可能涉及从当前解决方案中移除随机的客户子集,修复步骤可能涉及以不同的顺序将被移除的客户重新插入到解决方案中。
步骤3:从邻域中选择最优解决方案作为新的当前解。
步骤4:重复步骤2和步骤3直到当前解不再改进或达到预设的算法终止条件。
步骤5:当前解即为LNS算法求得的最终解。
具体步骤和每个步骤如何实现的细节会根据待解决的问题和算法的具体实现而有所不同。

3. 自适应大邻域算法(ALNS)

自适应大邻域算法就是在选择【破坏】和【修复】算子时,假如自适应选择的策略,总的来说就是给算子加入一个打分的环节。每一次迭代都通过轮盘赌选择一个算子进行操作。

4. 代码

4.1 导入tsp文件

NAME: china34
TYPE: TSP
COMMENT: 34 locations in China
DIMENSION: 34
EDGE_WEIGHT_TYPE: WGS
NODE_COORD_SECTION
1 101.74 36.56
2 103.73 36.03
3 106.27 38.47
4 108.95 34.27
5 113.65 34.76
6 117.00 36.65
7 114.48 38.03
8 112.53 37.87
9 111.65 40.82
10 116.41 39.90
11 117.20 39.08
12 123.38 41.80
13 125.35 43.88
14 126.63 45.75
15 121.47 31.23
16 120.19 30.26
17 118.78 32.04
18 117.27 31.86
19 114.31 30.52
20 113.00 28.21
21 115.89 28.68
22 119.30 26.08
23 121.30 25.03
24 114.17 22.32
25 113.55 22.20
26 113.23 23.16
27 110.35 20.02
28 108.33 22.84
29 106.71 26.57
30 106.55 29.56
31 104.06 30.67
32 102.73 25.04
33 91.11 29.97
34 87.68 43.77
EOF

4.2 破坏算子

4.2.1随机破坏

// 从路径中删除一些点
func RandomDestroy(path []int, cnt int) ([]int, []int) {
	tmpPath := make([]int, 0)
	destroyed := make([]int, 0)
	// 生成随机不重复的cnt个点
	for i := 0; i < cnt; i ++ {
		for {
			removeNode := common.RandInt(0, len(path) - 1)
			if common.NotIn(destroyed, removeNode) {
				destroyed = append(destroyed, removeNode)
				break
			}
		}	
	}
	for _, node := range path {
		if common.NotIn(destroyed, node) {
			tmpPath = append(tmpPath, node)
		}
	}
	return tmpPath, destroyed
}

4.2.2贪心破坏

func GreedyDestroy(path []int, cnt int) ([]int, []int) {
	tmpPath := make([]int, 0)
	destroyed := make([]int, 0)
	savingMap := make(map[int]float64)
	// 计算每个节点的节约值
	for _, node := range path {
		pathWithoutNode := PathWithoutNode(node, path)
		saving := common.CalcTSP(path) - common.CalcTSP(pathWithoutNode)
		savingMap[node] = saving
	}
	// 计算节约值最大的cnt个节点
	for i := 0; i < cnt; i ++ {
		maxSaving := 0.0
		var tobeDestroyed int
		for node, saving := range savingMap {
			if saving > maxSaving {
				maxSaving = saving
				tobeDestroyed = node
			}
		}
		destroyed = append(destroyed, tobeDestroyed)
		delete(savingMap, tobeDestroyed)
	}
	// 得到剩余的路径
	for _, node := range path {
		if common.NotIn(destroyed, node) {
			tmpPath = append(tmpPath, node)
		}
	}
	return tmpPath, destroyed
}

4.3修复算子

4.3.1 随机修复

// 将被破坏的点随机插入路径
func RandomRepair(path []int, destroyed []int) []int {
	afterRepair := path
	for _, node := range destroyed {
		afterRepair = RandomInsert(node, afterRepair)
	}
	return afterRepair
}
贪心修复
func GreedyRepair(path []int, destroyed []int) []int {
	afterRepair := path
	for _, node := range destroyed {
		afterRepair = GreedyInsert(node, afterRepair)
	}
	return afterRepair
}

4.4 打分

4.4.1 模拟退火接受准则

func (sa *SA) Accept(diff float64) bool {
	probability := math.Exp(-diff / sa.NowTemperature)
	return common.RandDecimal() < probability
}

4.4.2 打分

		// update score
		if newValue <= alns.NowValue {
			alns.NowPath = newPath
			alns.NowValue = newValue
			if newValue <= alns.HistoricallyBest {
				alns.HistoricallyBest = newValue
				alns.BestPath = newPath
				alns.DestroyScoreBoard[destroyFuncName] = 1.5
				alns.RepairScoreBoard[repairFuncName] = 1.5
			} else {
				alns.DestroyScoreBoard[destroyFuncName] = 1.2
				alns.RepairScoreBoard[repairFuncName] = 1.2
			}
		} else {
			if alns.Sa.Accept(newValue - alns.NowValue) {
				alns.NowPath = newPath
				alns.NowValue = newValue
				alns.DestroyScoreBoard[destroyFuncName] = 0.8
				alns.RepairScoreBoard[repairFuncName] = 0.8
			} else {
				alns.DestroyScoreBoard[destroyFuncName] = 0.5
				alns.RepairScoreBoard[repairFuncName] = 0.5
			}
		}

4.4.3 计算权重

// 计算权重:ρ = λρ + (1 - λ)Ψ
func (alns *ALNS) CalcWeight() {
	lambda := constant.WeightCoefficient
	// 计算破坏算子权重
	for k := range DestroyMap {
		alns.DestroyWeights[k] = lambda*alns.DestroyWeights[k] + (1-lambda)*alns.DestroyScoreBoard[k]
	}
	// 计算修复算子权重
	for k := range RepairMap {
		alns.RepairWeights[k] = lambda*alns.RepairWeights[k] + (1-lambda)*alns.RepairScoreBoard[k]
	}
}

4.5 主循环逻辑

func (alns *ALNS) Run() {

	// 初始化路径
	alns.InitPath(constant.InitSolutionByGreedy)
	for i := 1; i <= alns.SettingIteration; i++ {
		// 轮盘赌选择算子
		destroyFuncName := common.RouletteSelect(alns.DestroyWeights)
		repairFuncName := common.RouletteSelect(alns.RepairWeights)
		destroy, repair := DestroyMap[destroyFuncName], RepairMap[repairFuncName]
		tmpPath, destroyed := destroy.Func(alns.NowPath, alns.Cut)
		newPath := repair.Func(tmpPath, destroyed)
		newValue := common.CalcTSP(newPath)
		alns.OperatorUsageTimes[destroyFuncName] ++
		alns.OperatorUsageTimes[repairFuncName] ++

		// 打分
		if newValue <= alns.NowValue {
			alns.NowPath = newPath
			alns.NowValue = newValue
			if newValue <= alns.HistoricallyBest {
				alns.HistoricallyBest = newValue
				alns.BestPath = newPath
				alns.DestroyScoreBoard[destroyFuncName] = 1.5
				alns.RepairScoreBoard[repairFuncName] = 1.5
			} else {
				alns.DestroyScoreBoard[destroyFuncName] = 1.2
				alns.RepairScoreBoard[repairFuncName] = 1.2
			}
		} else {
			if alns.Sa.Accept(newValue - alns.NowValue) {
				alns.NowPath = newPath
				alns.NowValue = newValue
				alns.DestroyScoreBoard[destroyFuncName] = 0.8
				alns.RepairScoreBoard[repairFuncName] = 0.8
			} else {
				alns.DestroyScoreBoard[destroyFuncName] = 0.5
				alns.RepairScoreBoard[repairFuncName] = 0.5
			}
		}

		// 计算权重
		alns.CalcWeight()

		// update temperature
		alns.Sa.NowTemperature *= alns.Sa.CoolingRate
		// // print middle data
		// fmt.Println("当前迭代次数:", i, " 历史最优解:", alns.HistoricallyBest, " 当前解:", alns.NowValue)
	}
}

4.6 算法效果

运行效果受实验参数影响 对于china34这个算例,迭代1000次仅需120ms左右
对于xqf131这个算例,迭代1000次仅需2s左右

  • 12
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
自适应邻域算法(Adaptive Large Neighborhood Search,ALNS)是一种启发式算法,用于解决组合优化问题。它通过动态调整邻域结构,以达到更好的搜索效果。 以下是一个用Python实现ALNS算法的示例: ```python import random class Solution: def __init__(self, n): self.n = n self.seq = list(range(1, n+1)) random.shuffle(self.seq) self.cost = self.evaluate() def evaluate(self): # 计算当前解的成本 cost = 0 for i in range(self.n-1): for j in range(i+1, self.n): if abs(i-j) == abs(self.seq[i]-self.seq[j]): cost += 1 return cost def get_neighbor(self): # 生成一个随机邻居 i, j = random.sample(range(self.n), 2) seq = self.seq.copy() seq[i], seq[j] = seq[j], seq[i] return Solution.from_seq(seq) def apply_move(self, move): # 应用一个移动操作 i, j = move self.seq[i], self.seq[j] = self.seq[j], self.seq[i] self.cost = self.evaluate() @classmethod def from_seq(cls, seq): # 从序列创建一个解 instance = cls(len(seq)) instance.seq = seq instance.cost = instance.evaluate() return instance class ALNS: def __init__(self, problem, max_iter=1000, p_init=0.9, p_decay=0.99, max_tabu_size=10): self.problem = problem self.max_iter = max_iter self.p_init = p_init self.p_decay = p_decay self.max_tabu_size = max_tabu_size self.tabu_list = [] def run(self): curr = self.problem best = curr p = self.p_init for i in range(self.max_iter): neighbor = curr.get_neighbor() if neighbor.cost < curr.cost or random.random() < p: curr = neighbor if curr.cost < best.cost: best = curr self.update_tabu_list(curr, neighbor) p *= self.p_decay return best def update_tabu_list(self, curr, neighbor): # 更新禁忌表 move = self.get_move(curr, neighbor) if move in self.tabu_list: self.tabu_list.remove(move) self.tabu_list.append(move) if len(self.tabu_list) > self.max_tabu_size: self.tabu_list.pop(0) def get_move(self, curr, neighbor): # 获取当前解到邻居解的移动操作 for i in range(curr.n): if curr.seq[i] != neighbor.seq[i]: for j in range(i+1, curr.n): if curr.seq[j] != neighbor.seq[j] and (i, j) not in self.tabu_list: return (i, j) ``` 在这个示例中,我们定义了一个`Solution`类来表示一个解。`Solution`类有一个`seq`属性表示解的序列,一个`cost`属性表示解的成本。`Solution`类还有一个`evaluate`方法来计算解的成本,一个`get_neighbor`方法来生成一个随机邻居,一个`apply_move`方法来将一个移动操作应用到当前解上。`Solution`类还有一个`from_seq`类方法来从序列创建一个解。 我们还定义了一个`ALNS`类来实现ALNS算法。`ALNS`类有一个`run`方法来运行算法,一个`update_tabu_list`方法来更新禁忌表,一个`get_move`方法来获取当前解到邻居解的移动操作。 在`run`方法中,我们首先初始化当前解和最佳解为问题的初始解。然后在循环中,我们生成一个随机邻居,并根据概率和成本比较决定是否接受该邻居。如果接受该邻居,则更新当前解和禁忌表,并更新最佳解。最后,我们通过逐步减小概率`p`来控制算法的收敛性。 这个示例实现了一个简单的N皇后问题,但可以通过修改`Solution`类和`ALNS`类来适应其他组合优化问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值