一篇文章带你对全排列从入门到精通

【华为OD机试真题 2022&2023】真题目录 @点这里@

【华为OD机试真题】信号发射和接收 &试读& @点这里@

【华为OD机试真题】租车骑绿道 &试读& @点这里@

从n个数中选取m个数的组合及全排列

友情提示
关于标准库中next_permutation的使用:在初始序列为有序状态时,才能保证输出全部的且按字典顺序排列的序列,因为next_permutation是输出下一个状态的序列,所以为保证通用性,建议大家掌握常规的全排列方法(里面包含了字典序排序及去重的方法)
本博文下面也有,也可参看另外一篇博文力扣上的两个全排列的题目
题目描述:
给定两个整数n、m,输出从(1,2,…,n)中选出m个数的所有组合。
输入:
每个测试文件含有多个数据,输入两个整数n,m(0<m<=n<=10)。输入到文件末尾结束。
输出:
输出对应的组合数,每个组合中的数字由小到大排列,对于每组数据的所有组合按字典序排序

/下面这个组合代码能够满足字典排序输出
#include <iostream>

using namespace std;
int n, m, lst[10] = {0};
void func(int a, int b, int c)
{
    if (c != 0 && c <= b - a + 1)
    {
        lst[m - c] = a;//
        func(a + 1, b, c - 1);//这两行是分两种情况讨论,进行递归
        func(a + 1, b, c);
    }
    else if (c == 0)
    {
        cout << lst[0];
        for (int j = 1; j < m; ++j)
            cout << ' ' << lst[j];
        cout << endl;
    }

}
int main()
{
    while (cin >> n >> m)
        func(1, n, m);
    return 0;
}
/*基本思想:从1到n中选取m个数,按照分治法可以分为两种情况:
1、选取了1,那么从2到n中选取m-1个数
2、没选取1,那么从2到n中选取m个数
使用数组储存选取的数字,递归到选取0个数时即输出一组结果
需要注意一种情况,从a到b中选取c个数,如果c>b-a+1,即剩余数字个数都不够要选取的个数
就不需要进行递归了*/

下面的题目是从n个数字中选取m个,然后再对这m个数字分别进行全排列

//该全排列代码是在前面组合代码基础数修改而来,使用STL也可以满足字典顺序输出
#include <iostream>
#include <iterator>//iostream_iteraotr
#include <algorithm>//next_permutation
using namespace std;
int n, m, lst[10] = {0};//选出来的m个数字的存放位置,先对数组初始化为全0
void permutations(int lists[], int m)//相比上面的组合方法,仅增加了这个全排列函数
{
    do{
        copy(lists, lists+m-1, ostream_iterator<int>(cout, " "));
        cout << lists[m-1];
        cout << endl;
    }while (next_permutation(lists, lists+m));
}
void func(int a, int b, int c)
{
    if (c != 0 && c <= b - a + 1)
    {
        lst[m - c] = a;//
        func(a + 1, b, c - 1);//这两行是分两种情况讨论,进行递归
        func(a + 1, b, c);
    }
    else if (c == 0)
    {
        /*cout << lst[0];
        for (int j = 1; j < m; ++j)
            cout << ' ' << lst[j];
        cout << endl;*/
        permutations(lst, m);
    }

}
int main()
{
    while (cin >> n >> m)
        func(1, n, m);
    return 0;
}
n个数字的全排列

题目描述
给定一个整数n, 输出1~n的全排列
输入
每个测试文件只有一个数据,输入一个整数n(0<n<=8)。
输出
输出全排列(每个排列中的数字用空格隔开),且每组排列注意字典序输出所有排列(即要先输出123才能输出132)

next_permutstion解法
//STL解法:
#include <iostream>
#include <algorithm>
#include <iterator>
using namespace std;
const int Length = 8;
int n, counts = 0;
int factorial(int n);
template<class T>
void permutations(T list[], int m)//这里和常规的解法不同,没有用开始参数k
{
    do{
        copy(list, list+m-1, ostream_iterator<T>(cout, " "));
        cout << list[m-1];
        counts++;
        if (counts < factorial(n))
            cout << endl;
    }while (next_permutation(list, list+m));
}

