方法一:最经典的递归算法
a = list(range(4))
def go(beg):
if beg >= len(a):
print(a)
for i in range(beg, len(a)):
a[beg], a[i] = a[i], a[beg]
go(beg + 1)
a[beg], a[i] = a[i], a[beg]
go(0)
它生成的排列是非字典序的。
方法二:字典序生成全排列
这种方法复杂度较高,非常直观。
1,2,3,4
1,2,4,3
1,3,2,4
1,3,4,2
1,4,3,2
.......
4,3,2,1
从最后一个元素往前走,我们想让它是递增的,如果碰见了不递增的,那就让右面中略大于该数字的数字出头顶替这个违背了递增规律的数字。然后翻转右面。
方法三:全排列散列
给定一个int值,这个数字可以映射到一个排列
这种方法生成排列的顺序满足字典序。
方法四:类似方法一,但每次只交换1次
方法1每生成一个排列交换过去之后还要再交换回来,本方法只需要交换一次
这种方法很抽象。
"""
当n为偶数,每调用一次go(n-1)后,各个数字位置不变
当n为奇数,每调用一次go(n-1)后,各个数字循环左移一个
所以当n为偶数,让a[n]与a[i]交换,当n为奇数,让a[n]与a[0]交换
这样主要是为了让a[n]发生变化,让a中每个元素都从第n个位置过一下
"""
a = [i for i in range(4)]
def go(n):
if n == 1:
print(a)
return
# 如果n为偶数,那么n-1为奇数,执行偶数次heap(奇数),而heap(奇数)只是简单的首尾互换,所以偶数次heap(奇数)不改变数组
for i in range(n):
go(n - 1)
if n & 1:
a[0], a[n - 1] = a[n - 1], a[0]
else:
a[i], a[n - 1] = a[n - 1], a[i]
go(len(a))
奇数偶数的判断放在for循环里面不太好,可以这么整:
a = [i for i in range(4)]
def odd(n):
if n == 1:
print(a)
return
for i in range(n):
even(n - 1)
a[0], a[n - 1] = a[n - 1], a[0]
def even(n):
for i in range(n):
odd(n - 1)
a[i], a[n - 1] = a[n - 1], a[i]
def go(n):
if n & 1:
odd(n)
else:
even(n)
go(len(a))
方法五:Steinhaus-Johnson-Trotter算法
Steinhaus-Johnson-Trotter算法是一种基于最小变换的全排列生成算法。也就是说,相邻的两个排列只有两个位置的数字不一样(交换了位置),这非常像格雷码!
a = list(range(4))
di = [-1] * len(a)
cnt = 0
while 1:
print(a)
ma = None
for i in range(len(a)):
if 0 <= i + di[i] < len(a) and a[i + di[i]] < a[i]:
if ma is None or a[ma] < a[i]:
ma = i
if ma is None: break
target = ma + di[ma]
a[ma], a[target] = a[target], a[ma]
di[ma], di[target] = di[target], di[ma]
for i in range(len(a)):
if a[i] > a[target]:
di[i] *= -1
这种算法复杂度为O(n*n!)
,生成的序列如下:
[0, 1, 2, 3]
[0, 1, 3, 2]
[0, 3, 1, 2]
[3, 0, 1, 2]
[3, 0, 2, 1]
[0, 3, 2, 1]
[0, 2, 3, 1]
[0, 2, 1, 3]
[2, 0, 1, 3]
[2, 0, 3, 1]
[2, 3, 0, 1]
[3, 2, 0, 1]
[3, 2, 1, 0]
[2, 3, 1, 0]
[2, 1, 3, 0]
[2, 1, 0, 3]
[1, 2, 0, 3]
[1, 2, 3, 0]
[1, 3, 2, 0]
[3, 1, 2, 0]
[3, 1, 0, 2]
[1, 3, 0, 2]
[1, 0, 3, 2]
[1, 0, 2, 3]