C++中的排列组合算法详解

引言:排列组合的重要性

排列组合是计算机科学中的基础算法,广泛应用于密码学、数据分析、游戏开发等领域。在C++中,我们通常使用深度优先搜索(DFS)和回溯法来实现排列组合算法。本文将详细解析全排列、组合及其变种的实现原理和代码。

一、全排列算法

1.基本概念:

全排列是指从n个不同元素中取出n个元素,按照一定的顺序排列的所有可能情况。例如,元素{1,2,3}的全排列有6种:123、132、213、231、312、321。

2.算法实现:

#include <iostream>
using namespace std;
const int N = 101; // 数据范围
int a[N];          // 存储待排列的数
int show[N];       // 存储已排列的数
int n;             // 数的个数
bool check[N];     // 标记数字是否已使用

void dfs(int showid) {
    if (showid == n + 1) { // 完成一种排列
        for (int i = 1; i <= n; i++)
            cout << show[i] << " ";
        cout << endl;
        return;
    }
    for (int i = 1; i <= n; i++) {
        if (!check[i]) {         // 选择未使用的数字
            check[i] = true;     // 标记为已使用
            show[showid] = a[i]; // 放入当前位置
            dfs(showid + 1);     // 递归处理下一个位置
            check[i] = false;    // 回溯,取消标记
        }
    }
}

int main() {
    cin >> n;
    for (int i = 1; i <= n; i++)
        cin >> a[i];
    dfs(1); // 从第一个位置开始
    return 0;
}

3.算法解析:

  1. DFS框架:使用递归实现深度优先搜索

  2. 标记数组check[]记录数字使用状态

  3. 回溯机制:递归返回后撤销选择

  4. 时间复杂度:O(n!) - 阶乘复杂度

二、有重复数字的全排列

1.问题特点:

当输入数组包含重复数字时,基本全排列算法会产生重复结果。例如,输入{1,1,2}会产生3种排列而不是2种。

2.优化实现:

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 101;
int a[N];
int show[N];
int n;
bool check[N];

void dfs(int showid) {
    if (showid == n + 1) {
        for (int i = 1; i <= n; i++)
            cout << show[i] << " ";
        cout << endl;
        return;
    }
    for (int i = 1; i <= n; i++) {
        // 关键去重逻辑
        if (i > 1 && a[i] == a[i - 1] && !check[i - 1])
            continue;
        if (!check[i]) {
            check[i] = true;
            show[showid] = a[i];
            dfs(showid + 1);
            check[i] = false;
        }
    }
}

int main() {
    cin >> n;
    for (int i = 1; i <= n; i++)
        cin >> a[i];
    sort(a + 1, a + 1 + n); // 必须先排序
    dfs(1);
    return 0;
}

3.去重关键:

  1. 排序预处理:确保相同元素相邻

  2. 跳过条件if (i>1 && a[i]==a[i-1] && !check[i-1])

    • 当前元素与前一个相同

    • 前一个元素未被使用(说明是新层级)

  3. 时间复杂度:最坏情况仍为O(n!)

三、组合算法

1.基本概念:

组合是指从n个不同元素中取出r个元素,不考虑顺序。例如,从{1,2,3,4}中取2个元素的组合有6种:{1,2}、{1,3}、{1,4}、{2,3}、{2,4}、{3,4}。

2.算法实现:

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 101;
int a[N];
int show[N];
bool check[N];
int n, r; // n个数中取r个

void dfs(int showid) {
    if (showid == r + 1) { // 选够r个数
        for (int i = 1; i <= r; i++)
            cout << show[i] << " ";
        cout << endl;
        return;
    }
    for (int i = 1; i <= n; i++) {
        // 避免重复组合的关键条件
        if (!check[i] && a[i] > show[showid - 1]) {
            check[i] = true;
            show[showid] = a[i];
            dfs(showid + 1);
            check[i] = false;
        }
    }
}

int main() {
    cin >> n >> r;
    for (int i = 1; i <= n; i++)
        cin >> a[i];
    sort(a + 1, a + 1 + n); // 排序确保升序
    show[0] = -1; // 初始值小于所有可能数
    dfs(1);
    return 0;
}

3.组合关键点:

  1. 避免重复:只选择比前一个元素大的数

  2. 排序预处理:确保数组有序

  3. 初始值设置show[0] = -1作为哨兵值

  4. 时间复杂度:O(C(n,r)) - 组合数复杂度

四、带重复数字的组合

1.问题特点:

当输入包含重复数字时,需要避免生成重复的组合结果。

2.优化实现:

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 101;
int a[N];
int show[N];
bool check[N];
int n, r;

void dfs(int showid) {
    if (showid == r + 1) {
        for (int i = 1; i <= r; i++)
            cout << show[i] << " ";
        cout << endl;
        return;
    }
    for (int i = 1; i <= n; i++) {
        // 去重逻辑
        if (i > 1 && a[i] == a[i - 1] && !check[i - 1])
            continue;
        // 组合条件
        if (!check[i] && a[i] >= show[showid - 1]) {
            check[i] = true;
            show[showid] = a[i];
            dfs(showid + 1);
            check[i] = false;
        }
    }
}

int main() {
    cin >> n >> r;
    for (int i = 1; i <= n; i++)
        cin >> a[i];
    sort(a + 1, a + 1 + n);
    show[0] = -1; // 初始哨兵值
    dfs(1);
    return 0;
}

3.去重关键:

  1. 排序预处理:确保相同元素相邻

  2. 跳过条件if (i>1 && a[i]==a[i-1] && !check[i-1])

  3. 组合条件a[i] >= show[showid-1](允许相等)

五、组合的高效实现

1.优化思路:

使用起始索引避免重复选择和标记数组

#include<iostream>
#define N 30
using namespace std;
int n, r;
int a[N]; // 存储当前组合
int vis[N]; // 标记数组

void dfs(int step) {
    if (step == r + 1) { // 完成组合
        for (int i = 1; i <= r; i++)
            cout << a[i] << " ";
        cout << endl;
        return;
    }
    // 从上一个数开始选择
    for (int i = a[step - 1]; i <= n; i++) {
        if (!vis[i]) {
            a[step] = i;
            vis[i] = 1;
            dfs(step + 1);
            vis[i] = 0;
        }
    }
}

int main() {
    cin >> n >> r;
    a[0] = 1; // 初始化起始值
    dfs(1);
    return 0;
}

2.算法优势:

  1. 避免重复:通过i = a[step-1]确保顺序选择

  2. 减少循环:不需要遍历所有数字

  3. 空间优化:不需要存储原始数组

六、排列组合算法对比

算法类型特点时间复杂度空间复杂度关键技巧
全排列所有元素排列O(n!)O(n)标记数组、回溯
有重复全排列处理重复元素O(n!)O(n)排序、跳过条件
组合选择r个元素O(C(n,r))O(n)排序、升序选择
有重复组合处理重复元素O(C(n,r))O(n)排序、双重条件
高效组合优化选择过程O(C(n,r))O(n)起始索引

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值