int main()
{
    cin >> n;
    int a[Length];//可以把n替换成一个较大的常量
    for (int i = 0; i < n; ++i)
        a[i] = i+1;
    /*char a[] = "abcde";
	permutations(a, strlen(a));这里稍作修改就可以实现字符串的全排列*/
    permutations(a, n);
    return 0;
}

int factorial(int n)//主要是为了输出格式而增加的函数
{
    int result = 1;
    for (int i = 1; i <= n; ++i)
    {
        result *= i;
    }
    return result;
}
prev_permutation方法
发散思维(使用prev_permutation()实现字典逆序排列输出):
#include <iostream>
#include <algorithm>
#include <iterator>
using namespace std;
const int Length = 8;
int n, counts = 0;
int factorial(int n);
template<class T>
void permutations(T list[], int m)
{
    do{
        copy(list, list+m-1, ostream_iterator<T>(cout, " "));
        cout << list[m-1];
        counts++;
        if (counts < factorial(n))
            cout << endl;
    }while (prev_permutation(list, list+m));//改动点
}

int main()
{
    cin >> n;
    int a[Length];
    for (int i = 0; i < n; ++i)
        a[i] = n - i;//改动点
    permutations(a, n);
    return 0;
}

int factorial(int n)
{
    int result = 1;
    for (int i = 1; i <= n; ++i)
    {
        result *= i;
    }
    return result;
}
常规解法
//常规解法
#include <iostream>
#include <algorithm>
#include <iterator>
using namespace std;
const int Length = 8;
int n, counts = 0;
void swap(int& a, int& b);
int factorial(int n);
template<class T>
void permutations(T list[], int k, int m)
{
    if (k == m)
    {
        copy(list, list+m-1, ostream_iterator<T>(cout, " "));
        cout << list[m-1];
        counts++;
        if (counts < factorial(n))
            cout << endl;

    }
    else
    {
        for (int i = k; i < m; ++i)//注意从0开始到m-1,共m个数字
        {
            sort(list + k, list + m);//在每一步交换之前排序,保证输出按字典排序
            swap(list[k], list[i]);
            permutations(list, k+1, m);
            swap(list[k], list[i]);
        }
    }

}
int main()
{
    cin >> n;
    int a[Length];
    for (int i = 0; i < n; ++i)
        a[i] = i+1;
    permutations(a, 0, n);
    return 0;
}

void swap(int& a, int& b)
{
    int temp;
    temp = a;
    a = b;
    b = temp;
}
int factorial(int n)
{
    int result = 1;
    for (int i = 1; i <= n; ++i)
    {
        result *= i;
    }
    return result;
}

关于字符串的部分组合输出

这部分代码是比较通用的,可以是部分数字,也可以是部分字符的组合输出
如果还需要实现全排列,直接把全排列函数加入即可
现在这里新增一个比较好的通用代码

#include<iostream>
#include<vector>
#include <cstring>
#include <iterator>
#include <sstream>
using namespace std;

//从头扫描字符串得到第一个字符,针对第一个字符,有两种选择
//把这个字符放到组合中去,接下来我们需要在剩下的n-1个字符中选取m-1个字符;
//如果不把这个字符放到组合中去,则需要在剩下的n-1个字符中选取m个字符
void Combination(const char* str, int number, vector<char>& result)
{
	if (number == 0)
	{
		copy(result.begin(), result.end(), ostream_iterator<char>(cout, ""));
		cout << endl;
		return;//比较重要,不能去掉
	}
	if (*str == '\0')
		return;
	result.push_back(*str);
	Combination(str + 1, number - 1, result);//把这个字符放到组合中去,接下来我们需要在剩下的n-1个字符中选取m-1个字符
	result.pop_back();
	Combination(str + 1, number, result);//不把这个字符放到组合中去,则需要在剩下的n-1个字符中选取m个字符
}

void Combination(const char* str, int length)
{
	if (str == nullptr)
		return;
	//int length = strlen(str);
	vector<char> result;
    Combination(str, length, result);
}

