集合元素的排列和組合

2021-07-02 19:41:04 字數 3527 閱讀 5592

一、集合的排列

給定乙個集合s,含有n個不重複的元素,輸出該集合元素的所有排列,leetcode對應題目為:列印所有排列的複雜度為o(n*n!),因為共有n!個不同的排列,列印每個排列的複雜度為o(n)。列印所有的排列一般採用深搜策略,先給出乙個常規的方法:

[cpp]view plain

copy

print?

void

perm(

intstart,

intend,vector<

int> &num,vectorint

>> &result)  

for(int

i=start;i<=end;i++)  

}  該**可看成是標準深搜的乙個具體例項:

[cpp]view plain

copy

print?

void

dfs(

inti,

intn,…)  

ifi=n  

print;  

return

;  for

j=i to k 

dodfs(j,n,…);  

深搜的第一步是判斷是否已經達到遞迴結束的條件,之後針對不同的解空間進行遞迴。在某些情況下,在遞迴之前也伴隨著剪枝,以加速演算法的執行速度。上述儲存集合排列的**思想很簡單:將起始位置的元素置成集合的每乙個元素,然後遞迴下乙個位置。該問題的解空間是乙個排列樹。例如,針對集合的解空間為:

圖1排列樹形式的解空間

排列樹形式的解空間有個規律,就是隨著遞迴深度的增加,每個節點的子節點個數都逐次減一,這也是為什麼排列演算法的複雜度是o(n!)。

此外,還有一種生成排列的方法,思想不太好懂,下面只給出**:

[cpp]view plain

copy

print?

void

perm(

intn,vector<

int> &num,vectorint

>> &result)  

for(int

i=0;i

else

}  }  

演算法正確性證明的基本思想是:陣列長度n為奇數時,操作完成後,陣列不變,n為偶數時,操作完成後,陣列迴圈右移一位。

二、集合的k元素子集

給定乙個集合s,含有n個不重複的元素,生成所有的含有k個元素的子集,也即求組合數,leetcode對應題目為:

。該問題也可以通過深搜完成,在寫**之前先分析一下其解空間的構造。針對每乙個元素,我們都有兩種選擇:選擇該元素或者不選該元素,由此問題的解空間是一棵二叉樹。例如,對集合,選擇2個數的子集的解空間如下圖:

圖2 子集樹形式的解空間

根據上面的分析,我們可以知道求k個元素的子集,我們只需要判斷當前已經選擇的元素個數,如果已經選擇了k個元素,則找到乙個符合的子集,無需再遍歷子節點。如果尚未選擇k個元素,但是已經達到葉節點(n個元素已經判斷一遍),我們可以直接返回,這說明此次的遍歷沒有找到符合的子集。按照這個思路,**如下:

[cpp]view plain

copy

print?

void

com(

intdepth,

intn,

intk,vector<

int>& r,vectorint

> >& result)  

if(depth==n) 

return

;  r.push_back(depth+1);  

com(depth+1,n,k,r,result);  

r.pop_back();  

com(depth+1,n,k,r,result);  

}  

其中引數depth表示當前深度,引數n表示最大深度,引數k表示當前儲存的元素個數。如上所述,**有兩個終止條件:找到符合的子集,或者達到葉節點。遍歷時,只需要考慮兩種情況:選擇該元素或者不選該元素,然後遍歷下一層節點。

上述**是最原始的**,因為提交已經ac,所以無需再做優化,但實際上**還可以有很大的優化餘地。事實上,在某些路徑中,我們無需遍歷到葉節點也可以知道後面的遍歷是不符合要求的,如果需要新增的元素個數大於剩餘路徑上所有元素的個數,則即使新增剩餘所有的元素也不符合要求,此時我們可以直接進行剪枝,避免不必要的搜尋。如果不剪枝複雜度最壞為o(2n),剪枝之後的複雜度為o(nk)。

在《挑戰程式設計競賽》一書的157頁,有介紹一種非遞迴列舉所包含的所有大小為k的子集的方法。有興趣的讀者可自行閱讀,下面只給出**:

[cpp]view plain

copy

print?

vectorint

> > combine(

intn, 

intk)   

}  result.push_back(r);  

intx=comb&-comb,y=comb+x;  

comb=((comb&~y)/x>>1)|y;  

}  return

result;  

}  

三、集合的所有子集

問題二只求集合的k元素子集,現在要求集合的所有子集,leetcode對應的題目為:該問題更加簡單,只需要將圖2的子集樹完整遍歷一遍即可,從根節點到葉節點的每一條路徑都表示乙個可能的子集。**如下:

[cpp]view plain

copy

print?

void

sub(

intdepth,vector<

int> &s,vector<

int>& r,vectorint

>>& result)  

r.push_back(s[depth]);  

sub(depth+1,s,r,result);  

r.pop_back();  

sub(depth+1,s,r,result);  

}  

與問題二的區別是,不需要考慮當前已經儲存的元素個數,只需要判斷是否已經到達葉節點。當然,我們也可以通過遍歷乙個數的二進位制來獲得集合的所有子集。

[cpp]view plain

copy

print?

vectorint

> > subsets(vector<

int> &s)   

}  result.push_back(r);  

}  return

result;  

}  

上述三個問題都可以通過深搜完美解決,後兩個問題還可以通過位運算解決,不管是深搜還是位運算都有比較相似的模式,希望通過上面的分析,大家能深刻理解深搜和位運算。

集合的排列與組合

introductory combinatorics fifth edition 學習筆記 排列和組合的區別在於放置和選擇是否和順序有關。集合的排列 n元素集合的r排列a n,r n n 1 n 2 n r 1 n n r 集合的組合 n元素集合的r組合c n,r n n r r 組合不考慮順序 問...

經典演算法 排列組合 N元素集合的M元素子集

題目說明 假設有個集合擁有n個元素,任意的從集合中取出m個元素,則這m個元素所形成的可能子集有那些?題目解析 假設有5個元素的集合,取出3個元素的可能子集如下 這些子集已經使用字典順序排列,如此才可以觀察出一些規則 如果最右乙個元素小於m,則如上面一樣的不斷加1 如果右邊一位已至最大值,則加1的位置...

排列和組合

排列組合計算公式 排列a n,m n n 1 n m 1 n!n m n為下標,m為上標,以下同 組合c n,m a n,m a m,m n!m!n m 問題 從1到n 包含 中選出m n個數,在下列情況下,有多少種組合?限制條件 1 無限制 2 各位數字公升序排列 3 不能有重複數字 4 各位數字...