全排列
全排列 1.0
#include <bits/stdc++.h>
using namespace std;
void permutation(char a[],int m,int n){//对下标为m_n的字符数组段进行排列
if(m==n){//安排好了最后一位
for(int i=0;i<=n;i++){
cout<<a[i];
}
cout<<endl;
}
for(int i=m;i<=n;i++){
swap(a[m],a[i]);//每个字符轮流排第一,安排好了第一位
permutation(a,m+1,n);
//排好了第二位直到末尾的字符段,则完成了一种全排列
swap(a[m],a[i]);
//完成了一种全排列之后,把之前的交换都恢复
//(先恢复最后一次m==n时交换的,
//那么 permutation(a,n,n)完成了,就回溯到
// permutation(a,n-1,n),恢复m==n-1时交换的 ……
}
}
int main(int argc, char** argv) {
char str[10];
cin>>str;
int len=strlen(str);
permutation(str,0,len-1);
return 0;
}
这种交换不能按照字典序进行全排列,为什么呢?
注意题目要求,“给定的字符串中的字母已经按照从小到大的顺序排列”
想要达到目的,每个字符轮流做第一位确保得到所有组合情况,但却没能
确保除第一位以外,剩余的各位仍然按照从小到大的顺序排列
在数组中直接将某位移到第一位,对剩余字符的处理却是 将第一位直接抛到后面不知道某位,则此次排序直接不能保证升序 之前一直被两个swap函数迷惑住,以为换回来是为了保证有序的,并不是,换回来是为了得到全排列组合后回到原来的最初始的序列 进行另外的全排列。
全排列 的内容有序与否,是第一次交换后的结果,极限思维,更新第一位后排列得到的 //123 321
第一种组合都是 仅仅更新第一位的序列,直接无序了
全排列 2.0
受到以上思想的启发,想要达到目的——每个字符轮流做第一位确保得到所有组合情况,并确保除第一位以外,剩余的各位仍然按照从小到大的顺序排列 。”注意题目要求,“给定的字符串中的字母已经按照从小到大的顺序排列”,那么直接维持原有序列就好,将某位字符移到第一位,剩余字符保持原序,在数组小标1——n的位置放好。
#include <bits/stdc++.h>
using namespace std;
void swap1(char* a,int m,int i){//对下标为m_n的字符数组段进行排列,把a[i]提到首位m
char temp=a[i];
// a[i]=a[i-1] a[i-1]=a[i-2] ……a[m+1]=a[m]
for(int j=i;j>=m+1;j--){
a[j]=a[j-1];
}
a[m]=temp;
}
void swap2(char *a,int m,int i){
char temp=a[m];
// a[m]=a[m+1] a[m+1]=a[m+2] ……a[i-1]=a[i]
for(int j=m;j<=i-1;j++){
a[j]=a[j+1];
}
a[i]=temp;
}
void permutation1(char a[],int m,int n){//对下标为m_n的字符数组段进行排列
if(m==n){//安排好了最后一位
for(int i=0;i<=n;i++){
cout<<a[i];
}
cout<<endl;
}
for(int i=m;i<=n;i++){
// swap(a[m],a[i]);//每个字符轮流排第一,安排好了第一位
swap1(a,m,i);
permutation1(a,m+1,n);
//排好了第二位直到末尾的字符段,则完成了一种全排列
// swap(a[m],a[i]);
swap2(a,m,i);
//完成了一种全排列之后,把之前的交换都恢复
//(先恢复最后一次m==n时交换的,
//那么 permutation(a,n,n)完成了,就回溯到
// permutation(a,n-1,n),恢复m==n-1时交换的 ……
}
}
int main(int argc, char** argv) {
char str[10];
cin>>str;
int len=strlen(str);
permutation1(str,0,len-1);
return 0;
}
在写函数permutation1 的时候,又发现一个不清楚的问题,就是 想把子函数中数组的形参 声明为数组的引用。
数组的形参声明方式有:
(一)、chara
对于这种形式,老师以前上课讲过,用字符串地址的方式声明字符串,在内存中开了一个足够大的缓冲区。
用char声明的是常量字符数组,所以之前用这种方式声明数组来做事总是出错。
当然啦,char*当作形参表示的是char a[]这个数组的地址,倒没有设计到main中数组的生命方式。
(二)、
想用数组的引用来声明形参,
奈何学识浅薄,涉及到了STL模板,此处应有表情包
C/C++对数组的引用
C++对数组的引用
C++对数组的引用(讨论的帖子)
之后再好好看看。
全排列 3.0
同思路2还有另一种实现方式,输出的序列用另一个数组存放,原数组元素从小到大排列的特性不去改变,只要利用这一点,从原数组中从前往后读取并存放在输出数组中,由于从前往后,输出数组读取到的元素一定是从小到大。
#include <bits/stdc++.h>
using namespace std;
char str[10];
char output[10];
int isvisited[10];//对原数组中的元素设置是否进行过访问的标志,因为从
int len=0;
void permutation3(int step){//分别确定output[step]的可能选取情况
if(step==len){//安排好了最后一位
// for(int i=0;i<len;i++){
// cout<<output[i];
// }
// cout<<endl;
// return;
//或者直接
cout<<output<<endl;//output字符数组->字符串嘛
return;
}
else{
for(int i=0;i<len;i++){//原数组的下标
// output[step]=str[i];//一种组合情况中确定好了 output[step],
// 接下来该选取 output[step+1],考虑到进入permutation3(step+1)后又是从str[0]
//开始由小到大选一个 (这种形式就相当于利用递归来实现一个len重循环了)
//,一种组合情况中不可能选两个相同的元素,故对之前选取过的元素
// 需要设置isvisited 标志 ,没访问过的才进行选择。在一种组合没有完成之前,
//str中的元素isvisited标记会逐渐全部变为1,每次需要选标记为0且 下标最小的元素
//直到一次组合完成,再从后往前回溯,逐一将访问标志恢复为0,为下一次组合做准备
if(!isvisited[i]) {
output[step]=str[i];
isvisited[i]=1;
permutation3(step+1);
isvisited[i]=0;
}
}
}
}
int main(int argc, char** argv) {
memset(output,0,sizeof(output));
memset(isvisited,0,sizeof(isvisited));
cin>>str;
len=strlen(str);
permutation3(0);//对于output数组,从output[0]开始讨论每个元素可能的情况
return 0;
}
对于output数组,从output[0]开始讨论每个元素可能的情况
output[step]=str[i];//一种组合情况中确定好了 output[step],
接下来该选取 output[step+1],考虑到进入permutation3(step+1)后又是从str[0]
开始由小到大选一个 (这种形式就相当于利用递归来实现一个len重循环了)
,一种组合情况中不可能选两个相同的元素,故对之前选取过的元素
需要设置isvisited 标志 ,没访问过的才进行选择。在一种组合没有完成之前,
str中的元素isvisited标记会逐渐全部变为1,每次需要选标记为0且 下标最小的元素
直到一次组合完成,再从后往前回溯,逐一将访问标志恢复为0,为下一次组合做准备
全排列 4.0
利用已有的next_permutation函数
介绍:next_permutation函数将按字母表顺序生成给定序列的下一个较大的排列,直到整个序列为降序为止。注意添加头文件#include
使用方法:next_permutation(数组头地址,数组尾地址);
若下一个排列存在,则返回真,如果不存在则返回假
若求上一个排列,则用prev_permutation
next_permutation(str,str+strlen(str))
数组下标从0开始噢,全排列的部分是,str[0]到str[len-1]
#include <bits/stdc++.h>
using namespace std;
int main(int argc, char** argv) {
char str[10];
cin>>str;
int len=strlen(str);
for(int i=0;i<len;i++){
cout<<str[i];//输出字典序最小的全排列
}
cout<<endl;
while(next_permutation(str,str+len)){//利用next_permutation()自动找到
// 比上一个 全排列字典序更大的全排列中最小的那个,如果能找到就返回true,否则返回false
for(int i=0;i<len;i++){
cout<<str[i];
}
cout<<endl;
}
return 0;
}
至于next_permutation()的具体实现方式,参考以下(从别的大佬那儿搬来的):
STL具体操作之next_permutation和prev_permutation函数
#include<iostream>
#include<algorithm>
using namespace std;
int a[12];
bool turn(int a[] ,int n)
{
int k = n - 1;
while(a[k-1] > a[k]) k--;//找到位置k
//当k的位置是0的时候,说明整个排列时递减的,这个排列的字典序最大
if(k == 0) return false;//因此返回false
k = k - 1;
int t = n - 1;
while(a[k] > a[t]) t--;//从后往前找到第一个大于a[k]的数字的位置
swap(a[k] , a[t]);//交换
reverse(a + k + 1, a + n);//翻转
return true;//返回true
}
int main()
{
int n;
cin>>n;
for(int i = 0 ; i<n ; i++) a[i] = i + 1;//输入
for(int i = 0 ; i<n ; i++) cout<<a[i]<<" ";//输出
cout<<endl;
while(turn(a, n))
{
//输出进行一个排序后的排列
for(int i = 0 ; i<n ; i++) cout<<a[i]<<" ";
cout<<endl;
}
return 0;
}
作者:Fphoenix
链接:https://www.acwing.com/solution/content/25275/
来源:AcWing