阅读目录
- 递归实现
- 递归方法一
- 递归方法二
- 字典序法
- 深度优先算法实现全排列
递归实现
递归方法一
- 固定数组的第一个元素list[0],然后对之后的元素list[1:]进行递归全排列,得到 list[1:] 的全排列之后,遍历 list[1:] 的全排列结果,将 list[0] 分别插入每一个结果中的每一个位置。
- 如数组 [1,2,3],固定1,对[2,3]全排列,得到结果【2,3】和【3,2】。接着将1插入每一个结果的每一个位置,即对于【2,3】,将1插入之后得到【1,2,3】、【2,1,3】、【2,3,1】,对于【3,2】,将1插入之后得到【1,3,2】、【3,1,2】、【3,2,1】。
def fullpermutation(list):
if list == None:
return None
if len(list) == 1: # 从list[1]处开始递归的
return [list]
res = []
left = list[0]
right = fullpermutation(list[1:])
for i in right:
for j in range(len(i)+1):
res.append(i[:j]+[left]+i[j:])
return res
print(fullpermutation([1,2,3]))
- 首先递归回的过程,right先拿到一个[[3]],第一层for循环,取到[3],第二层for循环的结果为:
i = [3]
print(i[:0] + [2] + i[0:]) # 注意递归回到上一层规模[2,3],此时 left[0]=2
print(i[:1] + [2] + i[1:])
'''
[2, 3]
[3, 2]
'''
然后是return res 把上面两个值right=[[2, 3], [3, 2]],返回给上一层,此时规模为[1,2,3],left[0]=1,第一层for 循环分别取到 [2, 3], [3, 2],第二层for循环的结果为:
i = [2, 3]
print(i[:0] + [1] + i[0:])
print(i[:1] + [1] + i[1:])
print(i[:2] + [1] + i[2:])
'''
[1, 2, 3]
[2, 1, 3]
[2, 3, 1]
'''
i = [3, 2]
print(i[:0] + [1] + i[0:])
print(i[:1] + [1] + i[1:])
print(i[:2] + [1] + i[2:])
'''
[1, 3, 2]
[3, 1, 2]
[3, 2, 1]
'''
所以最终结果为:[[1, 2, 3], [2, 1, 3], [2, 3, 1], [1, 3, 2], [3, 1, 2], [3, 2, 1]]
去重
def fullpermutation(list):
if list == None:
return None
if len(list) == 1: # 从list[1]处开始递归的
return [list]
res = []
left = list[0]
right = fullpermutation(list[1:])
for i in right:
if i[:j]+[left]+i[j:] not in res: # 去重
for j in range(len(i)+1):
res.append(i[:j]+[left]+i[j:])
return res
print(fullpermutation([1,2,3]))
递归方法二
- 仍然以【1,2,3】为例,使用穷举来实现的话。第一步,先把1放在第一位,然后对【2,3】进行全排列;那如何对【2,3】进行全排列呢,分别固定2、3在第一位,对剩下的元素进行全排列,得到【2】、【3】,因此得到【2,3】,【3,2】,因此得到【1,2,3】和【1,3,2】。回到开头,将2固定在第一位,即交换1,2,对【1,3】进行全排…
- 具体来说做法如下:
1、列表只有一个元素[a],它的全排列只有a。
2、列表有两个元素[a, b],它的全排列为[a, b], [b, a]:{ 将第一个元素a固定,对b进行全排列得到[a, b]。将b固定,对a进行全排列,得到[b, a] }
3、列表有三个元素[a, b, c],{ 将a固定,对bc进行全排列{ 将b固定,对c全排列[abc]。交换bc,将c固定对b进行全排列[acb] },交换ab,[b, a, c] 对ac进行全排列{ … }… …}
4、列表有n个元素,将第一个元素固定,对剩下n - 1个元素进行全排列。将第一个元素依此与其他元素交换,对每次交换后对剩下的n-1个元素进行全排列。
5、对剩下的n - 1个元素全排列,同上,固定第一个元素后,对n - 2排列。
6、直到数组数量为1,全排列就是它自己,完成一次排列。 - 如何确定当前数组的已经固定了n-1个元素,这时我们可以引入begin和end两个指针。当begin等于end时,就说明当前数n个元素都已经固定好了,直接返回结果当前数组,就是一次排列。这也是递归的终止条件。
def permutations(arr, begin, end):
if begin == end: # 当begin等于end,就说明数组中的元素都全部固定了,这是递归的终止条件
print(arr) # 打印当前这一次排列
else:
for index in range(begin, end):
arr[index], arr[begin] = arr[begin], arr[index]
# 数组的第一个元素和其他任意一个元素元素都交换一次,包括刚开始他自己
permutations(arr, begin + 1, end)
# 交换完成之后,对剩下的元素进行交换,即全排
arr[index], arr[begin] = arr[begin], arr[index]
# 当以arr[index]在第一位时,都排列完的时候,还要将将交换双方换回来,在进行下一次循环
s = [1, 2, 3] # 测试:s = [1, 3, 3] 当有重复时,就要考虑去重了
permutations(s, 0, len(s))
去重
如上测试返s = [1, 3, 3]时,回结果所示,当数组有重复元素的时候,上面的程序不会进行去重,因此可以对上述程序进行优化,也就是说,从第一个数字起每个数分别与它后面非重复出现的数字交换。用编程的话描述就是第i个数与第j个数交换时,要求[i,j)中没有与第j个数相等的数。需要增加判断条件,在两个值交换之前首先判断者两个数值是否相等,若相等则不交换,不相等则进行交换,但是由于本身必须与本身进行交换,因此,需要额外增加一个判断条件,即是否自己和自身交换!
def is_swap(array, i, j): # 定义检查函数,检查能不能交换
if i == j:
return True
for n in range(i, j):
if array[n] != array[j]:
continue
else: # 如果在这个区间内有元素与array[j]相等的话,
return False
return True
def permutations(arr, begin, end):
if begin == end:
print(arr)
else:
for index in range(begin, end):
if is_swap(arr, begin, index):
arr[index], arr[begin] = arr[begin], arr[index]
permutations(arr, begin + 1, end)
arr[index], arr[begin] = arr[begin], arr[index]
字典序法
- 起点:字典序最小的排列,例如122345。起点为正序
终点:字典序最大的排列,例如543221。终点为倒序
过程:按当前的排列找出刚好比它大的下一个排列
如:524321的下一个排列是531224 - 如何计算出来的?
我们从后向前找第一双相邻的递增数字,”21”、”32”、”43”都是非递增的,”24”即满足要求,称前一个数字2为替换数,替换数的下标称为替换点,再从后面找一个比替换数大的最小数(这个数必然存在),1、2都不行,3可以,将3和2交换得到”534221”,然后再将替换点后的字符串”4221”进行倒序,即前面第一个4和最后一个1交换,前面第二个,和倒数第二交换…即得到”531224”。 - 对于像”543221”这种已经是最“大”的排列,返回false。
这样,一个while循环再加上计算字符串下一个排列的函数就可以实现非递归的全排列算法。 - 步骤:后找、小大、交换、翻转
后找:字符串中最后一个升序的位置i,即:S[i] < S[i+1];
查找(小大):S [i+1…N-1] 中比 S[i] 大的最小值 S[j] ;
交换:S[i],S[j];交换操作前和操作后,S[i+1…N-1]一定都是降序的
翻转:S[i+1…N-1]
def swap(array, i, j): # 元素交换函数
if i >= j:
return
array[i], array[j] = array[j], array[i]
def reverse(array, i, j): # 元素翻转函数
if array is None or i < 0 or j < 0 or j <= i or len(array) < j + 1:
# j是最后一个索引的位置,即是 len(array)-1
return
while i < j:
swap(array, i, j)
i += 1
j -= 1
def get_next_permutation(array, size): # 获得当前序列下一个序列函数
# 后找:字符串中最后一个升序的位置i,即:S[i]<S[i+1]
i = size - 2
while i >= 0 and array[i + 1] <= array[i]:
i -= 1
if i < 0:
return False
# 查找(小大):S[i+1…N-1]中比S[i]大的最小值S[j]
j = size - 1
while j >= 0 and array[j] <= array[i]:
j -= 1
# 交换:S[i],S[j]
swap(array, i, j)
# 翻转:S[i+1…N-1]
reverse(array, i + 1, size - 1)
return True
def perm(array, size): # 总函数
array.sort() # 输入必须正序,否则只会返回全排列的部分结果
print(array)
while get_next_permutation(array, size):
print(array)
perm([1, 2, 3], 3)
第二种非翻转方法,如下图:
前两三步都一样,只是我们一直看开始找的第一个数的位置,即交换后也找到那个数的位置,最后一步操作为:改变当前数(包括当前数)之后的数字,按照从小到大的顺序,即是下一个数
深度优先算法实现全排列
深度搜索,返回时回溯
visit = [True, True, True]
temp = ["" for x in range(0, 3)]
def dfs(position):
if position == len(arr):
print(temp)
return
for index in range(0, len(arr)):
if visit[index] == True:
temp[position] = arr[index]
visit[index] = False
dfs(position + 1)
visit[index] = True
arr = ["a", "b", "c"]
dfs(0)