遞迴-python3解決n個數的排列問題
0.摘要
給定n個不相同的數字,輸出所有的排列方式。
1.思路
首先,我們回憶一下數學上解決排列問題的方法:
我們先從所有資料中選取一個,放在第一位;然後,再從剩下的資料中選取一個,放在第二位;不斷重複,直到最後一位。這樣我們就得到了所有的排列結果。
寫成具體可操作的步驟即:
step1:a[0],a[1]……a[n],從n個元素選擇一個
step2:除去step1已經選取的元素,從n-1個元素再選擇一個
……
step(n):除去step1-step(n-1)已經選取的n-1個元素,只剩一個元素可選。
根據排列組合的思路:n個不同的數,排列組合共有n!種。因此,直接的思路是,使用n重for迴圈遍歷,即可得到所有排列組合結果。
但是,這樣的思路存在兩個問題:
- 問題一:題目中給定的n並沒有確定,所以for迴圈層數不確定。上述思路只適合n為確定數字的情況;
- 問題二:已經選取的元素,需要有一個數組記錄狀態,這樣才能避免被重複選取
2.一個可行但不太好的方案:
首先,解決問題一:
分析上述問題,發現它符合運用遞迴的條件:
- 可以把要解決的問題轉化為一個新問題,而這個新的問題的解決方法仍與原來的解決方法相同,只是所處理的物件有規律地遞增或遞減。
- 可以應用這個轉化過程使問題得到解決。
- 必定要有一個明確的結束遞迴的條件。
如果在遞迴過程中加入for迴圈,那麼隨著遞迴層數的變化,自然就形成了多層for迴圈巢狀的結構。for迴圈的層數完全由遞迴層數決定,或者由遞迴結束條件決定。
然後,我們解決問題二:
如何避免一個數字被多個位置重複選取呢?最直接的方法是,呼叫過之後,就刪除該資料。
由於在python中,形參和實參會相互影響。所以在使用之前,需要先copy資料,防止原資料被破壞。
解決完問題,我們看完整的程式碼:
import numpy as np def fun(in_list,out_list): if len(in_list) == 0: print(out_list) else: for element in in_list: inner_in = in_list.copy() inner_out = out_list.copy() inner_in.remove(element) inner_out.append(element) fun(inner_in,inner_out) def full_permutation(in_list): out_list = [] fun(in_list,out_list) a = np.arange(5).tolist() full_permutation(a)
在fun()函式中,我們實現了遞迴操作,並添加了for迴圈語句。這樣,就能夠利用遞迴構建多層for迴圈。從而解決了問題一
程式最後給出了一個簡單的測試樣例,讀者可自己執行,從而驗證結果。
3.更優的方案
在第2節中,我們稱之為不太好的方案,原因是我們將列表大量copy,這樣防止了不同遞迴路徑的資料干擾,但卻需要佔用大量空間。如何在不復制列表的情況下,解決問題二呢?
方法當然是有的,引入輔助列表即可解決。引入一個列表,記錄資料是否已經被使用。
當該資料已被使用,則狀態為False,下一次取數的時候,就不會取到該資料。
import numpy as np
def fun(n,in_list,state,out_list):
if (n == 0):
print(out_list)
else:
for i in range(len(in_list)):
if state[i]:
out_list[n-1] = in_list[i]
state[i] = False
fun(n-1,in_list,state,out_list)
state[i] = True
def full_permutation(in_list):
n = len(in_list)
state = [True for _ in range(n)]
out_list = in_list.copy()
fun(n,in_list,state,out_list)
a = np.arange(4).tolist()
full_permutation(a)
在這段程式碼中,我們使用了state[]記錄排列組合資料的狀態。
在遞迴過程中,資料使用後,狀態置為False;
本次遞迴結束後,資料狀態要恢復為True,不影響其他遞迴過程對資料的使用。
另外,為了方便書寫,排列組合取值從最後一位開始。