一.引言
给定一个正整数 N,输出数列 1,2,3,...,N 的全排列。简单分析一下,N 个正整数,总共满足的可能性为 N!= N * (N-1) * ... * 1。通过遍历每一个位置,分别放置不同的数字即可达到问题要求。
先用一个最 for 的方案理解一下:
nums = [1, 2, 3]
candidates = []
for i in nums:
for j in nums:
for k in nums:
if i != j and i != k and j != k:
candidates.append(str(i) + str(j) + str(k))
第一位固定1,对剩下的两个数字分别固定,以此类推,这种方法适合写少量循环,如果数组元素太多 if 条件写起来比较麻烦。
123
132
213
231
312
321
二.回朔
第一个位置有N中可能,对于每一个可能,固定第一位数字,对后面 N-1 个数字进行全排列,依次推进,直到对最后 1 个数字全排列,因此可以通过递归和回朔解决。
def solveByBackTrack(nums):
def backtrack(position, end):
if position == end:
res.append(nums[:])
return
for index in range(position, end):
nums[index], nums[position] = nums[position], nums[index]
backtrack(position + 1, end)
nums[index], nums[position] = nums[position], nums[index]
res = []
backtrack(0, len(nums))
return list(map(lambda arr: ''.join(list(map(lambda x: str(x), arr))), res))
A.递归与回朔:
backtrack 方法从 position 0 开始,第一个 for 循环将0与其他所有位置交换,相当于固定第一个元素,此时有 N 种可能,因为每一个数字都被换到了第一位,随后对当前数组的剩下 N-1 位置进行 backtrack,道理同上,注意 backtrack 回朔时返回上一个状态。
B.退出条件
position 换到最后一个位置,即最后只能用 position 的元素和 end 的元素调换位置,说明当前只剩一个元素,当前排列完成,退出。
三.深度搜索
第一个位置有N种可能,对于每一个可能,当做一个根节点,后面 N-1 个数字都可以是当前根节点的 node,继续推荐,这 N-1 个 node 每个节点下都有 N-2 个 node,依次往下,因此可以通过DFS 解决。
def solveByDFS(nums):
visit = [True for _ in range(len(nums))]
tmp = nums[:]
def dfs(position):
if position == len(nums):
res.append(tmp[:])
return
for index in range(0, len(nums)):
if visit[index]:
tmp[position] = nums[index]
visit[index] = False
dfs(position + 1)
visit[index] = True
res = []
dfs(0)
return list(map(lambda arr: ''.join(list(map(lambda x: str(x), arr))), res))
A.深度搜索
这里使用了长度为 N 的数组进行状态标记,第一个 for 循环固定 1-N 个数字,访问过后的节点标记为 False,随后 dfs(position + 1) 向后续为 True 的点进行探索,直到 position = len(nums)。
B.退出条件
position 访问至 len(nums) 时,只有最后一个元素为 Ture,将此位置标记为 False,并进入 dfs(position + 1),此时将结果存至 res 并返回。
四.暴力解法
总共有 N! 种可能,构造一个 Set,使用 random.shuffle 对数组进行打散并添加到 Set 中,当Set.size = N! 时候代表全排列全部找到。
def lazySolution(nums):
candidate = set()
nums2str = list(map(lambda x: str(x), nums))
total = reduce(lambda x, y: x * y, nums)
while True:
if len(candidate) >= total:
break
random.shuffle(nums2str)
candidate.add("".join(nums2str))
re = sorted(candidate)
return re
A.退出条件
总共 N! 种可能, Set 可以去重,所以当 Set 的 size 达到全部可能数时,标志全排列完成。
五.测试
def cost(n, epoch):
back_cost = 0
dfs_cost = 0
lazy_cost = 0
for i in range(epoch):
_nums = list(range(1, n + 1))
import time
time1 = time.time()
print(solveByBackTrack(_nums))
time2 = time.time()
print(solveByDFS(_nums))
time3 = time.time()
print(lazySolution(_nums))
time4 = time.time()
back_cost += time2 - time1
dfs_cost += time3 - time2
lazy_cost += time4 - time3
print("回朔平均耗时:", float(back_cost) / epoch)
print("DFS平均耗时:", float(dfs_cost) / epoch)
print("包里搜索平均耗时:", float(lazy_cost) / epoch)
遍历数组选择 1-7,跑50轮测平均速度:
回朔平均耗时: 0.009738812446594238
DFS平均耗时: 0.010725607872009277
暴力搜索平均耗时: 0.0832136631011963