Python 排列集合元素,排列集合元素指列出集合中元素的所有排列形式。对于长度为 n
的集合,共有 n!
种排列形式,很多优化问题都使用排列进行暴力求解。
阅读维基百科词条Combinatorial optimization,可知对于大规模问题,穷举所有排列并不适合,但对于小规模问题,使用itertools.permutations()
函数求解很方便。
组合优化的一个经典应用是分配任务: n
位员工要完成 n
项任务,但每个人完成某项任务的成本是不同的。同一项任务,某些员工处理起来成本很高,另一些员工处理起来成本却比较低。我们的任务是恰当地为每位员工分配任务,使得总成本最低。我们的任务是恰当地为每位员工分配任务,使得总成本最低。
不妨创建一个表格来记录每位员工完成每项任务的成本。假设现有6位员工要完成6项任务,则表格中要记录36个成本值,每格中的数字分别表示0到5号员工完成任务A到任务F需要的成本。
要列出集合的所有可能排序不难,但这种方法的扩展性不好。例如10!等于3 628 800,可以用list(permutations(range(10)))
得到全部300多万个序列。
通常我们希望在几秒内就得到问题的答案。如果前面的问题规模翻一倍,到20!,即2 432 902 008 176 640 000组排列时,就会出现扩展性问题。如果生成10!个排列需要0.56秒,那么生成20!组排列值则需要12 000年。
假设已有的成本矩阵包含36个值,显示6位员工完成6个任务的所有可能成本,可如下所示计算最优解:
perms = permutations(range(6))
alternatives = [
(
sum(
cost[x][y] for y, x in enumerate(perm)
),
perm
)
for perm in perms
]
m = min(alternatives)[0]
print([ans for s, ans in alternatives if s == m])
首先生成每名员工执行每个任务的所有排列,保存在perms
中,然后创建每个组合与该组合的总成本的二元组。为了计算某项任务的成本,需要枚举各个组合,形成员工与任务二元组。例如有个组合是(1, 3, 4, 2, 0, 5)
,对应的员工与任务二元组list(enumerate((1, 3, 4, 2, 0, 5)))
的值为[(0, 1), (1, 3), (2, 4), (3, 2), (4, 0), (5, 5)]
。成本矩阵中组合值之和就是这项任务组合的总成本。
最小值对应的就是最优解。很多时候最优解不止一个,上面的算法可以找到所有最优解。表达式min(alternatives)[0]
返回最小值集合的第一个元素。
对于示例规模的问题,这种解法速度很快,但对于更大规模的问题,采用近似算法更适合。