题目:
给出n个字母,逐行输出所有可能的排列组合。
输入在第一行给出一个数字n,接下一行给出n个字母。
输出所有可能的排列,每种排列占一行。
案例:
输入:
3
abc
输出:
abc
acb
bac
bca
cba
cab
思路:
利用数组内部的有序交换,防止重复,这次我们直接用老板的思维来看待问题,以对ABC进行排列举例,最大老板只需要用A分别与A,B,C进行交换,这里是三种情况三条分支,用循环来控制。下一层有3个同级老板要对BC,AC,BA进行同样的操作,依此类推,详见下图:
最顶层老板思维:
1.制定好规则,保证下一层老板的行为和所解决的问题是和自己一样的,也就是递归的形式不(递归重复部分),只是规模变小了(递归变量设计)。而规模最小自然也就是递归的截止条件了(递归截止条件)。
2.考虑好如何才算是解决问题,这题当任务分配至最小老板,就产生了问题的其中一个解,所以为了保证同级老板的公平性,需要将解决问题的条件变回原样(也就是需要回溯!!) 。回想硬币分配问题,是每个老板汇总方法数,最大老板得到最终汇总才算解决问题,自然不用回溯。
本题难点:
1.多分支循环设计
2.回溯。
3.理解程序的执行时是先纵后横
上代码:
#include <bits/stdc++.h>
using namespace std;
char str[105];
int n;
int fullpermutation(int k);
int main() {
cin >> n;
getchar();
for(int i=0;i<n;i++)
scanf("%c",&str[i]);
fullpermutation(0);
return 0;
}
int fullpermutation(int k) {
if(k==n) {
for(int i=0;i<k;i++)
printf("%c",str[i]);
printf("\n");
}
for(int i=k;i<n;i++) {//很多个分支,用循环!
swap(str[k],str[i]);//每个当前分支,当前拥有有的字母都要打一次头
fullpermutation(k+1);//下个老板
swap(str[i],str[k]);//回溯,因为上一层老板要保证分发下去什么样,收回来就得是什么样。
/*注意和上一题硬币分配的区别,硬币那题上一层老板要得到下一层老板的方法数往上汇总,层层累加
这题最底层老板产生的就是其中一个答案,直接保存或输出,而上一层老板要保证分发下去什么样,
收回来就得是什么样。以便于每个统计老板的分配都是公平的,不会出错的,也就是排序不重复。
*/
}
}
下面加大难度,如果题目要求按字典序排序输出,那么上述算法就无法满足了,原因是交换的思路,无法保证字母是按照字典序排序的,从画的图里也可以看出,那么这要如何解决呢?
首先交换的方式肯定不可行了,那就只能逐个字母进行拼凑,排除重复需要额外定义一个规模为n的标记数组,每次都遍历一遍所有字母,未被标记的就可以加入拼凑行列,拼凑的长度达到n就可以输出,每次递归结束要将标记恢复。细节见代码:
#include <bits/stdc++.h>
using namespace std;
char str[105],ans[105];
int vis[105];
int n,p;
int fullpermutation(int k);
int main() {
cin >> n;
getchar();
for(int i=0;i<n;i++)
scanf("%c",&str[i]);
fullpermutation(0);
return 0;
}
int fullpermutation(int k) {
if(k==n) {
for(int i=0;i<k;i++)
printf("%c",ans[i]);
printf("\n");
}
for(int i=0;i<n;i++) {
if(!vis[i]) {
vis[i] = 1;//标记数组
ans[p++] = str[i];//未被标记纳入拼凑行列
fullpermutation(k+1);
p--;//恢复原状态
vis[i] = 0;
}
}
}
以上~