排列組合是組合數學的基礎,從n個不同元素中任取m個,約定1<m≤n,按任意一種次序排成一列,稱爲排列,其排列種數記爲(Arrange)A(n,m)。從n個不同元素中任取m個(約定1<m<n)成一組,稱爲一個組合,其組合種數記爲C(n,m)。計算A(n,m)與C(n,m)只要簡單進行乘運算即可,要具體展現出排列的每一列與組合的每一組,決非輕而易舉。
注意:
1、 排列A有序 A(n,m) = n * (n-1) * …*(n-m+1)
2、 組合C無序 C(n,m) = A(n,m) / A(m,m)
3、 注意到組合與組成元素的順序無關,約定組合中的組成元素按遞增排序。因而,把以上程序中的約束條件作簡單修改: a[i]==a[j] 修改爲 a[i]>=a[j]
有兩種方法:
1、DFS方法適用於不重複的情況下,分治法可適用於有元素相同的情況下
2:分治法元素相同時的情況爲:
//1、arr[i]!=arr[k],因爲這樣交換後剩下的元素是一樣的,兩種全排列是重複的
//2、arr[k+1]—>arr[i-1],也就是k和i之間的元素不能和arr[i]相等,因爲k和i之間的元素都是和k交換過的,
//如果arr[i]和arr[k+1]—>arr[i-1]之間的元素有相等的,那麼這兩種排列也是重複的
//下面代碼中||運算前的是第一種情況,或運算之後的是第二種情況
排列:DFS
方法一:回溯法(全排列的數組中不可以有重複的元素)
/*1-n中m個數的全排列*/
#include <iostream>
#include <vector>
using namespace std;
int count = 0;
vector<int> v;
bool Single(int num)
{
for (int i=0; i<v.size(); ++i)
{
if (v[i] == num)
return false;
}
return true;
}
//全排列
void perm(int n, int m)
{
if (m == 0)
{
++count;
return;
}
for (int i=1; i<=n; ++i)
{
if (Single(i))
{
v.push_back(i);
perm(n, m-1);
v.pop_back();
}
}
}
int main()
{
int n, m;
cin >> n >> m;
perm(n, m);
cout << count << endl;
return 0;
}
回溯法:組合(組合的數組中不可以有重複的元素)
/*1-n中m個數的組合*/
#include <iostream>
#include <vector>
using namespace std;
int count = 0;
vector<int> v;
bool Single(int num)
{
for (int i=0; i<v.size(); ++i)
{
if (v[i] == num)
return false;
}
return true;
}
//組合
void perm(int n, int m)
{
if (m == 0)
{
++count;
return;
}
for (int i=1; i<=n; ++i)
{
if (Single(i))
{
v.push_back(i);
perm(n, m-1);
v.pop_back();
}
}
}
int main()
{
int n, m;
cin >> n >> m;
perm(n, m);
cout << count << endl;
return 0;
}
全排列方法二:分治法(數組中可以有重複元素,下一個程序就是怎樣消除重複)
#include <iostream>
using namespace std;
int count = 0;
void swap(int* arr, int i, int j)
{
arr[i] = arr[i] ^ arr[j];
arr[j] = arr[i] ^ arr[j];
arr[i] = arr[i] ^ arr[j];
}
void perm(int* arr, int len, int m, int n)
{
//生成了一種全排列
if (m == n)
{
++count;
return;
}
for (int i=m; i<=n; ++i)
{
swap(arr, m, i);
perm(arr, len, m+1, n);
swap(arr, m, i);
}
}
int main()
{
int arr[9];
for (int i=0; i<9; ++i)
{
arr[i] = i+1;
}
perm(arr, 9, 0, 8);
cout << count << endl;
return 0;
}
分治法:全排列(數組中可以有重複的元素ADCD)
#include <iostream>
#include <string>
using namespace std;
string str;
string* pwd;
int n;
int count = 0;
void swap(int index, int i, int j)
{
char tmp = pwd[index][i];
pwd[index][i] = pwd[index][j];
pwd[index][j] = tmp;
}
//判斷是否重複出現
//一般的全排列算法只能排列數組中沒有重複的數字或字母
//但是如果出現重複的情況下比如aaaabbbb進行全排列,好多排列雖然看似不重複,但是結果是重複的
//這種方法進行全排列時(分治法),從1-n個元素中選n次,每次選1個之前不同的數,然後對剩下的數進行全排列
//也就是perm(R)=(r1)perm(R1),(r2)perm(R2),(r3)pwem(R3)....(rn)perm(Rn)
//每次i和k交換的時候,重點是:
//1、arr[i]!=arr[k],因爲這樣交換後剩下的元素是一樣的,兩種全排列是重複的
//2、arr[k+1]--->arr[i-1],也就是k和i之間的元素不能和arr[i]相等,因爲k和i之間的元素都是和k交換過的,
//如果arr[i]和arr[k+1]--->arr[i-1]之間的元素有相等的,那麼這兩種排列也是重複的
//下面代碼中||運算前的是第一種情況,或運算之後的是第二種情況
bool Appear(int index, char target, int begin, int end)
{
for (int i=begin; i<=end; ++i)
{
if (target == pwd[index][i])
return true;
}
return false;
}
void perm(int index, int k, int m)
{
//得到一種全排列
if (k == m)
{
//判斷在不在str中
if (str.find(pwd[index]) != -1)
{
++count;
}
return;
}
//全排列
for (int i=k; i<=m; ++i)
{
//判斷字母是否重複
if ((i!=k && pwd[index][i]==pwd[index][k]) || Appear(index, pwd[index][i], k+1, i-1))
continue;
swap(index, k, i);
perm(index, k+1, m);
swap(index, k, i);
}
}
int main()
{
cin >> str;
cin >> n;
pwd = new string[n];
for (int i=0; i<n; ++i)
cin >> pwd[i];
for (int i=0; i<n; ++i)
{
perm(i, 0, 7);
}
cout << count << endl;
delete[] pwd;
return 0;
}