目录
说明
permutations
一、函数的使用
二、算法
思考
优化
思路
三、完整代码
itertools 被称为「宝石(gem)」和「几乎是最酷的东西 ],它是用来操作迭代器的一个模块,功能非常强大。而今天的主角是组合迭代器这类操作里面的permutations函数。
什么是组合迭代器?组合迭代器(Combinatoric Iterators)——组合操作包括排列,笛卡儿积,或者一些离散元素的选择,组合迭代器就是产生这样序列的迭代器。
permutations函数返回的是可迭代元素中的一个排列组合(全排列)。
使用该方法要先导入itertools模块
import itertools
itertools模块返回大多都是可迭代序列,如果直接输出的话..
print(itertools.permutations([1, 2, 3]))
会返回它所在地址
因此可遍历返回的迭代对象
for i in itertools.permutations([1, 2, 3]):
print(i, end=" ")
结果:
(1, 2, 3) (1, 3, 2) (2, 1, 3) (2, 3, 1) (3, 1, 2) (3, 2, 1)
另外还能加个参数,表示返回元素的长度
for i in itertools.permutations([1, 2, 3], 2):
print(i, end=" ")
结果:
(1, 2) (1, 3) (2, 1) (2, 3) (3, 1) (3, 2)
通过输出它的类型发现
for i in itertools.permutations([1, 2, 3], 2):
print(i, end=" ")
print(type(i))
他返回的迭代对象都是元组。
(1, 2)
(1, 3)
(2, 1)
(2, 3)
(3, 1)
(3, 2)
实现算法前我们思考两个问题。
1.permutations函数返回的迭代序列有什么特点?
通过上面的例子我们知道,它返回的迭代序列是按接收进来序列元素的顺序进行迭代的。
如果要让大家写出一个数列的全部排列,那么你们会怎么写呢?我先说我,我是这样数的。以[1,2,3,4]为例,先选择第一个元素(1),然后选择(1)的下一个元素(2),然后选(2)的下个元素(3)··· 选下个元素(4),选了(4)之后,因为已经选了4个元素,长度与传入的序列([1, 2, 3, 4])等长了,所以得到第一个元组(1, 2, 3, 4)。继续,回到上一步(3),(3)因为下一个元素只有(4),而(4)已经选过了,所以就不能再选了。然后再回一步,回到(2),(2)的下一个元素是(3),而(3)已经选过了,所以就选(4),选了(4)之后,现在还剩(3)没选,所以就选(3),选了之后得到第二个元组(1, 2, 4, 3),(2)后面数完了之后就从(3)开始数(此时的序列为(1, 3))···省略···然后遍历到以(4)作为第一个元素···省略···最后得出最终结果。
我想,应该不少人数这用数列都是按这种顺序来数的吧。反正我高中有时候遇到简单一点的排列组合的填空选择题,懒得用A,C那些计算就会用类似这种但低级一点的方法去数。其实这种数法有个专有名词叫深度优先遍历 (Depth First Search) 简称 DFS。
2.permutations函数返回的迭代序列有会重复吗?
通过上面的例子发现,返回的迭代序列没有重复的。但是,当传入的序列里边存在相同元素时,会生成重复的元组。
for i in itertools.permutations([1, 1, 2]):
print(i, end=" ")
的结果是
(1, 1, 2) (1, 2, 1) (1, 1, 2) (1, 2, 1) (2, 1, 1) (2, 1, 1)
可以看到产生了重复的元组(1, 1, 2), (1, 2, 1), (2, 1, 1)
而在实际应用中我们一般不需要重复的元素,因此需要去重。最简单的做法就是使用集合去重,但在时间和空间复杂度上讲,这样不可行。在处理海量数据时,如果先遍历出全部结果再用集合去重的话会产生大量重复分支,大大降低执行效率,严重还可能出现卡顿现象。所以我们需要剪枝,而且是在没有生成的时候将其去除。
permutations函数对存在相同元素的列表或元组使用时会产生重复元素, 我们应使用更高效的方法对返回的元素去重。
剪枝:如果此元素与上一元素相等且上一元素未使用,需要剪枝去除。
注意:gif里面忘说了。需要先对原数组进行排序,否则剪枝功能会失效。
# 自定义Itertools函数
class MyItertools:
# 使用默认参数L用于定义输出长度
def permutations(self, nums, L=-1):
# 当传有长度参数且长度参数符合逻辑时,我们使用传入的参数作为返回长度,否则直接使用列表nums长度作为返回长度
L = len(nums) if L <= 0 or L > len(nums) else L
nums.sort()
# 定义等长标记数组
used = [False for i in nums]
# 用于存储每一个输出结果
result = []
def dfs(res):
# 终止条件
if len(res) == L:
result.append(res)
return
# enumerate函数:同时返回元素的下标和值
for i, val in enumerate(nums):
# 如果此元素使用过了直接跳过
if used[i]:
continue
# 剪枝判断条件
if i > 0 and val == nums[i - 1] and not used[i - 1]:
continue
# 标记使用
used[i] = True
# 选择下一个元素
dfs(res + [val])
# 状态重置
used[i] = False
dfs([])
return result
if __name__ == '__main__':
for i in MyItertools().permutations([1, 2, 1]):
print(i)