int main()
{
	//char s[] = "abc";
    int n, m;
    cin >> n >> m;
    stringstream stream;
    stream.clear();
    stream.str("");
    for (int i = 1; i <= n; ++i)
    {
        stream << i;
    }
    string s = stream.str();
	Combination(s.data(), m);
	return 0;
}

// 求从数组a[1..n]中任选m个元素的所有组合。
// a[1..n]表示候选集,n为候选集大小,n>=m>0。
// b[1..M]用来存储当前组合中的元素(这里存储的是元素下标),
// 常量M表示满足条件的一个组合中元素的个数,M=m,这两个参数仅用来输出结果。
#include <iostream>
#include <string>
const int M = 3;
using namespace std;
void combine(char a[], int n, int m, char b[])
{
	for (int i = m; i <= n; i++)// 注意这里的循环范围,正向的话就会按字典顺序输出
	{//从尾到头存入下标,到m=1时,已存了0~m-1的下标,开始输出前三个数字,
	//然后递归回退,再递归,等到m=1时,再输出,如此循环
		b[m - 1] = i - 1;//把下标存储到数组b中
		if (m > 1)
			combine(a, i - 1, m - 1, b);
		else // m == 1, 输出一个组合
		{
			for (int j = 0; j < M; j++)
				cout << a[b[j]] << " ";
			cout << endl;
		}
	}
}

int main()
{
	char a[] = "abcde";
	char b[M];
	combine(a, strlen(a), M, b);
	return 0;
}

如果需要再进一步进行全排列的话

//下面是对上面方法的进一步拓展,对选出的组合再进行全排列
// 求从数组a[1..n]中任选m个元素的所有组合。
// a[1..n]表示候选集,n为候选集大小,n>=m>0。
// b[1..M]用来存储当前组合中的元素(这里存储的是元素下标),
// 常量M表示满足条件的一个组合中元素的个数,M=m,这两个参数仅用来输出结果。
#include <iostream>
#include <string>
#include <cstring>
#include <iterator>
#include <algorithm>
const int M = 3;
using namespace std;
template<class T>
void permutations(T list[], int m)//这里和常规的解法不同,没有用开始参数k
{
    do{
        copy(list, list+m-1, ostream_iterator<T>(cout, " "));
        cout << list[m-1];
        cout << endl;
    }while (next_permutation(list, list+m));
}
void combine(char a[], int n, int m, char b[])
{
	for (int i = m; i <= n; i++)// 注意这里的循环范围,正向的话就会按字典顺序输出
	{//从尾到头存入下标,到m=1时,已存了0~m-1的下标,开始输出前三个数字,
	//然后递归回退,再递归,等到m=1时,再输出,如此循环
		b[m - 1] = i - 1;//把下标存储到数组b中
		if (m > 1)
			combine(a, i - 1, m - 1, b);
		else // m == 1, 输出一个组合
		{
            char permu[M];//把选出的一种组合转存入另一个数组中,方便进行进一步全排列
            for (int j = 0; j < M; j++)
				permu[j] = a[b[j]];
            permutations(permu, M);
		}
	}
}

int main()
{
	char a[] = "abcde";
	char b[M];
	combine(a, strlen(a), M, b);
	return 0;
}
对于全排列中有重复的情况的处理

题目描述:
给定一个包含大写英文字母的字符串S,要求你给出对S重新排列的所有不同的排列数。
如:S为ABA,则不同的排列有ABA,AAB,BAA三种。
输入:
输入一个长度不超过10的字符串S,我们确保都是大写的
输出:
输出S重新排列的所有不同排列数(包含自己本身)。
样例:
ABA
3

ABCDEFGHHA
907200

AABBCC
90

//STL解法,能够自动处理字符重复的情况;

#include <iostream>
#include <algorithm>
#include <iterator>
#include <string>

using namespace std;
int counts = 0;
template<class T>
void permutations(T lists)
{
	do {//注意被发送到输出流的数据类型是char
		copy(lists.begin(), lists.end(), ostream_iterator<char>(cout, " "));
		cout << endl;
		counts++;
	} while (next_permutation(lists.begin(), lists.end()));
}

