排列组合算法

本文深入探讨全排列与组合算法的实现方法,包括递归方法、字典序法及迭代方法,详细解释每种方法的思路与实现过程,旨在帮助开发者理解并应用这些算法解决实际问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

全排列

递归方法

思路描述

  • 将字符串分为两组,第一组为第一个字符,第二组为剩余的N-1个字符:
    • 确定可以放在第一个位置的字符,即:将第一个字符与之后N-1个字符进行交换得到
    • 对之后的N-1个字符做全排列

算法实现

void permutation(char *str, char *pBegin) {
	if (*pBegin == '\0') {
		cout << str << endl;
	}

	for (char *pos =  pBegin; *pos != '\0'; pos++) {
		char tmp = *pos;
		*pos = *pBegin;
		*pBegin = tmp;
		
		permutation(str, pBegin+1);
		
		tmp = *pos;
		*pos = *pBegin;
		*pBegin = tmp;

	}
}

字典序法(迭代)

思路描述

  • 在当前序列中,从尾端向前寻找两个相邻元素,前一个记为*i,后一个记为*t,并且满足*i < *t。然后再从尾端寻找另一个元素*j,如果满足*i < *j,即将第i个元素和第j个元素对调,并将t个元素之后的所有元素颠倒排序,即就出下一个序列了。
  • 如果到最后一个排列next_permutation会返回false,但使用了这个方法后,序列会变成字典序列的第一个,如cba变成abc。就可以继续next了,可以参考next_permutation用法。

算法实现

#include <iostream>
#include <algorithm>
using namespace std;

typedef string::iterator StringItr;

void itr_swap(StringItr i, StringItr j)
{
    char tmp = *i; 
    *i = *j; 
    *j = tmp;
}

void reverse(StringItr i, StringItr j) {
    while (i < j) {
        itr_swap(i, j); 
        ++i, --j;
    }   
}

bool next(string& str) 
{

    StringItr start = str.begin();
    StringItr end = str.end() - 1;
    if(start == end) {
        return false;
    }
    StringItr i = start;
    ++i;
    if (i == end)
        return false;
    i = end;

    while(true) {
        StringItr t = i;
        --i;
        if (*i < *t) {
            StringItr j = end;

            while(!(*i < *j))
                j--;

            itr_swap(i, j);
            reverse(t, end);
            return true;
        }

        if (i == start) {
            //all reverse, generate min dictionary
            reverse(start, end);
            return false;
        }
    }
}

void permutation(string& str)
{
    if (str.empty())
        return;

    StringItr start = str.begin();
    StringItr end = str.end();

    sort(start, end);
    do{
        cout << ">>" << str << endl;
    }while(next(str));
}

int main(int argc, char *argv[])
{
    if (argc == 2) {
        string str(argv[1]);
        permutation(str);
    } else {
        string str = "bac";
        permutation(str);
    }
}

组合

递归方法

思路描述

  • 对每一个字符而言,可以选择将其加入组合中,也可以选择不将其加入组合中:
    • 若选择加入组合,则从后N-1个字符中,选择m-1个字符加入到组合中
    • 若不选择加入组合,则从后N-1个字符中,选择m个字符加入到组合中

算法实现

#include <iostream>
using namespace std;
typedef string::iterator StringItr;

void _combination(string& str, StringItr current, int m, char *b, int index)
{
    if (current == str.end())
        return;
    
    //don't choose current pos
    _combination(str, current+1, m, b, index);

    //choose current pos
    b[++index] = *current;
    _combination(str, current+1, m, b, index);

    if (index == (m-1)) {
        b[m] = '\0';
        cout << b << endl;
    }   
}

void combination(string& str) 
{
    char b[str.size() + 1]; 
    for (int i = 1; i < str.size(); i++ ) { 
        _combination(str, str.begin(), i, b, -1);
    }   
}

int main(int argc, char *argv[])
{
    if (argc == 2) {
        string str(argv[1]);
        combination(str);
    } else {
        string str = "abc";
        combination(str);
    }   
    return 0;
}


迭代方法-01法

思路描述

  • 展开一个数组,其下标表示1~N个数,数组元素的值为1表示其代表的数被选中,为0表示没被选中。
    • 初始化:将数组前M个元素置1,表示第一个组合为前M个数
    • 然后从左到右扫描数组元素值为“10”组合,找到第一个“10”组合后将其变为01组合,同时将左边所有的“1”全部移动到数组的最左端。
    • 当第一个1移动到数组的n-m的位置时,M个1全部移动到最右端,就得到了最后一个组合

算法实现

#include <iostream>
using namespace std;

void print_result(string& str, int *loc)
{
    int length = str.size();
    for (int i = 0; i < length; i++) {
        if (loc[i])
            cout << str[i];
    }   
    cout << endl;
}

int first_1_pos(int *loc, int length)
{
    for (int i = 0; i < length; i++) {
        if(loc[i]) {
            return i;
        }   
    }   
    return -1; 
}

int first_10_pos(int *loc, int length)
{
    for (int i = 0; i < length - 1; i++) {
        if(loc[i] && loc[i+1] == 0) {
            return i;
        }
    }
    return -1;
}

void swap(int &a, int &b)
{
    a = b + a;
    b = a - b;
    a = a - b;
}

void move_1_to_left(int *loc, int end)
{
    for (int old = 0, pos = 0; old < end; old++) {
        if (loc[old] && pos != old) {
            loc[pos++] = 1;
            loc[old] = 0;
        }
    }
}

void _combination(string& str, int *loc, int m)
{
    //initialize loc array
    int length = str.size();
    for (int i = 0; i < length; i++) {
        if (i < m)
            loc[i] = 1;
        else
            loc[i] = 0;
    }
    print_result(str, loc);

    int first_1 = first_1_pos(loc, length);
    int end_condition = length - m;

    //loop to search all m combinations
    while (first_1 != end_condition) {
        int first_10 = first_10_pos(loc, length);

        swap(loc[first_10], loc[first_10+1]);
        move_1_to_left(loc, first_10);

        print_result(str, loc);

        first_1 = first_1_pos(loc, length);
    }
}

void combination(string& str)
{
    int length = str.size();
    int loc[length];
    for (int i = 1; i < length; i++) {
        _combination(str, loc, i);
    }
}

int main(int argc, char *argv[])
{
    if (argc == 2) {
        string str(argv[1]);
        combination(str);
    } else {
        string str = "abc";
        combination(str);
    }
    return 0;
}







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值