全排列的定义
一般地,从n个不同元素中取出m(m≤n)个元素,按照一定的顺序排成一列,叫做从n个元素中取出m个元素的一个排列(permutation)。特别地,当m=n时,这个排列被称作全排列(all permutation)。
n个元素的全排列的个数记为Pn,
我们通过全排列算法,可以枚举出一个集合中,所有元素的排列顺序.
代码示例
public class Permutation {
public static void main(String[] args) {
Perm(new char[]{'a','b','c'},0,3);
}
//全排列递归算法
public static void Perm(char[] arr , int start ,int end)
{
//arr数组存放排列的数,start表示层数,end表示数组的长度
if(start==end)
{
//start==end 表示到达最后一个数,不能再交换,输出数组.
for(int i=0 ;i<end ;i++){
System.out.print(arr[i]);
}
System.out.println();
}
else{
for(int i=start;i<end;i++)
{
swap(arr,i,start);
//层数start+1
Perm(arr,start+1,end);
swap(arr,i,start);
}
}
}
public static void swap(char[] nums,int a , int b)
{
char temp = nums[a];
nums[a] = nums[b];
nums[b] = temp;
}
}
/* 输出效果:
abc
acb
bac
bca
cba
cab
*/
思路解析
全排列算法的示意图
全排列可以看做固定前i位,对第i+1位之后的再进行全排列,比如固定第一位,后面跟着n-1位的全排列。那么解决n-1位元素的全排列就能解决n位元素的全排列了,这样的设计很容易就能用递归实现。
在代码中,start变量表示固定了几位数, 当start==end时,数组中全部元素都被固定,即为一次排列情况,停止递归,输出结果。
swap(arr,i,start); 当我们对序列进行交换之后,就将交换后的序列除去第一个元素放入到下一次递归中去了,递归完成了再进行下一次循环。这是某一次循环程序所做的工作,这里有一个问题,那就是在进入到下一次循环时,序列是被改变了。可是,如果我们要假定第一位的所有可能性的话,那么,就必须是在建立在这些序列的初始状态一致的情况下,所以每次交换后,要还原,确保初始状态一致.参考博文 【https://blog.csdn.net/lemon_tree12138/article/details/50986990】
应用
2016年第七届蓝桥杯java B组省赛试题 第三题
3、凑算式 (结果填空)
这个算式中A-I代表1-9的数字,不同的字母代表不同的数字。
比如:
6+8/3+952/714 就是一种解法,
5+3/1+972/486 是另一种解法。
这个算式一共有多少种解法?
注意:你提交应该是个整数,不要填写任何多余的内容或说明性文字。
解析
本题中,A-I代表1-9的数字,求该算式一共有多少种解法.题目即可转化为数组[1-9]中,有多少种排列情况能满足上式.
public class Permutation {
static int result=0;
public static void main(String[] args) {
Perm(new int[]{1,2,3,4,5,6,7,8,9},0,9);
System.out.println(result);
}
public static void check(int[]arr){
int A=arr[0];
int B=arr[1];
int C=arr[2];
int DEF=arr[3]*100+arr[4]*10+arr[5];
int GHI=arr[6]*100+arr[7]*10+arr[8];
//浮点数不可以做==判断, 等式转化为整型比较
int left=A*C*GHI+B*GHI+DEF*C;
int right=10*C*GHI;
if (left == right){
result++;
}
}
//全排列递归算法
public static void Perm(int[]arr, int start ,int end)
{
//arr数组存放排列的数,start表示层 代表第几个数,end表示数组的长度
if(start==end)
{
check(arr);
}
for(int i=start;i<end;i++)
{
swap(arr,i,start);
Perm(arr,start+1,end);
swap(arr,i,start);
}
}
public static void swap(int[] nums,int a , int b)
{
int temp = nums[a];
nums[a] = nums[b];
nums[b] = temp;
}
}
/* 输出效果:
29
*/
2017蓝桥杯省赛JavaB组 第二题
2.标题:纸牌三角形
A,2,3,4,5,6,7,8,9 共9张纸牌排成一个正三角形(A按1计算)。要求每个边的和相等。
下图就是一种排法(如有对齐问题,参看p1.png)。
A
9 6
4 8
3 7 5 2
这样的排法可能会有很多。
如果考虑旋转、镜像后相同的算同一种,一共有多少种不同的排法呢?
请你计算并提交该数字。
注意:需要提交的是一个整数,不要提交任何多余内容。
解析
同理,本题也是枚举数组[1-9]的排列,判断有多少种情况能满足题目要求. 我们只需修改check()方法即可
本题中返回结果需要除以6,原因是本题排除旋转、镜像情况
旋转: 图形为三角形,对于每一种情况,有另外两种对应旋转情况,因此需要除以3.
镜像:图像的镜像变换分为两种:图像的水平镜像操作是将图像的左半部分和右半部份以图像垂直中轴线为中心镜像进行对换;图像的垂直镜像操作是将图像上半部分和下半部分以图像水平中轴线为中心镜像进行对换。 本题中图形为三角形,对于每一种情况,有另一个镜像情况对应,即左右两边交换,因此需要除以2.
public class Permutation {
static int result=0;
public static void main(String[] args) {
Perm(new int[]{1,2,3,4,5,6,7,8,9},0,9);
System.out.println(result/6);
}
public static void check(int[]arr){
if(arr[0]+arr[1]+arr[3]+arr[5] ==arr[0]+arr[2]+arr[4]+arr[8]
&&arr[0]+arr[1]+arr[3]+arr[5]==arr[5]+arr[6]+arr[7]+arr[8]){
result++;
}
}
//全排列递归算法
public static void Perm(int[]arr, int start ,int end)
{
//arr数组存放排列的数,start表示层 代表第几个数,end表示数组的长度
if(start==end)
{
check(arr);
}
for(int i=start;i<end;i++)
{
swap(arr,i,start);
Perm(arr,start+1,end);
swap(arr,i,start);
}
}
public static void swap(int[] nums,int a , int b)
{
int temp = nums[a];
nums[a] = nums[b];
nums[b] = temp;
}
}
/* 输出效果:
144
*/
总结
全排列算法是一种非常暴力的算法,其时间复杂度为O(n!),但对于某些特定的题目,解决起来非常方便.在蓝桥杯比赛中,基本每年的题目都有1-2题可以使用全排列进行解决.
全排列算法是暴力解题或枚举解题的核心,我们可以把它作为模板进行理解和默写,在比赛中使用该模板可以快速解决问题,节省时间.
本文介绍的是无重复元素的全排列算法的递归实现, 对于有重复元素的全排列算法的实现,欢迎大家去自行实现!