全排列(无序+按字典序)

全排列

题目来源:open judge1750全排列

全排列 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

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值