全排列的生成算法就是對於給定的字符集,用有效的方法將所有可能的全排列無重複無遺漏地枚舉出來。任何n個字符集的排列都可以與1~n的n個數字的排列一一對應,因此在此就以n個數字的排列爲例說明排列的生成法。
n個字符的全體排列之間存在一個確定的線性順序關係。所有的排列中除最後一個排列外,都有一個後繼;除第一個排列外,都有一個前驅。每個排列的後繼都可以從 它 的前驅經過最少的變化而得到,全排列的生成算法就是從第一個排列開始逐個生成所有的排列的方法。
全排列的生成法通常有以下幾種:
字典序法
遞增進位數製法
遞減進位數製法
鄰位交換法
遞歸類算法
1.字典序法
字典序法中,對於數字1、2、3......n的排列,不同排列的先後關係是從左到右逐個比較對應的數字的先後來決定的。例如對於5個數字的排列12354和12345,排列12345在前,排列12354在後。按照這樣的規定,5個數字的所有的排列中最前面的是12345,最後面的是54321。
字典序算法如下:
設P是1~n的一個全排列:p=p1p2......pn=p1p2......pj-1pjpj+1......pk-1pkpk+1......pn
1)從排列的右端開始,找出第一個比右邊數字小的數字的序號j(j從左端開始計算),即 j=max{i|pi<pi+1}
2)在pj的右邊的數字中,找出所有比pj大的數中最小的數字pk,即 k=max{i|pi>pj}(右邊的數從右至左是遞增的,因此k是所有大於pj的數字中序號最大者)
3)對換pi,pk
4)再將pj+1......pk-1pkpk+1pn倒轉得到排列p’’=p1p2.....pj-1pjpn.....pk+1pkpk-1.....pj+1,這就是排列p的下一個下一個排列。
例如839647521是數字1~9的一個排列。從它生成下一個排列的步驟如下:
自右至左找出排列中第一個比右邊數字小的數字4 839647521
在該數字後的數字中找出比4大的數中最小的一個5 839647521
將5與4交換 839657421
將7421倒轉 839651247
所以839647521的下一個排列是839651247。
2.遞增進位數製法
在遞增進位制數法中,從一個排列求另一個排列需要用到中介數。如果用 ki表示排列p1p2...pi...pn中元素pi的右邊比pi小的數的個數,則排列的中介數就是對應的排列k1 ...... ki...... kn-1。
例如排列839647521的中介數是72642321,7、2、6、......分別是排列中數字8、3、9、......的右邊比它小的數字個數。
中介數是計算排列的中間環節。已知一個排列,要求下一個排列,首先確定其中介數,一個排列的後繼,其中介數是原排列中介數加1,需要注意的是,如果中介數的末位kn-1+1=2,則要向前進位,一般情形,如果ki+1=n-i+1,則要進位,這就是所謂的遞增進位制。例如排列839647521的中介數是72642321,則下一個排列的中介數是67342221+1=67342300(因爲1+1=2,所以向前進位,2+1=3,又發生進位,所以下一個中介數是67342300)。
得到中介數後,可根據它還原對應得排列。
算法如下:
中介數k1、k2、......、kn-1的各位數字順序表示排列中的數字n、n-1、......、2在排列中距右端的的空位數,因此,要按k1、k2、......、kn-1的值從右向左確定n、n-1、......、2的位置,並逐個放置在排列中:i放在右起的ki+1位,如果某位已放有數字,則該位置不算在內,最後一個空位放1。
因此從67342300可得到排列849617523,它就是839647521的後一個排列。因爲9最先放置,k1=6,9放在右起第7位,空出6個空位,然後是放8,k2=7,8放在右起第8位,但9佔用一位,故8應放在右起第9位,餘類推。
3.遞減進位制數法
在遞增進位制數法中,中介數的最低位是逢2進1,進位頻繁,這是一個缺點。把遞增進位制數翻轉,就得到遞減進位制數。
839647521的中介數是67342221(k1k2...kn-1),倒轉成爲12224376(kn-1...k2k1),這是遞減進位制數的中介數:ki(i=n-1,n-2,...,2)位逢i向ki-1位進1。給定排列p,p的下一個排列的中介數定義爲p的中介數加1。例如p=839647521,p的中介數爲12224376,p的下一個排列的中介數爲12224376+1=12224377,由此得到p的下一個排列爲893647521。
給定中介數,可用與遞增進位制數法類似的方法還原出排列。但在遞減進位制數中,可以不先計算中介數就直接從一個排列求出下一個排列。具體算法如下:
1)如果p(i)=n且i<>n,則p(i)與p(i-1)交換
2)如果p(n)=n,則找出一個連續遞減序列9、8、......、i,將其從排列左端刪除,再以相反順序加在排列右端,然後將i-1與左邊的數字交換
例如p=893647521的下一個排列是983647521。求983647521的下一個排列時,因爲9在最左邊且第2位爲8,第3位不是7,所以將8和9從小到大排於最右端364752189,再將7與其左方數字對調得到983647521的下一個排列是367452189。又例如求987635421的下一個排列,只需要將9876從小到大排到最右端並將5與其左方數字3對調,得到534216789。
4.鄰位對換法
鄰位對換法中下一個排列總是上一個排列某相鄰兩位對換得到的。以4個元素的排列爲例,將最後的元素4逐次與前面的元素交換,可以生成4個新排列:
1 2 3 4 1 2 4 3 1 4 2 3 4 1 2 3
然後將最後一個排列的末尾的兩個元素交換,再逐次將排頭的4與其後的元素交換,又生成四個新排列:
4 1 3 2 1 4 3 2 1 3 4 2 1 3 2 4
再將最後一個排列的末尾的兩個元素交換,將4從後往前移:
3 1 2 4 3 1 4 2 3 4 1 2 4 3 1 2
如此循環既可求出全部排列。
5.元素增值法(n進製法)
1)從原始排列p=p1p2......pn開始,第n位加n-1,如果該位的值超過n,則將它除以n,用餘數取代該位,並進位(將第n-1位加1)
2)再按同樣方法處理n-1位,n-2位,......,直至不再發生進位爲止,處理完一個排列就產生了一個新的排列
3)將其中有相同元素的排列去掉
4)當第一個元素的值>n則結束
以3個數1、2、3的排列爲例:原始排列是1 2 3,從它開始,第3個元素是3,3+2=5,5 Mod 3=2,第2個元素是2,2+1=3,所以新排列是1 3 2。通過元素增值,順序產生的排列是:1 2 3,1 3 2,2 1 1,2 1 3,2 2 2,2 3 1,2 3 3,3 1 2,3 2 1
有下劃線的排列中存在重複元素,丟棄,餘下的就是全部排列。
6.遞歸類算法
全排列的生成方法用遞歸方式描述比較簡潔,實現的方法也有多種。
1)回溯法
回溯法通常是構造一顆生成樹。以3個元素爲例;樹的節點有個數據,可取值是1、2、3。如果某個爲0,則表示尚未取值。
初始狀態是(0,0,0),第1個元素值可以分別挑選1,2,3,因此擴展出3個子結點。用相同方法找出這些結點的第2個元素的可能值,如此反覆進行,一旦出現新結點的3個數據全非零,那就找到了一種全排列方案。當嘗試了所有可能方案,即獲得了問題的解答。
2)遞歸算法
如果用P表示n個元素的排列,而Pi表示不包含元素i的排列,(i)Pi表示在排列Pi前加上前綴i的排列,那麼,n個元素的排列可遞歸定義爲:
如果n=1,則排列P只有一個元素i
如果n>1,則排列P由排列(i)Pi構成(i=1、2、....、n-1)。
根據定義,容易看出如果已經生成了k-1個元素的排列,那麼,k個元素的排列可以在每個k-1個元素的排列Pi前添加元素i而生成。例如2個元素的排列是1 2和2 1,對與個元素而言,p1是2 3和3 2,在每個排列前加上1即生成1 2 3和1 3 2兩個新排列,p2和p3則是1 3、3 1和1 2、2 1,按同樣方法可生成新排列2 1 3、2 3 1和3 1 2、3 2 1。
3)循環移位法
如果已經生成了k-1個元素的排列,則在每個排列後添加元素k使之成爲k個元素的排列,然後將每個排列循環左移(右移),每移動一次就產生一個新的排列。
例如2個元素的排列是1 2和2 1。在1 2 後加上3成爲新排列1 2 3,將它循環左移可再生成新排列2 3 1、3 1 2,同樣2 1 可生成新排列2 1 3、1 3 2和3 2 1。
view plaincopy to clipboardprint?
#include "stdafx.h"
#include<iostream>
using namespace std;
#include<time.h>
#define maxn 100
void outp(int p[],int n) //輸出一個排列
{
int i;
cout<<" ";
for(i=1;i<=n;i++)
cout<<p[i]<<" ";
cout<<endl;
}
void swap(int & x,int & y)
{
int t=x;
x=y;
y=t;
}
void dict(int p[],int n) //字典序法
{
int i,j;
outp(p,n);
i=n-1;
while(i>0)
{
if (p[i]<p[i+1])
{
for(j=n;j>=i+1;j--)
if(p[i]<=p[j]) break;
swap(p[i],p[j]);
for(j=n;j>=1;j--)
{
i+=1;
if (i>=j) break;
swap(p[i],p[j]);
}
outp(p,n);
i=n;
}
i-=1;
};
}
bool midn(int m[],int n)
{
int k=n-1;
while(1)
{
m[k]+=1;
if(m[k]<n-k+1)break;
m[k]=0;
k-=1;
}
int s=0;
bool b=false;
for( k=1;k<=n;)
s+=m[k++];
if(s==0) b=true;
return b;
}
void incr(int p[],int n)
{
int m[maxn];
int i,j,a;
for(i=1;i<=n;)
m[i++]=0;
while(i>0)
{
for(i=1;i<=n;)
p[i++]=0;
for(i=1;i<=n;i++)
{
a=m[i]+1;
j=n;
while(j>0)
{
if(p[j]==0)
{
a-=1;
if(a==0) break;
}
j-=1;
}
p[j]=n-i+1;
}
outp(p,n);
if (midn(m,n)==true) break;
}
}
void degr(int p[],int n)
{
int i,j;
while(1)
{
outp(p,n);
if(p[1]!=n)
{
i=0;
while(p[++i]!=n);
swap(p[i],p[i-1]);
}
else
{
i=1;
while(i<n)
{
if(p[i]!=p[i+1]+1) break;
i+=1;
}
if(i==n)break;
j=i;
while(p[i]!=p[j]-1)
i+=1;
swap(p[i],p[i-1]);
for(i=1;i<=n-j;)
p[i++]=p[i+j];
for (i=1;i<=j;i++)
p[n-i+1] =n-i+1;
}
}
}
void conv(int p[],int n)
{
long m=1;
for(int i=3;i<n;)
m=m*i++;
for(int i=1;i<m;i++)
{
outp(p,n);
for(int j=n;j>1;j--)
{
swap(p[j],p[j-1]);
outp(p,n);
}
swap(p[n],p[n-1]);
outp(p,n);
for(int j=1;j<n;j++)
{
swap(p[j],p[j+1]);
outp(p,n);
}
swap(p[1],p[2]);
}
}
bool test(int p[], int n)
{
bool b=false;
for(int i=1;i<=n;i++)
for(int j=i+1;j<=n;j++)
if(p[i]==p[j])
{
b=true;
break;
}
return b;
}
void Nnum(int p[],int n)
{
while(n>0)
{
if (test(p,n)==false) outp(p,n);
p[n]+=n-1;
for(int j=n;j>1;j--)
{
if(p[j]>n)
{
p[j]%=n;
p[j-1]+=1;
if(p[1]>n)
{
n=0;
break;
}
}
}
}
}
void recu(int p[],int n,int k)
{
if(k==n)
outp (p,n);
else
{
for(int i=k;i<=n;i++)
{
swap (p[k],p[i]);
recu(p, n,k+1);
swap (p[k],p[i]);
}
}
}
void cyc(int p[],int n,int k)
{
if (k>n)
outp(p,n);
else
{
for(int i=0;i<k;i++)
{
int t=p[1];
for(int j=2;j<=k;j++)
p[j-1]=p[j];
p[k]=t;
cyc(p,n,k+1);
}
}
}
void remo(int p[],int n,int k)
{
bool b;
if (k>n)
outp (p,n);
else
{
for(int i=1;i<=n;i++)
{
b=false;
p[k]=i;
for(int j=1;j<k;j++)
if(i==p[j])
{
b=true;
break;
}
if(b==false) remo(p,n,k+1);
}
}
}
int main()
{
int i,j,m;
int p[maxn];
int n;
clock_t start,finish;
cout<<"輸入排列元素總數n=";
cin>>n;
for(i=1;i<=n;i++)
p[i]=i;
cout<<"1——字典序法"<<endl;
cout<<"2——遞增進位數製法"<<endl;
cout<<"3——遞減進位數製法"<<endl;
cout<<"4——鄰位交換法"<<endl;
cout<<"5——n進制數法"<<endl;
cout<<"6——遞歸生成法"<<endl;
cout<<"7——循環移位法"<<endl;
cout<<"8——回溯法"<<endl;
cout<<"請選擇: ";
cin>>m;
start=clock();
switch (m)
{
case 1:dict(p,n);break;
case 2:incr(p,n);break;
case 3:degr(p,n);break;
case 4:conv(p,n);break;
case 5:Nnum(p,n);break;
case 6:recu(p,n,1);break;
case 7:cyc(p,n,1);break;
case 8:remo(p,n,1);
}
finish=clock();
cout<<"程序執行時間:"
<<(double)(finish-start)/CLOCKS_PER_SEC<<"秒"<<endl;
system("pause");
return 0;
}
#include "stdafx.h"
#include<iostream>
using namespace std;
#include<time.h>
#define maxn 100
void outp(int p[],int n) //輸出一個排列
{
int i;
cout<<" ";
for(i=1;i<=n;i++)
cout<<p[i]<<" ";
cout<<endl;
}
void swap(int & x,int & y)
{
int t=x;
x=y;
y=t;
}
void dict(int p[],int n) //字典序法
{
int i,j;
outp(p,n);
i=n-1;
while(i>0)
{
if (p[i]<p[i+1])
{
for(j=n;j>=i+1;j--)
if(p[i]<=p[j]) break;
swap(p[i],p[j]);
for(j=n;j>=1;j--)
{
i+=1;
if (i>=j) break;
swap(p[i],p[j]);
}
outp(p,n);
i=n;
}
i-=1;
};
}
bool midn(int m[],int n)
{
int k=n-1;
while(1)
{
m[k]+=1;
if(m[k]<n-k+1)break;
m[k]=0;
k-=1;
}
int s=0;
bool b=false;
for( k=1;k<=n;)
s+=m[k++];
if(s==0) b=true;
return b;
}
void incr(int p[],int n)
{
int m[maxn];
int i,j,a;
for(i=1;i<=n;)
m[i++]=0;
while(i>0)
{
for(i=1;i<=n;)
p[i++]=0;
for(i=1;i<=n;i++)
{
a=m[i]+1;
j=n;
while(j>0)
{
if(p[j]==0)
{
a-=1;
if(a==0) break;
}
j-=1;
}
p[j]=n-i+1;
}
outp(p,n);
if (midn(m,n)==true) break;
}
}
void degr(int p[],int n)
{
int i,j;
while(1)
{
outp(p,n);
if(p[1]!=n)
{
i=0;
while(p[++i]!=n);
swap(p[i],p[i-1]);
}
else
{
i=1;
while(i<n)
{
if(p[i]!=p[i+1]+1) break;
i+=1;
}
if(i==n)break;
j=i;
while(p[i]!=p[j]-1)
i+=1;
swap(p[i],p[i-1]);
for(i=1;i<=n-j;)
p[i++]=p[i+j];
for (i=1;i<=j;i++)
p[n-i+1] =n-i+1;
}
}
}
void conv(int p[],int n)
{
long m=1;
for(int i=3;i<n;)
m=m*i++;
for(int i=1;i<m;i++)
{
outp(p,n);
for(int j=n;j>1;j--)
{
swap(p[j],p[j-1]);
outp(p,n);
}
swap(p[n],p[n-1]);
outp(p,n);
for(int j=1;j<n;j++)
{
swap(p[j],p[j+1]);
outp(p,n);
}
swap(p[1],p[2]);
}
}
bool test(int p[], int n)
{
bool b=false;
for(int i=1;i<=n;i++)
for(int j=i+1;j<=n;j++)
if(p[i]==p[j])
{
b=true;
break;
}
return b;
}
void Nnum(int p[],int n)
{
while(n>0)
{
if (test(p,n)==false) outp(p,n);
p[n]+=n-1;
for(int j=n;j>1;j--)
{
if(p[j]>n)
{
p[j]%=n;
p[j-1]+=1;
if(p[1]>n)
{
n=0;
break;
}
}
}
}
}
void recu(int p[],int n,int k)
{
if(k==n)
outp (p,n);
else
{
for(int i=k;i<=n;i++)
{
swap (p[k],p[i]);
recu(p, n,k+1);
swap (p[k],p[i]);
}
}
}
void cyc(int p[],int n,int k)
{
if (k>n)
outp(p,n);
else
{
for(int i=0;i<k;i++)
{
int t=p[1];
for(int j=2;j<=k;j++)
p[j-1]=p[j];
p[k]=t;
cyc(p,n,k+1);
}
}
}
void remo(int p[],int n,int k)
{
bool b;
if (k>n)
outp (p,n);
else
{
for(int i=1;i<=n;i++)
{
b=false;
p[k]=i;
for(int j=1;j<k;j++)
if(i==p[j])
{
b=true;
break;
}
if(b==false) remo(p,n,k+1);
}
}
}
int main()
{
int i,j,m;
int p[maxn];
int n;
clock_t start,finish;
cout<<"輸入排列元素總數n=";
cin>>n;
for(i=1;i<=n;i++)
p[i]=i;
cout<<"1——字典序法"<<endl;
cout<<"2——遞增進位數製法"<<endl;
cout<<"3——遞減進位數製法"<<endl;
cout<<"4——鄰位交換法"<<endl;
cout<<"5——n進制數法"<<endl;
cout<<"6——遞歸生成法"<<endl;
cout<<"7——循環移位法"<<endl;
cout<<"8——回溯法"<<endl;
cout<<"請選擇: ";
cin>>m;
start=clock();
switch (m)
{
case 1:dict(p,n);break;
case 2:incr(p,n);break;
case 3:degr(p,n);break;
case 4:conv(p,n);break;
case 5:Nnum(p,n);break;
case 6:recu(p,n,1);break;
case 7:cyc(p,n,1);break;
case 8:remo(p,n,1);
}
finish=clock();
cout<<"程序執行時間:"
<<(double)(finish-start)/CLOCKS_PER_SEC<<"秒"<<endl;
system("pause");
return 0;
}
不知道大家有沒有聽說,明年起程序員考試就不分初,中,高級了,而我們軟件專業明年就要過程序了,據說相當於考中程,或者還要難一些,雖然不知道消息的正確性,但這的確是我們的老師告訴我們的,所以老師就出一些題給我們練,下面是一道關於數學中全排列的算法的問題,編了我4天!真是的看起來容易,編起來難..........下面給出我的源代碼,並給大家解釋思路:
view plaincopy to clipboardprint?
#include "stdio.h"
void chang(char str[],int m) /*定義循環左移函數(我沒有用左移函數)*/
{
int i,j;
char temp=str[0];
for (i=0;i<m;i++) str[i]=str[i+1];
str[i]=temp;
}
void pai(char str[],int m,int n) /*定義全排列函數*/
{
int k;
void chang(char str[],int m);
if (m<n) /* 定 義 遞 歸 調 用 出 口 */
{
for (k=0;k<=m;k++)
{
pai(str,m+1,n); /*遞歸調用*/
chang(str,m); /*調用左移函數*/
}
}
else printf("%s/t",str);
}
main()
{
char str[]="ABCD"; /*全排列字符,可以任意多個(相應的下面排列函數中參數"4"改成全排列字符的個數)*/
clrscr();
pai(str,0,4); /*這裏參數0(下標)表示從第一個元素開始,4表示元素個數(不是下標)*/
getch();
}
#include "stdio.h"
void chang(char str[],int m) /*定義循環左移函數(我沒有用左移函數)*/
{
int i,j;
char temp=str[0];
for (i=0;i<m;i++) str[i]=str[i+1];
str[i]=temp;
}
void pai(char str[],int m,int n) /*定義全排列函數*/
{
int k;
void chang(char str[],int m);
if (m<n) /* 定 義 遞 歸 調 用 出 口 */
{
for (k=0;k<=m;k++)
{
pai(str,m+1,n); /*遞歸調用*/
chang(str,m); /*調用左移函數*/
}
}
else printf("%s/t",str);
}
main()
{
char str[]="ABCD"; /*全排列字符,可以任意多個(相應的下面排列函數中參數"4"改成全排列字符的個數)*/
clrscr();
pai(str,0,4); /*這裏參數0(下標)表示從第一個元素開始,4表示元素個數(不是下標)*/
getch();
}
大家看到了,以上就是一步一步循環左移就能得到所有全排列的數了.以上程序在Trubo C 2.0 中運行通過,如果大家還有什麼疑問,請加我QQ:156301529,Email:[email protected],我們共同討論.另外,我在想,如果是n個數或字符中取m個進行排列的話,該怎麼改呢?
view plaincopy to clipboardprint?
char t;
if (m<n-1) {
permutation(a, m+1, n);
for (i=m+1;i<n;i++) {
t=a[m]; a[m]=a[i]; a[i]=t;
permutation(a, m+1, n);
t=a[m]; a[m]=a[i]; a[i]=t;
}
} else
{
printf("%s/n", a);
}
}
int main() {
char a[]="ABCDE";
permutation(a, 0,5);
return 0;
}