【发布时间】:2021-07-12 23:16:24
【问题描述】:
Leetcode 256:Paint House
我正在尝试使用模拟退火算法解决这个问题。但结果与正确答案相差甚远,这是用DP方法计算出来的。
例如,我做了一个 10x3 成本矩阵如下,正确的最小成本是 50,但大多数情况下模拟退火的结果是 70~90。
简单来说,我构建了一个初始状态(房屋颜色的组合),第一个房子的颜色是红色,第二个是蓝色,第三个是绿色,第四个是红色,以此类推在。让我们假设这种颜色组合的成本是最低成本。
在退火的过程中,我首先随机选择了一个房子,改变了它的颜色,并调整了这所房子的两个邻居的颜色,以满足“没有两个相邻的房子颜色相同”的前提。然后我重新计算了新颜色组合的成本。如果新成本小于最小值,则新成本将变为最小值。否则,我接受了更昂贵的组合当且仅当rand() <= acceptanceProbability。我一遍又一遍地重复这些步骤,直到温度 T 接近 0。
SA 算法适合这个问题吗?如果答案是肯定的并且我的代码中没有错误,我应该调整什么,颜色的初始组合,冷却速度或其他任何东西,以使使用 SA 计算的成本接近正确的。
package main
import (
"fmt"
"math"
"math/rand"
"time"
)
const (
RED = iota // 0
BLUE // 1
GREEN // 2
)
var randSrc = rand.NewSource(time.Now().UnixNano())
var randGen = rand.New(randSrc)
func main() {
var costs [][]int
costs = append(costs, []int{10, 4, 11})
costs = append(costs, []int{14, 4, 6})
costs = append(costs, []int{ 2, 6, 2})
costs = append(costs, []int{10, 5, 2})
costs = append(costs, []int{16, 11, 2})
costs = append(costs, []int{ 6, 16, 6})
costs = append(costs, []int{ 6, 7, 2})
costs = append(costs, []int{ 8, 15, 9})
costs = append(costs, []int{ 9, 12, 11})
costs = append(costs, []int{13, 15, 3})
fmt.Println(minCost(costs))
}
func minCost(costs [][]int) int {
// STEP1. init start status(a list of colors)
rows := len(costs)
colors := make([]int, rows, rows)
for r := 0; r < rows; r++ {
// suppose 1st house is RED, 2nd is BLUE, 3rd is GREEN, 4th is RED, ...
colors[r] = (RED + r) % 3
}
// calculate initial cost
cost := calcCosts(costs, colors)
// STEP2. simulated annealing
minCost := cost
bestColors := colors
for T := 5000000.0; T > 0.001; T = T * 0.99 {
nextColors := next(bestColors)
nextCost := calcCosts(costs, nextColors)
if nextCost < minCost {
minCost = nextCost
bestColors = nextColors
} else if randGen.Float64() <= acceptanceProbability(cost, nextCost, T) {
minCost = nextCost
bestColors = nextColors
}
}
return minCost
}
// generates next candidate, another combination of colors
func next(colors []int) []int {
rows := len(colors)
nextColors := make([]int, rows, rows)
copy(nextColors, colors)
// chose 1 row randomly, change this row's color
rr := randGen.Intn(rows) // rr in [0, rows)
oc := nextColors[rr] // old color of this row
nc := randGen.Intn(3) // random new color for this row
for oc == nc {
nc = randGen.Intn(3)
}
nextColors[rr] = nc
// Adjust up-next and down-next's colors
if rr-1 >= 0 {
// up-next exists
neighbourColor := nextColors[rr-1]
for neighbourColor == nc {
neighbourColor = randGen.Intn(3) // [0,n)
}
nextColors[rr-1] = neighbourColor
}
if rr+1 <= rows-1 {
// down-next exists
neighbourColor := nextColors[rr+1]
for neighbourColor == nc {
neighbourColor = randGen.Intn(3) // [0,n)
}
nextColors[rr+1] = neighbourColor
}
return nextColors
}
func calcCosts(costs [][]int, colors []int) int {
cost := 0
for r, row := range costs {
cost += row[colors[r]]
}
return cost
}
func acceptanceProbability(cost int, nextCost int, T float64) float64 {
if cost > nextCost {
return 1.0
}
p := math.Exp(float64(cost-nextCost) / T) // cost - next <= 0
return p
}
【问题讨论】:
-
这种情况似乎使问题不适用于模拟退火:“您必须对所有房屋进行涂漆,以使相邻的两个房屋的颜色都不相同。”
-
这不是真的。 SA 有两种处理条件的方法: (1) 只提出满足所有条件的状态。在这种情况下最好的选择。 (2) 当状态无效时增加惩罚。对于这种情况,(1)可以正常工作。
-
@user3184950 感谢您,我在代码中发现了 2 个错误。我只更改了任何选定房屋的“直接邻居”,而不更改其邻居的邻居(间接邻居)。在大多数情况下,它会导致“无效状态”,除非我重新更改颜色。我应该使用
minCost作为acceptanceProbability()的第一个参数,而不是初始和不可变成本cost。我现在可以得到正确答案了!
标签: algorithm traveling-salesman simulated-annealing