排列组合是组合数学的基础,从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;
}