方法一:最經典的遞歸算法
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]