int main()
{
	string str;
	getline(cin, str);
	//vector<char> a(str.begin(), str.end());如果用vector的话,可以这样初始化P697有详细的初始化方法
	sort(str.begin(), str.end());
	permutations(str);
	cout << counts << endl;
	return 0;
}
//常规解法:需要增加一个函数来处理重复问题
#include <iostream>
#include <algorithm>
#include <iterator>
#include <string>
using namespace std;
int counts = 0;

bool HasDuplicate(char b[], int start, int i) //i为该元素下标
{
	for (int k = start; k < i; k++)   //判断在该元素之前有没有相同元素
		if (b[k] == b[i])
			return false;         //如果有,则返回false
	return true;
}
template<class T>
void permutations(T lists, int k, int m)
{
	if (k == m)
	{
		cout << lists << endl;
		counts++;
	}
	else
	{
		for (int i = k; i < m; ++i)
		{
			sort(&lists[k], &lists[m]);//每次交换之前排序,保证字典顺序输出
			if (HasDuplicate(&lists[0], k, i))//去重
			{
				swap(lists[k], lists[i]);
				permutations(lists, k + 1, m);
				swap(lists[k], lists[i]);
			}
		}
	}

}

int main()
{
	string str;
	getline(cin, str);
	int len = str.size();
	permutations(str, 0, len);
	cout << counts << endl;
	return 0;
}
下面是关于排列组合的算法

组合算法

本程序的思路是开一个数组,其下标表示1到m个数,数组元素的值为1表示其下标
代表的数被选中,为0则没选中。
首先初始化,将数组前n个元素置1,表示第一个组合为前n个数。
然后从左到右扫描数组元素值的“10”组合,找到第一个“10”组合后将其变为
“01”组合,同时将其左边的所有“1”全部移动到数组的最左端。
当第一个“1”移动到数组的m-n的位置,即n个“1”全部移动到最右端时,就得
到了最后一个组合。
例如求5中选3的组合:


1 1 1 0 0 //1,2,3
1 1 0 1 0 //1,2,4
1 0 1 1 0 //1,3,4
0 1 1 1 0 //2,3,4
1 1 0 0 1 //1,2,5
1 0 1 0 1 //1,3,5
0 1 1 0 1 //2,3,5
1 0 0 1 1 //1,4,5
0 1 0 1 1 //2,4,5
0 0 1 1 1 //3,4,5

全排列算法

从1到N,输出全排列,共N!条。
分析:用N进制的方法吧。设一个N个单元的数组,对第一个单元做加一操作,满N进
一。每加一次一就判断一下各位数组单元有无重复,有则再转回去做加一操作,没
有则说明得到了一个排列方案

例如:求1-3的全排列,共3!条
设数组初始状态为0 0 0,以下
为计算全排列的步骤:
0 0 0 +1
1 0 0 +1
2 0 0 +1
3 0 0 满3进1 ->
0 1 0 +1
1 1 0 +1
2 1 0 +1
3 1 0 满3进1 ->
0 2 0 +1
1 2 0 +1
2 2 0 +1
3 2 0 满3进1 ->
0 3 0 满3进1 ->
0 0 1 +1
1 0 1 +1
2 0 1 +1
3 0 1 满3进1 ->
0 1 1 +1
1 1 1 +1
2 1 1 +1
3 1 1 满3进1 ->
0 2 1 +1
1 2 1 +1
2 2 1 +1
3 2 1 满3进1 ->
0 3 1 满3进1 ->
0 0 2 +1
1 0 2 +1
2 0 2 +1
3 0 2 满3进1 ->
0 1 2 +1
1 1 2 +1
2 1 2 +1
3 1 2 满3进1 ->
0 2 2 +1
1 2 2 +1
2 2 2 +1
3 2 2 满3进1 ->
0 3 2 满3进1 ->
0 0 3 满3进1 ->
0 0 0

共6个YES,每一个对应一种排列。
  • 3
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

笑着的程序员

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值