排列组合(两种方法)

排列组合是组合数学的基础,从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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章