這是我的第一篇博文,筆者資曆尚淺,不對之處有勞斧正。演算法之於程式,同如靈魂之於肉體,靈魂駕馭肉體,演算法主宰程式,一點也不浮誇。學好演算法,好比行走江湖有一技榜身;相反忽視演算法,“問題”就多了,難求一解,效率低下之類,自不必說。受限於此,更是難以脫穎於同行,駐足於尖端。“碼農”一說,由此得來。
切入正題,此篇主要分享3種不同的求組合演算法(c++)
方法1 遞迴:
以一組數列為例:
1 2 3 4 5 中取3個數的組合,即n=5,r=3
組合規律如下:
首先固定第一個數為5,其後就是求解 n=4, r=2的組合數,共6個組合
其次固定第一個數為4,其後就是求解 n=3, r=2的組合數,共3個組合
最後固定第一個數為3,其後就是求解 n=2, r=2的組合數,共1個組合
這就找到了n=5,r=3與n=4,r=2,n=3,r=2,以及n=2,r=2的遞迴關係。
N個數中r個數組合遞推到n-1,r-1;n-2,r-1;…r-1,r-1共n-r+1次遞迴。
遞迴停止條件是r=1.
#include "iostream.h"int a[100];void comb(int m,int k){int i,j;for(i=m;i>=k;i--){ a[k]=i; if(k>1) comb(i-1,k-1); else { for(j=a[0];j>0;j--) cout<<a[j]; cout<<endl; } }}void main(){ intn=5,r=3; a[0]=r; comb(n,r); }
方法2:非遞迴
通過while迴圈控制機制,採用回溯法的演算法思想,實現非遞迴的組合演算法。
以m=5,r=3為例。
數列0,1,2,3,4
第一個組合即為初始化的0,1,2,用數組a[100]的a[0],a[1],a[2]儲存。 a[cur]標誌當前控制的是a[0],a[1],a[2]中的哪一個(初始選擇最後一個,即為a[2]).通過a[cur]-cur<=m-r可以判斷是否越界。比如:若a[cur]為a[2]時,當a[2]取3,3-2<=5-3 故沒越界,此刻組合為0,1,3;當a[2]取5時,5-2>5-3,組合輸出為0,1,5很顯然5根本取不到,故越界。
若不滿足a[cur]-cur<=m-r ,那麼執行a[--cur]++,回溯到上一層,a[cur]由a[2]變為a[1],同時a[1]=1變為a[1]=2,a[2]則變為3,組合即為0,2,3.若此時滿足a[cur]-cur<m-r,表示還有數列還有空間,cur=r-1,即a[cur]由a[1]變為a[2]繼續迴圈。不滿足,則繼續執行迴圈,下次a[cur]將回溯到a[0]。
#include "iostream.h"int a[100];void comb(int m,int r){ int cur; for(int i=0;i<r;i++) a[i]=i; cur=r-1; do{ if (a[cur]-cur<=m-r ){ for (int j=0;j<r;j++) cout<<a[j] <<" "; cout<<endl; a[cur]++; continue; } else{ if (cur==0){ break; } a[--cur]++; for(int i=1;i<r-cur;i++){ a[cur+i]=a[cur]+i; } if(a[cur]-cur<m-r) cur=r-1; } } while (1);}void main (){ int n;cin>>n;comb(n,i); }
遞迴的方法一般可讀性強,代碼量較小,設計難度小,使用範圍廣,但佔用空間大,時間複雜度大(即耗時間長度)。而非遞迴的方法一般空間,時間效率都高,但可讀性差,適用範圍小且設計難度大。
但在強調軟體維護優先於軟體效率的今天,除了少數像求階層和斐波那契數列那樣的尾遞迴程式,其他需要設定棧才能轉換為非遞迴的程式就沒有轉換非遞迴的必要。(此結論參考演算法設計與分析第2版)
方法3:for迴圈解決法
以n=5,r=3為例,所有組合如下:
1 2 3
1 2 4
1 2 5
1 3 4
1 3 5
1 4 5
2 3 4
2 3 5
2 4 5
3 4 5
運用我們找規律的邏輯能力,不難發現這些組合滿足兩個特點:1.各不相同 2.前面的數小於後面的數。所以演算法設計如下:
#include “iostream.h”void main(){int n=5,i,j,k;for(i=1;i<=n;i++)for(j=1;j<=n;j++)for(k=1;k<=n;k++)if(i<j&&j<k){t=t+1;cout<<i<<” ”<<j<<””<<k<<endl;}}
此方法代碼最少,但因for迴圈機制的局限性,不能實現n,r的通用求法。
組合問題是一個經典不衰的問題,求解方法當然不止這些,例如2進位標記法,子集數搜尋法之類會在子集數專欄介紹。最後,我的博文分享就此開始,作此以助人,同時也自勉,願中國軟體開發聯盟越走越遠。